DDD Fundamentals
Domain-Driven Design — Reference Guide
DDD fundamentals applied to microservices decomposition. This is the "theory" — Business Domain.md is the "practice" applied to the assessment.
1. What is DDD & Why Does It Matter Here?
Domain-Driven Design (Eric Evans, 2003) =
Software design centered on the DOMAIN (business domain)
not on technology.
Why is it critical for microservices?
Monolith → Microservices = WHERE do you cut?
DDD provides the answer: cut along BOUNDED CONTEXTS
Wrong: cut by layer (1 service for DB, 1 for API, 1 for UI)
Wrong: cut by code module (arbitrary boundaries)
Right: cut by DOMAIN BOUNDARY (each service = 1 business capability)
2. Strategic Design — High-Level Concepts
2.1 Domain & Subdomain
Domain = the entire business area the software serves
Example: Enterprise Travel & Event Management Platform
Subdomain = a smaller part of the domain, divided by business function
3 types of Subdomains:
┌─────────────────────────────────────────────────────────────┐
│ │
│ CORE DOMAIN SUPPORTING DOMAIN GENERIC DOMAIN │
│ ───────────── ───────────────── ────────────── │
│ Competitive Needed but does not Commodity, │
│ advantage. create competitive buy off-the-shelf│
│ Must build in-house, advantage. or use existing │
│ invest the most Build or buy platform │
│ │
│ Examples: Examples: Examples: │
│ • Travel Booking • Workforce Mgmt • Email/SMS │
│ • Event Management • Reporting • Authentication │
│ • Payment • Allocation • File Storage │
│ │
│ → Pour resources & → Adequate quality → Buy SaaS or │
│ AI effort here is sufficient use platform │
└─────────────────────────────────────────────────────────────┘
2.2 Bounded Context
Bounded Context = a clear semantic boundary for a domain model
Key insight:
The same word "Booking" has DIFFERENT MEANINGS in different contexts:
Travel Context: Booking = flight + hotel + itinerary + pricing
Event Context: Booking = venue + date + capacity + registration
Payment Context: Booking = transaction reference + amount + status
Workforce Context: Booking = staff assignment + shift + location
→ Each context has its OWN MODEL for "Booking"
→ NO shared model → NO shared database
→ This is the reason for per-service databases
Bounded Context ↔ Microservice mapping:
Ideal: 1 Bounded Context = 1 Microservice
In practice:
• 1 BC can = multiple services (if BC is too large)
• Multiple BCs can = 1 service (if BCs are too small, use modules within 1 service)
For this assessment (5 engineers):
1 BC = 1 Service is the right granularity (not too small, not too large)
2.3 Ubiquitous Language
Ubiquitous Language = SHARED LANGUAGE between developers and business
Each Bounded Context has its own language
Code must REFLECT business language
Example (Travel Context):
✅ class Itinerary { ... } not ❌ class TravelPlan { ... }
✅ class BookingConfirmation { ... } not ❌ class OrderStatus { ... }
✅ method ConfirmBooking() not ❌ method ProcessOrder()
Why does this matter?
When business says "confirm booking" → dev finds ConfirmBooking()
No need to "translate" between business language and code
2.4 Context Map — Relationships Between BCs
Bounded Contexts are NOT fully isolated — they interact.
The Context Map describes HOW they interact.
Relationship patterns:
┌──────────────────────────────────────────────────────────────┐
│ │
│ PARTNERSHIP │
│ Two teams coordinate, co-develop the interface │
│ Travel ←→ Event: shared concepts "date", "location" │
│ │
│ CUSTOMER-SUPPLIER (upstream/downstream) │
│ Upstream provides data, downstream consumes │
│ Travel (upstream) → Reporting (downstream): booking data │
│ │
│ CONFORMIST │
│ Downstream accepts upstream model as-is │
│ External GDS API → Travel: must conform to external schema │
│ │
│ ANTI-CORRUPTION LAYER (ACL) │
│ Downstream translates upstream model to internal model │
│ New Services → ACL → Legacy Monolith (Payment) │
│ │
│ SHARED KERNEL │
│ Two BCs share a small part of the model (use with caution!) │
│ Shared: UserId, Currency, DateRange value objects │
│ │
│ OPEN HOST SERVICE + PUBLISHED LANGUAGE │
│ Service exposes public API with well-defined schema │
│ API Gateway: exposes REST API for all consumers │
│ │
└──────────────────────────────────────────────────────────────┘
3. Tactical Design — Concepts Within a Bounded Context
3.1 Building Blocks
┌──────────────────────────────────────────────────────────┐
│ Tactical Design Building Blocks │
│ │
│ ENTITY │
│ • Has identity (ID) │
│ • Lifecycle: created → modified → deleted │
│ • Examples: Booking, User, Event │
│ │
│ VALUE OBJECT │
│ • No identity, compared by value │
│ • Immutable │
│ • Examples: Money(100, "USD"), DateRange(start, end), │
│ Address(street, city, country) │
│ │
│ AGGREGATE │
│ • Cluster of entities + value objects │
│ • 1 AGGREGATE ROOT = entry point │
│ • Transaction boundary: save/load entire aggregate │
│ • Examples: Booking (root) → BookingItems → Travelers │
│ │
│ DOMAIN EVENT │
│ • Records "something happened" │
│ • Past tense: BookingCreated, PaymentProcessed │
│ • Triggers side effects within or across services │
│ │
│ REPOSITORY │
│ • Interface for accessing aggregates from persistence │
│ • IBookingRepository.GetById(id) │
│ • Implementation: EF Core, Dapper, etc. │
│ │
│ DOMAIN SERVICE │
│ • Logic that doesn't belong to a single entity │
│ • Example: PricingService.CalculateTotal(booking, rules) │
│ • Stateless │
│ │
│ APPLICATION SERVICE │
│ • Orchestrates use cases, contains no business logic │
│ • MediatR handler = application service │
│ • Example: CreateBookingHandler calls domain → repo → event│
│ │
└──────────────────────────────────────────────────────────┘
3.2 Aggregate Design Rules
Rules for aggregate design (critical for microservices):
1. PROTECT INVARIANTS WITHIN AGGREGATE
Booking total must = sum(BookingItem.price)
→ Booking aggregate enforces this rule
2. SMALL AGGREGATES
Small aggregate = small transaction = better performance = less concurrency conflict
❌ 1 aggregate with 10,000 child entities
✅ Booking with max 20-50 BookingItems
3. REFERENCE OTHER AGGREGATES BY ID
❌ Booking.Traveler (embedded)
✅ Booking.TravelerId (ID reference)
→ Cross-aggregate = eventual consistency
4. ONE AGGREGATE = ONE TRANSACTION
Never save 2 aggregates in 1 DB transaction
→ Use domain events to sync between aggregates
3.3 CQRS & Event Sourcing
CQRS (Command Query Responsibility Segregation):
┌─────────────────────────────────────────────────┐
│ │
│ Commands (Write) Queries (Read) │
│ ───────────── ──────────── │
│ CreateBooking GetBookingById │
│ CancelBooking SearchBookings │
│ UpdateItinerary GetDashboard │
│ │
│ Write Model Read Model │
│ (normalized, (denormalized, │
│ domain-rich) query-optimized) │
│ │
│ Azure SQL Azure SQL (views) │
│ (transactional) or Redis (cache) │
│ │
│ → Scale independently │
│ → Optimize each for its purpose │
└─────────────────────────────────────────────────┘
Event Sourcing (optional, per aggregate):
Instead of storing current state → store sequence of events
BookingCreated → ItemAdded → ItemAdded → PaymentReceived → Confirmed
Rebuild state: replay events from beginning
✅ Benefits: Full audit trail, temporal queries, debugging
❌ Cost: Complex, eventual consistency, snapshots needed for performance
Recommendation for this project:
CQRS = YES (for all services)
Event Sourcing = SELECTIVE (only Booking aggregate if audit is required)
4. DDD in the Microservices Context
4.1 Bounded Context → Service Boundary
DDD concept → Microservice equivalent
────────────────── ──────────────────────────
Bounded Context → Service boundary
Ubiquitous Language → API contract + event schema
Aggregate → Internal consistency boundary
Domain Event → Integration event (cross-service)
Repository → Service's database
Context Map → Service communication topology
ACL → Adapter service / gateway layer
4.2 Communication Patterns
WITHIN a service (synchronous):
API → Application Service → Domain Service → Repository
MediatR pipeline: Request → Handler → Response
BETWEEN services (asynchronous preferred):
Service A → publish DomainEvent → Azure Service Bus
Service B → subscribe → handle event → update own state
Example:
Travel publishes BookingConfirmed
→ Workforce subscribes → auto-allocate staff
→ Communications subscribes → send confirmation email
→ Reporting subscribes → update dashboard
BETWEEN services (synchronous when needed):
Service A → HTTP call → Service B API
Use when: immediate response needed (price check, availability)
Risk: coupling, cascading failure
Mitigation: Circuit breaker (Polly), timeout, cache
4.3 Data Ownership
RULE: Each service OWNS its data. No shared database.
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Travel │ │ Event │ │ Workforce│
│ Service │ │ Service │ │ Service │
│ │ │ │ │ │
│ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │
│ │Travel│ │ │ │Event │ │ │ │WFM │ │
│ │ DB │ │ │ │ DB │ │ │ │ DB │ │
│ └──────┘ │ │ └──────┘ │ │ └──────┘ │
└──────────┘ └──────────┘ └──────────┘
↑ ↑ ↑
└────── NO cross-DB JOINs ──────┘
Instead:
• API calls to get data from other services
• Event subscriptions to maintain local copies (read model)
• CQRS read model denormalizes data needed from multiple sources
5. Anti-Patterns to Avoid
| Anti-Pattern | Description | Why It's Wrong | Correct Approach |
|---|---|---|---|
| Distributed Monolith | Microservices that must be deployed together | Worst of both worlds: latency + coupling | Loose coupling, async events |
| Shared Database | 2 services sharing the same DB | Coupled via schema, cannot deploy independently | Per-service DB |
| Anemic Domain Model | Entities with only properties, logic in services | Violates OOP, business logic scattered | Rich domain model, logic in entities |
| God Service | 1 service doing everything | This is just a monolith in disguise | Split by bounded context |
| Chatty Services | Service A calls B calls C calls D synchronously | Latency cascade, failure cascade | Async events, CQRS read model |
| Wrong Boundary | Split by tech layer (DB service, API service) | Every feature change → modify every service | Split by business capability |
| Big Bang Migration | Migrate everything at once | Zero downtime violated, massive risk | Strangler Fig, module by module |
6. DDD Process — How to Apply It
Step 1: EVENT STORMING (workshop)
Team + domain expert sticky notes events → discover domain
Output: list of domain events, aggregates, commands
Step 2: IDENTIFY BOUNDED CONTEXTS
Group related events/aggregates → context boundaries
Look for: different language, different lifecycle, different team
Step 3: CONTEXT MAP
How do contexts interact? Who depends on whom?
Identify: upstream/downstream, ACL needs, shared kernel
Step 4: DESIGN AGGREGATES
Per bounded context: what are the aggregates?
Apply small aggregate rules
Define domain events per aggregate
Step 5: DEFINE APIs & EVENTS
External contract: REST API schema + event schema
Internal: domain model, handlers, repositories
For this assessment (5 engineers, with AI):
Step 1: AI analyzes legacy code → extracts events (replaces workshop)
Step 2: AI suggests boundaries → team validates
Step 3: Team designs context map
Step 4-5: AI generates boilerplate → team reviews domain logic
7. Key DDD Concepts Summary Table
| Concept | Definition | Level | Application |
|---|---|---|---|
| Domain | Business domain | Strategic | Entire platform |
| Subdomain | Sub-area of domain | Strategic | Travel, Event, Payment, ... |
| Bounded Context | Semantic boundary for a model | Strategic | = Service boundary |
| Context Map | Map of context relationships | Strategic | Service topology |
| Ubiquitous Language | Shared language (dev + business) | Strategic | API + event naming |
| Entity | Object with identity | Tactical | Booking, User, Event |
| Value Object | Immutable, identity-less object | Tactical | Money, DateRange, Address |
| Aggregate | Consistency boundary cluster | Tactical | Booking + items + travelers |
| Domain Event | Record of something that happened | Tactical | BookingCreated |
| Repository | Persistence access interface | Tactical | IBookingRepository |
| Domain Service | Cross-entity business logic | Tactical | PricingService |
| Application Service | Use case orchestrator | Tactical | CreateBookingHandler |
| ACL | Anti-Corruption Layer | Strategic | Legacy Payment adapter |
| CQRS | Separate read/write models | Tactical | Per-service pattern |