Project architecture for your .NET CORE application- 3 proposals
Wondering how to organize yours project within one solution? Do you always have problems with library dependencies? Stop strggling — read this article to learn about types of architectures and choose the right one!
Why is it so important to choose a good architecture for your project?
In life, there comes a moment when everyone has to ask themselves the important question: “How to design it all to work propely”. But why re-invent the wheel by yourself? After all, you can use the already thought-out architecture?
If you think good architecture is expensive, try bad architecture — BRIAN FOOTE AND JOSEPH YODER
Currently, three types of architecture are most popular:
- All-in-one
- N-Layer
- Clean Architecture
All-in-One
The first one is that all files (classes, interfaces, etc.) are placed in folders, in one project. So it’s not an architecture for a bigger project than a tutorial or simple application.
Pros:
- simplicity
- low boilerplate
Cons:
- tl;dr; Spaghetti code
- hard to maintain code
- directories jungle
- no dedicated space for a specific logic types (e.g. domain, UI, data access layer)
- no way to set hierarchy between the folders and their components
- lack of possibility to share elements between applications (e.g. domain model between web and desktop apps)
To summarize, this is not a solution for any project that has to survive more than a few evenings.
N-Layer
Here we are dealing with splitting solution into sub-projects, where each of them is dedicated for “something”. This solution is confirmed by the “Single Responsibility Priciple”. Everyone — whether a student or senior developer— has at least heard of it.
A MODULE SHOULD BE RESPONSIBLE TO ONE, AND ONLY ONE, ACTOR — ROBERT C. MARTIN
It says something apparently very simple: One module (class, method), does only one thing. However, Robert C. Martin points out that “one action”, for different actors (in different domains), can mean something completely different. Therefore, we should be careful about shared methods. Every change in them, can take its harvest in places where they are used.
Getting back to the layers. Most often we distinguish three:
- Data Access Layer (DAL) — here we usually place entities, dbContext, migrations, classes with methods to fill the database (“Seeders”)
- Business Logic Layer (BLL) — here you can find implementations of the business rules ruling our application
- User Interface Layer (UIL) — as the name suggests, user interface e.g. Razor Pages
We should remember that the relationships run in the depths. This means that the UI elements should refer to the domain logic layer. The database should always be accessed via DAL.
What is the problem with this approach? Our domain logic is based on a specific layer — the access layer to the database. “Dependency Inversion Principle” says that the most flexible systems are based on abstracts.
The Dependency Inversion Principle tells us that the most flexible systems are those in which source code dependencies refer only to abstractions, not to concretions — ROBERT C. MARTIN
To be specific — our services should communicate not through a specific implementation (e.g. EntityFramework DbContext) but through an interface that defines methods like Create, Read, Update, Delete etc. (https://pl.wikipedia.org/wiki/CRUD). More about this in the section on “Clean Architecture”.
Pros:
- clarity and simplicity to find specific modules
- DAL and BLL can be shared between applications (e.g. web and desktop)
- possibility to force the communication direction of the modules (through project references)
Cons:
- The Dependency Inversion Principle is not applied here.
Clean Architecture
And the crème de la crème architecture also known as “Hexagonal Architecture”, “Ports-and-Adapters” or “Onion Architecture”.
The main difference from N-Layer is using the reverse dependency, i.e. the deepest layer is for business logic. It is independent. It is also possible to separate a project for entities — for the convenience and order. However, from the point of view of logical division, both projects are for us a domain layer.
In order to benefit from the inverse relationship, in the deepest layer we need to define an interface that will be responsible for data access. Its implementation will be done in the “Infrastructure” project — It will usually use the Entity Framework. However, there is nothing to prevent it from using NO-SQL (Redis, MongoDb, Cassandra), IMemoryCache or the file system.
Everything is cool, what about testing?
Let’s take unit tests as an example, the simplest and most commonly used. Because “business” is independent, we can test its rules without other layers. We just need to provide some implementation of the data access interface (e.g. EF + inMemory database or moq).
Pros:
- implementation of “The Dependency Inversion Principle”, which makes business independent
- clarity and simplicity to find specific modules
- DAL and BLL can be shared between applications (e.g. web and desktop)
- possibility to force the communication direction of the modules (through project references)
Cons:
- In order to maintain business independence, we may have to define redundant interfaces and brainstorm a lot
Summary
According to the first quote the lack of good architecture is expensive. That is why it is worth to take your time to implement a good skeleton of the project. This will save our effort in the next months of implementation. Please note that there is no point in using e.g. Clean Architecture for a project for one evening, where we check how new version of our favorite library works :)
More about Clean Architecture itself and using various popular libraries in it you will find in my next posts.