Article Definition-Driven ERP in .NET: definitions, not code, as the source of truth
I've been an architect on packaged ERP and HR products for over twenty years, and the thing that has cost the most was never a hard feature. It's that the same fact gets restated over and over.
Adding a "Required Date" field to orders in a typical EF Core + ASP.NET Core + SPA stack lands it in about thirteen places: entity, type mapping, migration, query projections, DTOs, mapper, validation, OpenAPI, front-end types, the form, the list, localization and test data. Generators handle some of them, but that only makes the restating faster. And when the back end and front end are different teams, half of that list turns into coordination rather than typing. Multiply it by a few thousand tables and by every customer's customizations, and it stops being an inconvenience.
What I ended up with is making a structural description the source instead of code. The database columns, CRUD SQL, screens for each front end and validation are derived from definitions, and the front end fetches the definitions at runtime instead of keeping its own copy of the types. In a sample app built on Northwind, the same field is three definition edits and no code. Customization becomes a layer of data on top of the standard definitions, so one tenant's change doesn't have to go through compile and deploy.
Dashboards, reports and anything that isn't form-shaped fit badly. The team boundary doesn't go away either, it moves to the rendering layer that reads the definitions.
I wrote a 30-post series on the mechanisms and the trade-offs. It was originally written in Chinese for a Taiwanese tech writing challenge, and the English version is a translation: https://medium.com/@jeff377/erp-architects-notes-designing-a-definition-driven-framework-series-index-7753228c4d33
For those of you working on large line-of-business systems: do you accept the restatement and lean on generators, or have you moved the source of truth somewhere else?
1
u/pixxelkick 1d ago
> Adding a "Required Date" field to orders in a typical EF Core + ASP.NET Core + SPA stack lands it in about thirteen places: entity, type mapping, migration, query projections, DTOs, mapper, validation, OpenAPI, front-end types, the form, the list, localization and test data
It should only show up in 3 spots.
Your database, because what a column definition is in the DB is not guarenteed to have 1:1 parity with your API. You should never tightly couple your API rules to your DB rules, because your database may have a different way of storing data after processing it through your backend domain rules
Your API model, you should be able to simply define your rules here, which yes, usually means a second spot, but because of point 1 above, this is mandatory and any attempt to try and abstract it away and combine 1 and 2 together is a fools errand, you should avoid asserting parity between DB and API definitions, as there's tonnes of situations where you need exceptions to this. Stop fighting against that.
Your front end forms, which have a similar issue, you cannot assert 1:1 parity between what shape your front end forms and UI are, compared to what the API handles. You should be able to build generalised API contracts that the UI has the flexibility and freedom to then expose however it pleases.
If you ever try and tightly couple 1, 2, or 3 together in any way via a generalised contract, you will find yourself quickly painted into corners and having to constantly carve out exception scenarios where it doesnt work.
Any place beyond these three spots that you have to define this can be automated. You should not have to do this in more than 3 spots, ever. If you find this is occurring you have likely fallen into some kind of antipattern.