Sample implementation and comparison of various approaches to building DDD applications. Useful as a baseline to quickly start a DDD dot net project.
This repository allows to quickly start a DDD oriented project in C# selecting a combination of DDD-related technologies which is right for your needs. Hexagonal Architecture, BDD, CQRS, Event Sourcing - to not overengineer your project you often need only part of them. Here we show you various implementation options with guidelines which should help you make a smart choice and start coding.
It's a place to learn how to implement DDD, CQRS, Event Sourcing, Hexagonal Architecture, BDD, etc.
It's a comparison of different implementations styles that can be used to solve the same requirement.
It's a set of ready made solutions that you can adapt in your own projects.
It's not a complete study of some domain.
It's not an ilustration of domain exploration or modeling process.
It's not a complete comparison of all possible implementation styles.
The main goal of this project is to show different implementation options for a DDD project and share some best practices for each of them. Which approach is the best and should be chosen? It all depends! ;) Depends on what? Probably on some drivers from the context you operate in. So we also give you here some tips about how the available options match different drivers. We hope it will allow you to quickly make a good choice and get some productivity boost.
Remember that DDD is not about implementation! It is "just" a lightweight technique of creating a model for your software using exactly the same language as your business. This model can be more or less complex depending on the business itself. There is no point in using a pneumatic drill where a simple hammer will do the job (even if the pneumatic drill is very shiny). Do not even try to use all most advanced DDD technologies (like Event Sourcing) without a proper reflection.
It is impractical to present the process of domain exploration and modeling as well as various approaches to implementation at once. That's why we decided to limit the domain to an absolute minimum so that we could present implementation techniques of particular patterns.
This limitation will mainly concern the number of domain concepts and to a lesser degree their complexity as this is necessary for meaningful use of the presented patterns.
The code is read much more often than it is modified. The implementation should therefore primarily be optimized for readability. In addition, the code is usually read by someone other than its author, so it is worth to ensure that the learning curve is as flat as possible.
We will try to make all presented solutions as easy as possible. The different options will of course vary in complexity. This will be an additional illustration of trade-offs between brevity and simplicity: what is gaining and what is lost by choosing more generic / automatic / magical solutions.
Simplicity, however, does not mean simplifications that we will try to avoid at all costs. Simplified solutions are usually not suitable for use in a real projects. They also require additional knowledge to distinguish what is essential from what is merely a simplification.
Discussed patterns are not strictly related to any technology, therefore the project will not be written from the perspective of a specific set of technologies or cloud provider.
The only limitation is the choice of .net platform and C # language.
Chosen libraries and technologies are optimal choices from the perspective of authors' experience. All of them can be easily replaced with analogical solutions while maintaining the essence of the implemented patterns.
This project is under development so we encourage you to follow:
To create this solution we used an architectural approach called screaming architecture. This means that the structure of the solution "screams" about the business domain and architectural choices. Bounded Contexts are mapped to solution folders, application architecture layers are in separate projects, namespaces hierarchy follows business divisions.
Documentation:
Blog:
Hexagonal Architecture is used in a part of Sales
Bounded Context.
This architecture style is best for Deep Model with high business complexity. It's a very rare situation when it's the best choice for the architecture of the whole system. When used for CRUD part of the system it only adds unnecessary, accidental complexity. That's why we used it only in a part of the system. The rest of Sales
and whole Contacts
Bounded Context is rather simple with only CRUD operations. For these parts simple single layer architecture was chosen.
Code:
Blog:
Code:
Order
PriceAgreement
Order.Events
Order.Snapshot
Blog:
Code:
OrderId
, ProductId
Amount
, ProductAmount
, Money
Offer
, Quote
OfferRequest
, BasePrice
Blog:
Code:
OfferModifier
, ClientLevelDiscount
, SpecialOffer
PriceChangesPolicy
, AllowPriceChangesIfTotalPriceIsLower
Blog:
Code:
PriceChangesPolicies
, OfferModifiers
Code:
CalculatePrices
Code:
Order.NewEvents
Alternatives that won't be implemented:
Code:
Tools:
Even in a single Bounded Context we often find parts with different complexity. Some part of the Bounded Context may require Deep Model and techniques like Hexagonal Architecture and DDD Building Blocks. At the same time, another part may need CRUD model where single or two-layered architecture (using frameworks and libraries as much as possible) is the best fit.
Using completely separate styles - Hexagonal Architecture for the Deep part and single/two layered architecture for CRUD part - is possible only if there are no use cases where we need to operate on both models. It's only a mater of time when such a use case occurs. What then? Which architecture style should be used?
The solution we propose is to use flexible Hexagonal Architecture, but avoid usage of OOP and Tactical DDD patterns for the CRUD parts. Check out the Sales Bounded Context where we show it in action. You can also check the Contacts Bounded Context where we show sample CRUD model implementation which is kept as simple as possible.
Code:
Contacts
Sales
Order
- OrderHeader
PlaceOrderHandler
, CreateOrderHandler
ConfirmOfferHandler
, GetOfferHandler
, SalesCrudOperations
WholesalesOrdersHeaderController
, WholesalesOrdersHeaderNotesController
, SalesCrudOperations
Domain model can be persisted in a several ways using SQL and noSQL databases. Here we compare various approaches by providing an example implementation for each of them.
Code:
OrderSqlRepository.EF
OrderSqlRepository.Raw
OrderSqlRepository.Document
OrderSqlRepository.EventsSourcing
AmbientTransactionDecorator
, 'ExplicitTransactionDecorator'
Blog:
Code:
TransactionalOutbox
, TransactionalOutboxes
TransactionalMessageSendingDecorator
OrderEventsOutbox
NonTransactionalOutbox
, NonTransactionalOutboxes
NonTransactionalMessageSendingDecorator
Code:
OrderSqlRepositoryTests
We prefer to put all the startup code in a separate project. This project know about everything but does only initialization including:
middlewares
In our opinion it's a better approach compared to putting startup code into the project with API code. It's especially useful in a modular monolith because each of the modules can have its own, separate API and use its own set of dependencies.
Code:
If you thought of something we can implement in this repo to make it even more useful for your projects let us know! We are looking forward for your feedback and ideas for new example implementations.
Good luck with implementing DDD in your projects!
The project is under MIT license.