This is a topic my colleague and I are discussing a lot the last few weeks because we started a little internal framework/library which should help us building small to medium vertical line-of-business web-apps using ASP.Net MVC. Its an old and widely discussed topic in the industry since years: what are your applications layers and what boundary should one use? Where does the presentation layer end, where or how is the business-logic accessed? How separated is it from the physical layer (data-access)?
There are a lot of layering- and separation-models. Then the more general strategies like Domain-Drive-Design, SOLID, well known patterns like Repository-Pattern or Unit-Of-Work come into play too.
Most topics result in the conclusion that one should not over engineer the solution and just do what the one application needs and what the customer ordered. That’s good if one just looks at one single application. But we have to do many such applications and therefore go further when specifying the architecture for the next few years and many apps. This then are hard to go for KISS (=keep it simple, stupid) – but we try.
Knowing your requirements is always a good start. So what are our requirements?
- The apps should use the same library and concepts so our devs can understand and maintain them easily. Currently each app has its own concepts and new devs need to be trained for each app.
- Fit well for small to medium (10-60 Database Tables) line-of-business apps. They have a lot CRUD but also a decent amount of workflows / business-/domain-logic.
- We not only build API’s but also standard entities and standard MVC UI for these standard entities. For example we have translation entities and optional a whole translation UI as part of the framework.
- We currently work with Entity Framework 6 but will move to Entity Framework Core 2 one day or even move on to document DB’s like MongoDB or Azure Cosmos DB’s in the not so far future. So the physical data-access layer should be decoupled and we like to re-use as much logic and UI’s as we can when replacing the data-access layer.
- The framework/library will be used for multiple apps over the next years as projects come to our table. (we are an IT-Service company in the project business)
- Web-Services (REST etc.) must be supported and building SPA apps e.g. using React is on the horizon.
- We saw that we have to force developers to a clear design/layering otherwise it will mess up (especially if external coders are in the team like near-/off-shoring).
- We like to think of the database like yet another data-storage. We don’t use DB-specific features like stored-procedures etc. – only when there is absolutely no other way.
- Business- / Domain-Logic must be fully unit-testet and stay in the .Net application code.
- Don’t write our own level-level-framework but re-use existing libraries.
- Use ORM’s (object-relational mappers) where possible. Don’t write SQL all over and don’t write our own ORM – even this would be fun 😉 Start with using Entity Framework 6.x and add support for EF Core and others later on – as needed.
- Don’t just separate everything and don’t make every little change a huge effort through 10 layers. We don’t need an enterprise-grad layering (yet 🙂 ). KISS is in our mind.
- The application must NOT be easily switchable from one to another backend (ORM etc) but must be able to choose one at the project start.
- The framework/library must support the all supported backends (EF6/SQL, MongoDB/CosmosDB, EF Core, etc).
- Layers we have in mind:
- Server-side Presentation (like some ASP.Net MVC view-models if needed)
- Business-/Domain-Layer (business-logic).
- Physical data-access (ready and write data into the database or table storage; ORM’s etc).
There are a couple of layer-variations when using an ORM. The following are those that make more or less sense to us. I guess there are about a trillion more variations one could create 🙂
First a note about the server-side view models because I drawn them “a little special”. My experience is that sometimes one does not need to build a 1:1 view-model but can use the entities from the layer below. Sometimes I build a complete custom view-model layer and sometimes I wrap the entities or collections in a view-model and add more data-properties for the view but don’t rebuild all entity-classes. The later is my preferred approach when we talk about server-side view-models. This story is about the same for all the variations and can change easily.
Basically define the EF/ORM Entities and use them all over – from repositories (data-access) to the representation. Its simple and every standard .Net / EF developer can participate from its first day on the project. The major downsides we discovered in our first alpha-versions is, that a) all data-backends need to support the single set of entities (EF, Mongo-Client all run on the same entity classes) and b) that not so experienced developers tend to place the business-logic etc. “somewhere” because they can. Layers are not really enforced. Both are no-go for us.
EF/ORM entities are used for data-access in the repository and given to the domain-layer. Therefore the domain-layer does the mapping from the EF entities to the higher-level business objects. The apps talking to our Domain-Services does get the business-objects (maybe specialised per use-case) and don’t know anything about our ORM entities. A clean separation between presentation and persistence happens. If something changes on the physical-layer the app will not need to know this. The domain-layer can use the ORM entities and things like IQueryable to address complex data-access scenarios and have benefits of the ORM-mindset. On the downside it is more effort comparing to Type 1 as we have an additional model to build, map, test and maintain. It also can result in data-access code within the domain-layer that does not work with all supported ORM/repos.
Domain-layer and data-access is completely separated with an additional DTO-layer. For me this is a theoretical variation and its again a lot more effort to implement and maintain. We will not go this way – I think.
This one is similar to Type B but the business-objects go the the data-access layer and the data-access will map to ORM models (if needed at all). If offers great flexibility for implementing the data-access layer but as the mapping is done per data-access its more effort then Type B. An advantage is, that the domain-layer does know nothing about the data-access and has no possibility to run code related to data-access (and also is complete unaware of any ORM feature like change-tracking etc). Therefore advanced data-access scenarios that harder to address with Type D then with Type B.
This type as a clear separation of the business-objects and the service-facade. The presentation does not know anything about the internals for the domain-layer or the layers below. Its all nicely separated but also a lot of effort to build and maintain (and test). For me this is more a enterprise-style layering and over-architectures for our small to medium apps.
Our final decision is not yet felt but we tend to go for B or D. What do you think? Did we miss a promising layering we should take into consideration too? Thoughts?