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:
- This site - give us a Star
- Our blog: https://itlibrium.com/tag/DDD-starter
- Twitter: ITLIBRIUM, Marcin, Szymon
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:
- Complex aggregate:
Order
- Aggregate's private Value Object:
PriceAgreement
- Aggregate's events:
Order.Events
- Aggregate's snapshot:
Order.Snapshot
Blog:
Code:
- Identifiers eg:
OrderId
,ProductId
- Simple quantities (often with operators facilitating calculations) eg:
Amount
,ProductAmount
,Money
- Complex quantities (often used by Policies) eg:
Offer
,Quote
- Other domain concepts (often used for communication between other Building Blocks) eg:
OfferRequest
,BasePrice
Blog:
Code:
- Calculations eg:
OfferModifier
,ClientLevelDiscount
,SpecialOffer
- Adjusting Aggregate's rules eg:
PriceChangesPolicy
,AllowPriceChangesIfTotalPriceIsLower
Blog:
Code:
- Creating Aggregates and Value Object: factory methods on each type
- Choosing Policy for given conditions:
PriceChangesPolicies
,OfferModifiers
Code:
- Domain sub-processes:
CalculatePrices
Code:
- List inside Aggregate:
Order.NewEvents
Alternatives that won't be implemented:
- static event publisher - hard to test Aggregates
- passing event publisher to aggregate
- through constructor - hard to restore Aggregates
- through method argument - technical language in business behaviors
- returning events from Aggregate's methods - harder implementation if method can return not only single Event; Events have to be passed as arguments to Repository methods which in less intuitive than passing Aggregate itself
Code:
Tools:
- BDD toolkit - our another open source project
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:
- CRUD Bounded Context with single layer architecture eg:
Contacts
- Bounded Context with Deep Model and CRUD Model eg:
Sales
- Separation rules (Aggregate) and simple data (Anemic Entity / Data Structure) eg:
Order
-OrderHeader
- Saving both Deep Model and CRUD Model in Command Handler (single transaction) eg: ,
PlaceOrderHandler
,CreateOrderHandler
- Reading CRUD Model in Command Handler eg: ,
ConfirmOfferHandler
,GetOfferHandler
,SalesCrudOperations
- Managing CRUD Model without Command Handler eg:
WholesalesOrdersHeaderController
,WholesalesOrdersHeaderNotesController
,SalesCrudOperations
- Separation rules (Aggregate) and simple data (Anemic Entity / Data Structure) eg:
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:
- SQL
- Tables from snapshot:
OrderSqlRepository.TablesFromSnapshot
- Tables from events:
OrderSqlRepository.TablesFromEvents
- Document from snapshot:
OrderSqlRepository.DocumentFromSnapshot
- Document from events:
OrderSqlRepository.DocumentFromEvents
- Event Sourcing - coming soon
- Tables from snapshot:
- Transactions:
TransactionDecorator
Blog:
Code:
- In a single transaction with domain model using Outbox Pattern
- Base abstractions:
TransactionalOutbox
,TransactionalOutboxes
- Integration with Use Case lifetime:
TransactionalMessageSendingDecorator
- Particular Outbox as a Port in Use Cases layer:
OrderEventsOutbox
- Publishing Outbox to Event Broker: coming soon
- Kafka:
- Base abstractions
KafkaTransactionalOutbox
,KafkaOutboxWriter
- Adapters:
KafkaOrderEventsOutbox
- Base abstractions
- Base abstractions:
- Post Commit
- Base abstractions:
NonTransactionalOutbox
,NonTransactionalOutboxes
- Integration with Use Case lifetime:
NonTransactionalMessageSendingDecorator
- Particular Outbox as a Port in Use Cases layer: coming soon
- Kafka:
- Base abstractions
KafkaNonTransactionalOutbox
- Adapters: coming soon
- Base abstractions
- Base abstractions:
- Pre Commit - In our opinion it's not very useful approach in real world scenarios.
Code:
- Integration tests for DDD Repository and SQL database:
OrderSqlRepositoryTests
We prefer to put all the startup code in a separate project. This project know about everything but does only initialization including:
- parsing and merging configuration
- registering all components in dependency injection container
- composing framework components like
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.