A simple template for Onion Architecture with .NET 5

Photo by C Drying on Unsplash

My simple template for Onion Architecture with .NET 5

Clean architectures are awesome, I don’t have any doubt about that. Highly scalable, fully testable, easy to evolve, easy to adapt to new requirements, etc etc etc.

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?

I created this template:

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
  • 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?

Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in an inner circle. That includes functions, classes. variables, or any other named software entity.

CQRS and Event Sourcing

This is another thing I wanted to promote with my team (and also more teams). When designing a distributed architecture I think that CQRS (Command Query Responsibility Segregation) and ES (Event Sourcing) can help a lot. Of course, they are not a Silver Bullet, they are just patterns that you can follow if your microservice needs it or not.

  • 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.

--

--

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.