

Clean Architecture for ASP.NET Core Solution: A Case Study
source link: https://blog.ndepend.com/clean-architecture-for-asp-net-core-solution/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

In this post I’ll explore the Jason Taylor’s CleanArchitecture .NET solution template available here on github. It constitutes a good template and it is based on several modern industry wide accepted good practices.
Solution Explorer View
Here is the Visual Studio Solution Explorer view of the Jason Taylor’s CleanArchitecture solution.
Application code and test code are clearly separated with src and tests solution folders. This is a warmly advised prerequisite for all Visual Studio solutions: app code and test code must not be mixed!
A section below will dedicate the tests organisation but we’ll mostly focus on the four projects under the src solution folder.
The Dependency Graph View
When I open a new solution the first thing I do is to visualize it as an NDepend dependency graph. This graph below tells much more than the Visual Studio Solution Explorer alone. From this application map graph, you can use the multiple features to start exploring it in depth.
Notice the color scheme: Orange means selected, green means caller of selected and blue means called by selected. Further screenshot will rely on this color scheme.
Now that we have a clear diagram of the solution architecture, it makes sense to present the projects from low-level to high-level in this order: Domain, Application, Infrastructure and WebUI. Before that, let’s precise some definitions and good practices in a quick section.
Project, Component, Monolithic Architecture
There are many ways to structure projects of an ASP.NET Core application. Fewer Visual Studio projects is not necessarily bad and many projects is not necessarily good. One has to keep in mind that the project granularity is physical: each project compiles to a DLL file.
On the other hand the notion of component is logical. Concretely a components is a group of cohesive types (classes/interfaces/structure/enumerations) that function together to achieve a well-defined goal. We use components to structure applications into units:
- Unit of processing,
- Unit of development,
- Unit of re-use,
- Unit of understanding,
- Unit of test
We need units to implement a complex application: this is the direct consequence of the famous Descartes’ Discourse of Method’s divide and conquers idea.
The notion of component is more fine-grained than the notion of project. Thus a project typically contains several components. Personally I like to use namespaces to structure a project into components: Application.NamespaceA implements component A, Application.NamespaceB implements component B …
If there is a dependency cycle between components, child components are not units anymore: they cannot be developed, re-used, tested, understood… independently. Thus we have a super-component and super-components makes our application monolithic. This is the infamous spaghetti code phenomenon! This is why it is recommended to keep the components graph layered (with no cycle). If you use namespaces to use component you can run code rules like Avoid namespaces mutually dependent and Avoid namespaces dependency cycles to prevent spaghetti code.
Thus some key characteristics of a clean architecture are:
- Projects are used for physical reasons, for example to separate ASP.NET Core code from Database access code.
- Components are used within project to structure the code into units, to divide and conquer.
- There is no cycles between components: they are layered.
I am not sure if the Jason’s intention is to rely on namespaces to define its components but the Clean-Architecture namespace is almost completely layered as shown on this Dependency Structure Matrix (DSM)
The 2 red rectangles exhibit a bit of entangling within the CleanArchitecture.Application.Common.* and Entities/Events namespaces. The aforementioned rule offers some hints on what to do to get a fully layered architecture:
Domain
As the CleanArchitecture‘s github page says, the domain project contains all entities, enums, exceptions, interfaces, types and logic specific to the domain layer. This results from the application of the popular Domain Driven Design (DDD) approach to structure software. The business domain is carefully modelized within the domain project.
The word carefully is important: the domain plainly determines the terminology and the abstractions that will be used across the application and even across the enterprise. For example we want to make sure that everybody in the team either use the term Client or the term Customer but we don’t want both terms to be used randomly to mean the same thing. This is the Ubiquitous Language DDD concept. Actually NDepend provides a rule template DDD ubiquitous language check that can be customized with a dictionary of terms to validate the domain terminology.
DDD defines other concepts like Entity (typically implemented through classes) and Value Object (typically implemented through structure and enumerations). This mapping between DDD concepts (Entity and Value Object) and C#/CLR concepts (classes vs. structure/enumeration) comes from the fact that the initial intentions are similar:
- An entity is defined by its identity the same way an object (instance of a class) at runtime can be identified through obj.GetHashCode().
- The identity of a value, like C# enumerations values and C# structure values, is defined by the values of its fields. This is the reason why it is recommended to make a structure immutable: else the identity of the structure’ values can change and it can be a problem for example if values are used as keys in a hash table or when developers don’t realize that structure values are passed by values and thus, are duplicated: changing a structure value state doesn’t change state the copies of the value.
One key fact to underline is that types inside the Domain project are Plain Old CLR Objects (POCO). To make that clear, the graph below shows the third-party assemblies consumed by the project Domain. We can see that only primitive types and simple types like String, Boolean, Enum, List<T>… are consumed by the domain. Concerns (like storage) and also business rules are implemented in other projects.
Finally let’s note that the physical aspect of project is properly used here: we only want the domain project to reference base assemblies like System.Runtime!
Application
The Application project only depends on the Domain project. As its name suggest this project is the core of the application: it contains business rules implementations. Some example of such rules are:
- Send this email to this client if these conditions are verified
- Validate this user input
- Calculate values such as a discount on an order.
- Data transformation
By double-click the edge between the Application and the Domain projects, we get a graph of classes and methods involved in the coupling between the two projects. You can click on the image to enlarge it:
With commands and queries classes, it is clear that this Application project impl is relying on the Command Query Responsibility Segregation (CQRS) pattern. This pattern is used to separate create and update operations (commands) from reads (queries) each returning their response models for clear segregation of each action. The reason for using CQRS is that for complex enough domains, relying on a single conceptual model for both commands and queries leads to a too complicated model that does neither well. Thus, the benefit of applying CQRS in such situation is that the long-term maintainability of the the application improves.
Apart implementing the business rules, the Application project has a second responsibility: abstract away concerns. Concerns of the application include notification services, database access, SMTP, file system access… For example the interface IApplicationDbContext is used to abstract the database accesses (create/update/read) of TodoLists and TodoItem entities defined in the domain.
If we look at the UpdateTodoListCommand and its handler code we can see how the database is consumed from the Application without even knowing how it is implemented.
Infrastructure
The Infrastructure project implements interfaces defines in the Application projects. We can see how it depends on external resources such as entity framework or identity provider and so on. Separating the infrastructure implementation from its usage in the Application project is one of the greatest benefits of this CleanArchitecture template. In the future we’ll be able to change easily any of these concerns implementations.
This might sound like premature to abstract away from all concerns straight. However such interfaces could let mock concerns in tests code and this would be a great benefit of abstracting. Also experience shows that with time, concerns implementation change because the industry itself evolves.
WebUI
One responsibility of the WebUI project is to inject the Infrastructure implementations within the Application layer. This is done by the Startup and Program classes defined in the WebUI project. This is the only reason why the WebUI project references the Infrastructure project. This becomes clear by looking at the class coupling between the two projects:
You can write a custom code rule to make sure that Infrastructure is only used by the WebUI classes Main and Startup. This rule captures the initial CleanArchitecture intention and will warn upon any violation this intention.
Apart that cool part, the WebUI project is a single page application based on Angular 10 and ASP.NET Core 5. Nothing special but you can look here at how the WebUI controllers uses the CQRS commands for example.
Test Organization
Tests focuses on Application and Domain testing. Personally I don’t necessarily segregate tests by projects tested like here. However the distinction between Integration and Unit tests is necessary because they serve a different purposes: whether you are working on a single class or on combined modules together you know which tests to run to prevent regressions.
Also unit tests are fast to execute, typically in the range of thousands per second. Thus the developer doesn’t have a performance penalty when executing them often. And as long as tests don’t slow down the developer they should be executed as often as possible.
On the other hand integration tests can be slow to execute and are typically executed daily on the build server, except when the developer really needs to execute them locally.
Finally, by running test I obtain 39% overall coverage. Covered and uncovered classes and methods can be seen on the metric view. I would have wished more test and coverage.
Clean Architecture Code CoverageConclusion
Now that we have a good understanding of each project, we can conclude with the classical Clean Architecture diagram style below. We can see how the projects are depending on each other. Also such diagram makes clear that the Domain and Application layers are not visible from the outside world. This kind of clean architecture is becoming standard nowadays so better master its concepts an apply it in your applications.
My dad being an early programmer in the 70's, I have been fortunate to switch from playing with Lego, to program my own micro-games, when I was still a kid. Since then I never stop programming.
I graduated in Mathematics and Software engineering. After a decade of C++ programming and consultancy, I got interested in the brand new .NET platform in 2002. I had the chance to write the best-seller book (in French) on .NET and C#, published by O'Reilly (> 15.000 copies) and also did manage some academic and professional courses on the platform and C#.
Over the years, I gained a passion for understanding structure and evolution of large complex real-world applications, and for talking with talented developers behind it. As a consequence, I got interested in static code analysis and started the project NDepend.
Today, with more than 8.000 client companies, including many of the Fortune 500 ones, NDepend offers deeper insight and understanding about their code bases to a wide range of professional users around the world.
I live with my wife and our twin babies Léna and Paul, in the beautiful island of Mauritius in the Indian Ocean.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK