A simple template for Onion Architecture with .NET 5

Photo by C Drying on Unsplash

My simple template for Onion Architecture with .NET 5

So, how did I try to solve this? How did I promote the usage of good practices in terms of architecture especially when building microservices or serverless functions without overwhelming with a ton of new concepts?

High Res: https://raw.githubusercontent.com/pereiren/dotnet-template-onion/master/images/dotnet-onion-ddd-cqrs-es.jpg
C:.
│ .gitignore
│ Dotnet.Onion.Template.sln
│ README.md

├───docs
│ ARCHITECTURE.md
│ CQRS-ES.md
│ DDD.md
│ HEXAGONAL.md
│ SOLID.md

├───images
│ dotnet-onion-ddd-cqrs-es.jpg

├───src
│ ├───Dotnet.Onion.Template.API
│ │ │ .dockerignore
│ │ │ Dockerfile
│ │ │ Dotnet.Onion.Template.API.csproj
│ │ │ Program.cs
│ │ │ Startup.cs
│ │ │
│ │ ├───Bindings
│ │ ├───Config
│ │ │ appsettings-dev.json
│ │ │ appsettings-int.json
│ │ │ appsettings-prod.json
│ │ │ appsettings-stag.json
│ │ │
│ │ ├───Controllers
│ │ │ TasksController.cs
│ │ │
│ │ ├───Extensions
│ │ │ └───Middleware
│ │ │ ErrorDetails.cs
│ │ │ ExceptionMiddleware.cs
│ │ │
│ │ └───Properties
│ │ launchSettings.json
│ │
│ ├───Dotnet.Onion.Template.Application
│ │ │ Dotnet.Onion.Template.Application.csproj
│ │ │
│ │ ├───Handlers
│ │ │ TaskCommandHandler.cs
│ │ │ TaskEventHandler.cs
│ │ │
│ │ ├───Mappers
│ │ │ TaskViewModelMapper.cs
│ │ │
│ │ ├───Services
│ │ │ ITaskService.cs
│ │ │ TaskService.cs
│ │ │
│ │ └───ViewModels
│ │ TaskViewModel.cs
│ │
│ ├───Dotnet.Onion.Template.Domain
│ │ │ Dotnet.Onion.Template.Domain.csproj
│ │ │ IAggregateRoot.cs
│ │ │ IRepository.cs
│ │ │
│ │ └───Tasks
│ │ │ ITaskFactory.cs
│ │ │ ITaskRepository.cs
│ │ │ Task.cs
│ │ │
│ │ ├───Commands
│ │ │ CreateNewTaskCommand.cs
│ │ │ DeleteTaskCommand.cs
│ │ │ TaskCommand.cs
│ │ │
│ │ ├───Events
│ │ │ TaskCreatedEvent.cs
│ │ │ TaskDeletedEvent.cs
│ │ │ TaskEvent.cs
│ │ │
│ │ └───ValueObjects
│ │ Description.cs
│ │ Summary.cs
│ │ TaskId.cs
│ │
│ └───Dotnet.Onion.Template.Infrastructure
│ │ Dotnet.Onion.Template.Infrastructure.csproj
│ │
│ ├───Factories
│ │ EntityFactory.cs
│ │ TaskFactory.cs
│ │
│ └───Repositories
│ TaskRepository.cs

└───tests
└───Dotnet.Onion.Template.Tests
│ Dotnet.Onion.Template.Tests.csproj

└───UnitTests
├───Application
│ └───Services
│ TaskServiceTests.cs

└───Helpers
HttpContextHelper.cs
TaskHelper.cs
TaskViewModelHelper.cs
  • Application layer: where all the business logic is implemented
  • Domain layer: where our domain is modeled (Task aggregate domain model for example) and some patterns about DDD are there like Repository Pattern (only interfaces, not implementation)
  • Infrastructure layer: where we implement the code that needs to go outside our application, like for example data access implementation

What does that mean?

CQRS and Event Sourcing

  • Reading (Queries): It is going to use Dependency Injection to the Repository class that it needs to go to the Database or any kind of data source system. The definition of that repository class is an interface, a contract, inside our Domain Layer (you can see that in the upper right corner of the diagram which is an image from the original post about Onion Architecture in 2008). The implementation as I mentioned before, should be outside the Domain Layer, in fact, it is inside the Infrastructure Layer. Why? Because if you are building the Repository implementation in the domain you are coupling the domain to the data source access and also you are breaking The Dependency Rule.
  • Writing (Commands): If you are modifying the Aggregate state (for example Tasks Aggregate) you need to specify that creating a Domain Command in the code to use CQRS properly. So from this point, if you want to use Domain Commands and CQRS in a way to get the best performance you just need to use a message bus. In our case, we chose to use the Mediator pattern which creates an internal bus and it is the one managing or handling the Domain Commands to use Handlers. These handlers are going to be called by a different thread any time, and if the thread pool is locked, the Domain Command is going to be in the Mediator bus waiting for a thread to be released and execute the rest of the code. This is everything C# magic and we just need to use the Mediator library to use this pattern. Once the code arrives at the specific Handler for any Command with a different thread, it is going to perform the changes in the data source (can be a database, API, etc) using the Repository pattern. But as I mentioned before using Dependency Injection to get a fully testable code following the dependency rule and SOLID principles applied in the architecture.

--

--

--

.NET Core lover. Passionate about Software Architecture. Trying to learn every day.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Deploying Apps & Config Across Your Distributed Cloud

Different Sorting Algorithms comparison based upon the Time Complexity

Open IT Hardware: A Hyperscale Public Cloud Advantage

Building a Continuous Integration Server With Jenkins

Chapter 4 Avoiding Bias with Random Walks

Software Validation — Testing Techniques

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
David Pereira

David Pereira

.NET Core lover. Passionate about Software Architecture. Trying to learn every day.

More from Medium

Handling PATCH Updates in ASP.NET Core

SOAP web services in .NET Core

Keyed Dependency Injection using .NET

Factory Design Pattern with Dependency Injection (Notepad Series #4)