UPDATE: This project has now been upgraded to .NET 8. I’ve blogged about it here.
I’ve become a huge fan of the Clean Architecture pattern when working on .NET API’s. I won’t go into a ton of detail here, as there are a lot of great resources out there on the subject already, including from the person that I first learned it from, Jason Taylor. If you’re unfamiliar with this pattern, do yourself a favor and check out this presentation by Jason from the GOTO 2019 conference:
But at the 10,000 foot level, an API project using the Clean Architecture pattern has 4 separate projects (along with the necessary unit testing projects):
- Domain – This project is meant to hold the domain entities of the application. The classes in this project map to the data tables or records in whatever data store the application is configured to use.
- Application – This project contains the business logic and rules that make the application as a whole run as it’s supposed to. It directly references the Domain project, but no other projects within the solution.
- Infrastructure – This project contains the implementation of any logic that needs to communicate with outside entities, such as a database, the file system, other HTTP API’s, and so forth. It references the Application project to gain access to application contracts that it will then implement for accessing the aforementioned external entities.
- Api – This is the front-end of the application, and provides the start-up code and the API endpoint entry points.
Solution Template Nuget Package
I’ve created a Nuget package called StaticSphere.CleanArchitecture.Api
, and published it to Nuget.org. You can install it using the following command:
dotnet new --install StaticSphere.CleanArchitecture.Api
Once installed, you can create a new solution by running the following command:
dotnet new clean-arch <<parameters>>
When executed, you’ll get a full .NET 6 ASP.NET API solution that contains the following folder structure (assuming you named to the solution HelloWorld):
- src
- HelloWorld.Api
- HelloWorld.Application
- HelloWorld.Domain
- HelloWorld.Infrastructure
- tests
- HelloWorld.Api.Tests
- HelloWorld.Application.Tests
- HelloWorld.Infrastructure.Tests
The projects are all .NET 6 applications with nullable references types and implicit usings enabled. The unit testing projects use Xunit out of the box. There are also support files such as .gitignore, .editorconfig, etc.
There are parameters that can be passed to the dotnet new
command that alter the solution that is created:
- –includeTests – Determines if the test projects should be included or not. Default value: true
- –skipRestore – If specified, skips the automatic restore of the project on create. Default value: false
- –useStartup – Determines if the API project should use Startup.cs instead of the newer Minimal API style Program.cs file. Default value: false
- –includeEF – If set, the created solution will include Entity Framework Core, and will be configured to use the specified provider (only these providers are currently supported)
- postgres – Adds Postgres Entity Framework configuration
- sqlserver – Adds SQL Server Entity Framework configuration
Open Source
The Nuget package that contains the solution template is completely free and open source, and is MIT licensed. The Nuget package can be found here, and the source code for the template can be found here. If you have any suggestions, or would like to contribute to the template, please let me know!
Top comments (4)
Out of curiosity where do you place your DTO (api controller response objects) and mapping services for converting repo domain objects to return types ?
Is this all in the application layer(if so do you have both interface and implementation of these services both in the application layer ? As for example with repo it’s practice to have only interfaces in application and implementation on infrastructure, does this work for all services / interfaces? I’m new to the pattern and want to ensure I’ve understood properly.
Hey, sorry for the late reply! Dev needs to email me when someone responds to one of my posts! Assuming I'm not too late to answer your question, what I like to do is to keep my DTO's as close to the endpoint as I can, either in the folder with the endpoint itself directly, or I'll create a DTOs folder, and create sub-folders there for the domain I'm creating the DTO for. In the sample code, the PersonViewModel lives in the Endpoints/People folder. I would have put it alongside the command or query itself if the DTO was specific to just that single endpoint. The other place you could put it would be in the ViewModels folder, under another folder called People to match the domain.
In the end, it's about consistency and being logical with your groupings. If you have any people related code, that code should likewise be in People folders and namespaces.
Yeah makes sense. Keep it as close to the implementation / use case as possible.
I think CQRS really lends itself to clean arch. Helping with the separation of concerns and layering.
Absolutely! We use CQRS pretty heavily where I work, and it has proven itself both from a separation of concerns point of view, as well as for security, as we can control the permissions on our flows pretty granularly.