itlibrium / DDD-starter-dotnet

Sample implementation and comparison of various approaches to building DDD applications. Useful as a baseline to quickly start a DDD dot net project.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

DDD starter for .net

About

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.

What DDD starter is?

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.

What DDD starter is not?

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 goal

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.

IMPORTANT DISCLAIMER

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.

Our approach

Limiting the domain to a minimum

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.

Simplicity

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.

Minimum technical dependencies

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.

Project development

This project is under development so we encourage you to follow:

  1. This site - give us a Star
  2. Our blog: https://itlibrium.com/blog?tag=DDD-starter
  3. Twitter: ITLIBRIUM, Marcin, Szymon

Table of contents

Screaming architecture

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:

  1. Projects and namespaces conventions

Blog:

  1. How to use c# projects and namespaces in a DDD project

Hexagonal Architecture

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:

  1. Sales Bounded Context

Blog:

  1. [PL] Architektura wspierająca podejście Domain First

DDD Building Blocks

Aggregate

Code:

  1. Complex aggregate: Order
  2. Aggregate's private Value Object: PriceAgreement
  3. Aggregate's events: Order.Events
  4. Aggregate's snapshot: Order.Snapshot

Blog:

  1. [PL] Identyfikowanie obiektów domenowych

Value Object

Code:

  1. Identifiers eg: OrderId, ProductId
  2. Simple quantities (often with operators facilitating calculations) eg: Amount, ProductAmount, Money
  3. Complex quantities (often used by Policies) eg: Offer, Quote
  4. Other domain concepts (often used for communication between other Building Blocks) eg: OfferRequest, BasePrice

Blog:

  1. [PL] Jak zaimplementować Value Object z DDD w C#

Policy

Code:

  1. Calculations eg: OfferModifier, ClientLevelDiscount, SpecialOffer
  2. Adjusting Aggregate's rules eg: PriceChangesPolicy, AllowPriceChangesIfTotalPriceIsLower

Blog:

  1. [PL] Jak zaimplementować Polityki z DDD w C#

Factory

Code:

  1. Creating Aggregates and Value Object: factory methods on each type
  2. Choosing Policy for given conditions: PriceChangesPolicies, OfferModifiers

Domain Service

Code:

  1. Domain sub-processes: CalculatePrices

Emitting Events from Aggregates

Code:

  1. List inside Aggregate: Order.NewEvents

Alternatives that won't be implemented:

  1. static event publisher - hard to test Aggregates
  2. passing event publisher to aggregate
    1. through constructor - hard to restore Aggregates
    2. through method argument - technical language in business behaviors
  3. 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

Testing domain model

Code:

  1. Tests for Money Value Object

Tools:

  1. BDD toolkit - our another open source project

Combining DDD and CRUD

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:

  1. CRUD Bounded Context with single layer architecture eg: Contacts
  2. Bounded Context with Deep Model and CRUD Model eg: Sales
    1. Separation rules (Aggregate) and simple data (Anemic Entity / Data Structure) eg: Order - OrderHeader
    2. Saving both Deep Model and CRUD Model in Command Handler (single transaction) eg: , PlaceOrderHandler, CreateOrderHandler
    3. Reading CRUD Model in Command Handler eg: , ConfirmOfferHandler, GetOfferHandler, SalesCrudOperations
    4. Managing CRUD Model without Command Handler eg: WholesalesOrdersHeaderController, WholesalesOrdersHeaderNotesController, SalesCrudOperations

Persistence of Aggregates

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:

  1. SQL
    1. Entity Framework: OrderSqlRepository.EF
    2. Raw SQL: OrderSqlRepository.Raw
    3. Document with Marten: OrderSqlRepository.Document
    4. Event Sourcing with Marten: OrderSqlRepository.EventsSourcing
  2. Transactions: AmbientTransactionDecorator, 'ExplicitTransactionDecorator'

Blog:

  1. [PL] 4 sposoby persystencji agregatow DDD

Publishing Events

Code:

  1. In a single transaction with domain model using Outbox Pattern
    1. Base abstractions: TransactionalOutbox, TransactionalOutboxes
    2. Integration with Use Case lifetime: TransactionalMessageSendingDecorator
    3. Particular Outbox as a Port in Use Cases layer: OrderEventsOutbox
    4. Publishing Outbox to Event Broker: coming soon
  2. Post Commit
    1. Base abstractions: NonTransactionalOutbox, NonTransactionalOutboxes
    2. Integration with Use Case lifetime: NonTransactionalMessageSendingDecorator
    3. Particular Outbox as a Port in Use Cases layer: coming soon
  3. Pre Commit - In our opinion it's not very useful approach in real world scenarios.

Testing integration with infrastructure

Code:

  1. Integration tests for DDD Repository and SQL database: OrderSqlRepositoryTests

Startup

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:

  1. Startup project

What's next?

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!

License

The project is under MIT license.

About

Sample implementation and comparison of various approaches to building DDD applications. Useful as a baseline to quickly start a DDD dot net project.

License:MIT License


Languages

Language:C# 98.5%Language:PowerShell 0.8%Language:Shell 0.7%Language:Batchfile 0.1%