C#
DDD Learning Repository: Order Fulfillment with Event Sourcing
A production-style, staff-level .NET 10 learning repository that deeply teaches Domain-Driven Design (DDD) and Event Sourcing through a realistic Order Fulfillment domain.
This is not a toy project. It is a serious learning repository with real code, strong architecture, deep documentation, Mermaid diagrams, and clear tradeoff discussions.
Why Order Fulfillment?
Order Fulfillment is an ideal domain for learning DDD + Event Sourcing because:
- Rich aggregate rules: Orders have complex state transitions (Draft → Submitted → InventoryReserved → PaymentConfirmed → Shipped → Delivered) with strict invariants at each step
- Natural domain events: Every state change is a meaningful business event (OrderCreated, PaymentConfirmed, OrderShipped) — not just CRUD operations
- Value of history: The full event timeline has real business value (auditing, dispute resolution, analytics)
- Multiple bounded contexts: Ordering and Inventory are naturally separate contexts that must integrate
- Eventual consistency: Inventory reservation, projection updates, and cross-context communication are naturally eventually consistent
- Caching opportunities: Order status lookups, dashboard stats, and timeline views benefit from caching
- Read model diversity: Current state, timeline history, and dashboard aggregations are naturally different projections from the same events
Architecture Overview
text
┌─────────────────────────────────────────────────────────┐
│ API (ASP.NET Core) │
│ Commands (POST) │ Queries (GET) │
└──────────┬──────────────────┬───────────────────────────┘
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ Application │ │ Read │
│ Handlers │ │ Repository │
│ (MediatR) │ │ (Dapper) │
└──────┬──────┘ └──────┬──────┘
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ Domain │ │ PostgreSQL │
│ Aggregates │ │ Read Models │
└──────┬──────┘ └─────────────┘
│ ▲
┌──────▼──────┐ │
│ Event Store │ ┌──────┴──────┐
│ (PostgreSQL) ├──►│ Projections │
└──────┬──────┘ └─────────────┘
│ ▲
┌──────▼──────┐ ┌──────┴──────┐
│ Kafka │ │ Background │
│ Integration │ │ Workers │
│ Events │ │ (Polling) │
└─────────────┘ └─────────────┘
│
┌──────▼──────┐
│ Redis │
│ Snapshots │
│ Cache │
│ Idempotency │
└─────────────┘Tech Stack
| Technology | Role | Why |
|---|---|---|
| .NET 10 | Runtime & API | Latest platform, modern C# features |
| PostgreSQL | Event Store + Read Models | ACID for events, flexible JSONB, proven at scale |
| Kafka | Integration Events | Durable, ordered, partitioned event distribution between contexts |
| Redis | Cache + Snapshots + Idempotency | Fast lookups, TTL-based expiry, atomic operations |
| Docker Compose | Local Development | Reproducible environment with all dependencies |
Project Structure
text
├── src/
│ ├── DddLearning.Domain/ # Pure domain model — no dependencies
│ │ ├── SharedKernel/ # Base classes: AggregateRoot, Entity, ValueObject
│ │ ├── Ordering/ # Ordering bounded context
│ │ │ ├── Aggregates/Order.cs # Main aggregate root with event sourcing
│ │ │ ├── Entities/OrderLine.cs # Entity within Order aggregate
│ │ │ ├── ValueObjects/ # Money, Address, OrderStatus, etc.
│ │ │ ├── Events/ # Domain events emitted by Order
│ │ │ ├── Services/ # Domain services (pricing)
│ │ │ ├── Factories/ # Complex creation logic
│ │ │ └── Specifications/ # Business rule specifications
│ │ └── Inventory/ # Inventory bounded context
│ │ ├── Aggregates/StockItem.cs # Inventory aggregate
│ │ ├── Events/ # Stock domain events
│ │ └── ValueObjects/ # SKU, StockLevel
│ │
│ ├── DddLearning.Contracts/ # Shared contracts — commands, queries, DTOs
│ │ ├── Commands/ # Command records (intent to change state)
│ │ ├── Queries/ # Query records (intent to read state)
│ │ ├── Events/ # Integration events (cross-context)
│ │ └── Responses/ # Response DTOs
│ │
│ ├── DddLearning.Application/ # Application services — orchestration
│ │ ├── Orders/Commands/ # Command handlers (MediatR)
│ │ ├── Orders/Queries/ # Query handlers
│ │ ├── Behaviors/ # Cross-cutting (logging, validation)
│ │ └── Interfaces/ # Port interfaces (event store, cache, etc.)
│ │
│ ├── DddLearning.Infrastructure/ # Adapters — PostgreSQL, Redis, Kafka
│ │ ├── EventStore/ # PostgreSQL event store implementation
│ │ ├── Repositories/ # Event-sourced aggregate repositories
│ │ ├── Kafka/ # Integration event publisher/consumer
│ │ ├── Redis/ # Cache, snapshots, idempotency
│ │ └── Persistence/ # Read model repository, DB init
│ │
│ ├── DddLearning.Projections/ # Event → Read Model projections
│ │ ├── OrderProjections/ # Order read model, timeline, dashboard
│ │ ├── ReadModels/ # POCO read model classes
│ │ └── Handlers/ # Projection dispatcher and rebuilder
│ │
│ ├── DddLearning.Api/ # ASP.NET Core Web API
│ │ ├── Endpoints/ # Minimal API endpoints
│ │ └── Middleware/ # Exception handling
│ │
│ └── DddLearning.BackgroundWorkers/ # Hosted services
│ └── Workers/ # Event polling, Kafka consumer
│
├── tests/
│ ├── DddLearning.UnitTests/ # Aggregate, value object, handler tests
│ ├── DddLearning.IntegrationTests/ # API and infrastructure tests
│ └── DddLearning.ArchitectureTests/ # Dependency rule enforcement
│
├── docs/ # Deep documentation with Mermaid diagrams
├── docker/ # Dockerfiles and init scripts
└── docker-compose.yml # Full local environmentQuick Start
Prerequisites
- Docker and Docker Compose
- .NET 10 SDK (for local development without Docker)
Run Everything
bash
docker compose up --buildAccess
- Swagger UI: http://localhost:5000/swagger
- Health Check: http://localhost:5000/health
- PostgreSQL: localhost:5432 (user: dddlearning, pass: dddlearning_pass)
- Redis: localhost:6379
- Kafka: localhost:9092
Demo Flow
- Create an order:
bash
curl -X POST http://localhost:5000/api/orders \
-H "Content-Type: application/json" \
-d '{"customerId": "550e8400-e29b-41d4-a716-446655440000", "street": "123 Main St", "city": "Seattle", "state": "WA", "zipCode": "98101", "country": "US"}'- Add a line item (use the order ID from step 1):
bash
curl -X POST http://localhost:5000/api/orders/{orderId}/lines \
-H "Content-Type: application/json" \
-d '{"productId": "660e8400-e29b-41d4-a716-446655440001", "quantity": 2, "amount": 29.99, "currency": "USD"}'- Submit the order:
bash
curl -X POST http://localhost:5000/api/orders/{orderId}/submit- Reserve inventory:
bash
curl -X POST http://localhost:5000/api/orders/{orderId}/reserve-inventory- Confirm payment:
bash
curl -X POST http://localhost:5000/api/orders/{orderId}/confirm-payment \
-H "Content-Type: application/json" \
-d '{"paymentReference": "PAY-12345"}'- Ship the order:
bash
curl -X POST http://localhost:5000/api/orders/{orderId}/ship \
-H "Content-Type: application/json" \
-d '{"trackingNumber": "TRACK-67890"}'- View the order:
bash
curl http://localhost:5000/api/orders/{orderId}- View the event timeline:
bash
curl http://localhost:5000/api/orders/{orderId}/timeline- View the dashboard:
bash
curl http://localhost:5000/api/orders/dashboardWhat You'll Learn
Domain-Driven Design
- Strategic Design: Bounded contexts, context mapping, ubiquitous language, subdomains
- Tactical Design: Entities, value objects, aggregates, repositories, factories, domain services, specifications
- Aggregate Design: Boundaries, invariants, consistency, transaction scope, size tradeoffs
- Rich Domain Model: Business rules in the domain, not in services or controllers
Event Sourcing
- Fundamentals: Events as source of truth, append-only streams, aggregate reconstruction
- Mechanics: Optimistic concurrency, versioning, serialization, snapshots
- Projections: Read model generation, catch-up subscriptions, rebuild capability
- Tradeoffs: When to use, when to avoid, complexity costs
Infrastructure Patterns
- Kafka: Integration event distribution, partition key strategy, idempotent consumers
- PostgreSQL: Event store design, read model tables, transactional guarantees
- Redis: Caching projections, snapshot storage, idempotency keys
Key Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Event Store | PostgreSQL + custom tables | Educational clarity; production would consider EventStoreDB or Marten |
| CQRS Mediator | MediatR | Clear command/query separation, pipeline behaviors |
| ORM | Dapper (not EF Core) | Explicit SQL for event store, educational value |
| Integration Events | Kafka | Durable, ordered, partitioned; industry standard |
| Caching | Redis | Fast, TTL support, atomic operations |
| Snapshots | Redis | Low-latency retrieval, natural TTL expiry |
Documentation
See the /docs folder for deep, staff-level documentation:
- DDD Overview
- Strategic Design
- Tactical Design
- Aggregates & Invariants
- Bounded Contexts
- Domain Events vs Integration Events
- Event Sourcing Overview
- Event Store Design
- Snapshots & Replay
- Projections & Read Models
- Eventual Consistency
- Kafka Integration
- Redis Usage
- PostgreSQL Usage
- Tradeoffs & When NOT to Use Event Sourcing
- Migration from CRUD to DDD
- Architecture Decision Records
- Running the Project
- Testing Strategy
- Interview & Staff-Level Cheatsheet
Testing
bash
# Unit tests (no infrastructure needed)
dotnet test tests/DddLearning.UnitTests
# Architecture tests (no infrastructure needed)
dotnet test tests/DddLearning.ArchitectureTests
# Integration tests (requires Docker infrastructure)
docker compose up -d postgres redis kafka
dotnet test tests/DddLearning.IntegrationTestsLicense
This is a learning repository. Use it to deepen your understanding of DDD, Event Sourcing, and distributed systems architecture.