Domain-Driven Design (DDD) Patterns
Relevant source files
The following files were used as context for generating this wiki page:
- claude.md
- BuildingBlocks/BuildingBlocks.Infrastructure.EfCore/EfCoreUnitOfWork.cs
- BuildingBlocks/BuildingBlocks.Tests/TestBase.cs
- BuildingBlocks/BuildingBlocks.Tests/TransactionBehaviorTests.cs
- Core/Scheduling/Scheduling.Application/Patients/Commands/CreatePatientCommand.cs
- BuildingBlocks/BuildingBlocks.Tests/EfCoreUnitOfWorkTests.cs
Introduction
This project serves as a comprehensive learning platform for mastering Domain-Driven Design (DDD) tactical patterns and Event-Driven Architecture. It implements a healthcare appointment scheduling system across multiple bounded contexts, including Scheduling, Billing, and Medical Records. The architecture focuses on high encapsulation, clear separation of concerns between domain logic and infrastructure, and robust consistency through transactional boundaries.
The implementation follows a structured path from fundamental tactical patterns (Entities, Value Objects, Aggregates) to advanced persistence and integration strategies using EF Core, MediatR, and asynchronous messaging with RabbitMQ.
Sources: claude.md:3-33, claude.md:143-150
Core Tactical Patterns
The system utilizes several key DDD tactical patterns to ensure business logic remains central to the application while technical concerns are delegated to the infrastructure layer.
Entities and Aggregates
Entities are defined as objects with unique identities, while Aggregates serve as consistency boundaries. All access to an aggregate's internal state must go through the Aggregate Root to enforce business rules.
Key Implementation Rules:
- Aggregates are accessed only through the root.
- Domain entities have no public setters to ensure encapsulation.
- Business logic resides within the domain layer rather than in external services.
Sources: claude.md:20-25, claude.md:167-172
Domain Events
Domain events represent significant state changes within the domain. They are used to trigger side effects within the same bounded context or to propagate changes to other contexts via integration events.
The EfCoreUnitOfWork handles domain event dispatching automatically before saving changes to the database.
Sources: claude.md:22, claude.md:169, BuildingBlocks/BuildingBlocks.Infrastructure.EfCore/EfCoreUnitOfWork.cs:51-84
Value Objects
Value objects are used for concepts defined by their attributes rather than identity. In this project, value objects are implemented as immutable C# records and are often mapped as owned entities in EF Core.
Sources: claude.md:21, claude.md:154, claude.md:170
Architectural Components
Command Query Responsibility Segregation (CQRS)
The project separates read and write operations using MediatR. Commands represent actions that change state, while Queries retrieve data without side effects.
| Component | Description | Example |
|---|---|---|
| Command | Encapsulates an intent to change state. | CreatePatientCommand |
| Command Handler | Executes business logic and persists changes. | CreatePatientCommandHandler |
| Query | Retrieves data for the UI. | GetPatientByIdQuery |
| Validator | Validates commands before execution. | CreatePatientCommandValidator |
Sources: claude.md:41-48, claude.md:174-177, Core/Scheduling/Scheduling.Application/Patients/Commands/CreatePatientCommand.cs:18-35
Persistence and Unit of Work
The EfCoreUnitOfWork manages the lifecycle of transactions and ensures that domain events and integration events are handled correctly during the persistence process.
Transactional Flow
The following sequence diagram illustrates how the system handles a command through the TransactionBehavior pipeline and the UnitOfWork.
Infrastructure and Integration
Event-Driven Communication
The system distinguishes between Domain Events (internal to a bounded context) and Integration Events (cross-context communication). Integration events are written to an outbox during the database transaction and delivered asynchronously via RabbitMQ and MassTransit.
Sources: claude.md:65-72, BuildingBlocks/BuildingBlocks.Infrastructure.EfCore/EfCoreUnitOfWork.cs:55-58
Validation and Security
Command validation is implemented using FluentValidation. A specialized UserValidator<T> base class integrates security by enforcing role-based access control during the validation phase.
internal class CreatePatientCommandValidator : UserValidator<CreatePatientCommand>
{
public CreatePatientCommandValidator(ICurrentUser currentUser, IValidator<CreatePatientRequest> reqValidator)
: base(currentUser, new[] { AppRoles.Nurse }, new[] { AppRoles.Doctor }, new[] { AppRoles.Admin })
{
RuleFor(c => c.Patient).SetValidator(reqValidator);
}
}
Sources: Core/Scheduling/Scheduling.Application/Patients/Commands/CreatePatientCommand.cs:35-43, claude.md:112-114
Testing Infrastructure
A robust testing hierarchy ensures confidence in DDD implementations.
- ValidatorTestBase: For unit testing command validators with mocked units of work.
- TestBase: For integration tests using SQLite in-memory, providing transaction isolation for each test.
Sources: claude.md:52-61, BuildingBlocks/BuildingBlocks.Tests/TestBase.cs:22-30
Summary
The DDD patterns in this project provide a scalable architecture where the domain model is protected from infrastructure concerns. By utilizing Aggregates as consistency boundaries, CQRS for clear operation intent, and a Unit of Work to coordinate persistence and event dispatching, the system maintains high technical quality and business alignment.