Designing Domain-Driven Design Architecture
1. Understanding Strategic Domain-Driven Design
| Concept | Purpose |
|---|---|
| Subdomain | Slice of business problem (Core/Supporting/Generic) |
| Core domain | Competitive differentiator; invest most effort |
| Supporting | Custom but non-differentiating |
| Generic | Buy or use OSS (auth, billing) |
| Bounded context | Code boundary for one model |
| Ubiquitous language | Shared terms in each context |
2. Designing Bounded Contexts
| Heuristic | Apply When |
|---|---|
| Same term, different meaning | "Order" in Sales vs Fulfillment → 2 contexts |
| Different teams | Conway's law alignment |
| Different lifecycle | Different deploy cadence |
| Different storage | OLTP vs analytics |
3. Designing Context Mapping
| Pattern | Relationship |
|---|---|
| Partnership | Two teams succeed/fail together |
| Shared Kernel | Small shared model; high coordination |
| Customer-Supplier | Upstream serves downstream needs |
| Conformist | Downstream conforms with no influence |
| Anti-Corruption Layer | Translation layer to protect model |
| Open Host Service | Public protocol for many consumers |
| Published Language | Shared schema (Avro, OpenAPI) |
| Separate Ways | No integration; duplicate as needed |
4. Designing Ubiquitous Language
| Element | Practice |
|---|---|
| Source | Domain experts; not engineers alone |
| Glossary | Maintain per bounded context |
| Code mirrors language | Class/method names match domain terms |
| Refactor on rename | Update code when language evolves |
5. Designing Aggregates and Aggregate Roots
| Rule | Description |
|---|---|
| Single root | One entity is the root; access only via root |
| Transactional boundary | One aggregate per transaction |
| Reference by ID | Other aggregates referenced by ID, not object |
| Small aggregates | Reduces contention, lock scope |
| Invariants | Enforced inside aggregate boundary |
Example: Aggregate root (Java)
public class Order { // root
private final OrderId id;
private final List<OrderLine> lines = new ArrayList<>();
private OrderStatus status;
public void addLine(ProductId pid, int qty, Money price) {
if (status != OrderStatus.DRAFT) throw new IllegalStateException();
lines.add(new OrderLine(pid, qty, price)); // invariant managed via root
}
public Money total() { return lines.stream().map(OrderLine::subtotal).reduce(Money.ZERO, Money::add); }
}
6. Designing Domain Events
| Property | Description |
|---|---|
| Past tense name | OrderPlaced, PaymentCaptured |
| Immutable | Facts about what happened |
| Carries data | Self-contained payload + metadata |
| Versioned | Schema evolution required |
| Outbox pattern | Persist event in same DB txn as state |
7. Designing Anti-Corruption Layer
| Component | Role |
|---|---|
| Translator | Maps external model → domain model |
| Adapter | Talks to external API/legacy |
| Facade | Simplified domain-friendly interface |
Note: Use ACL when integrating with legacy/3rd-party systems whose model would pollute yours. Adds latency and code; worth it for protection.
8. Designing Shared Kernel Pattern
| Aspect | Guideline |
|---|---|
| Scope | Minimal shared code (value objects, IDs) |
| Ownership | Joint ownership; changes need consensus |
| Versioning | SemVer; coordinated upgrades |
| Risk | Coupling; prefer Published Language when possible |
9. Designing Customer-Supplier Relationships
| Element | Detail |
|---|---|
| Backlog priority | Customer team gets items in supplier backlog |
| Contracts | API contracts agreed upfront |
| Tests | Consumer-driven contract tests (Pact) |
| Deprecation | Notice period + migration support |
10. Designing Open Host Service Pattern
| Element | Description |
|---|---|
| Stable protocol | Versioned REST/gRPC/event schema |
| Self-service docs | OpenAPI, AsyncAPI, portal |
| Backwards compat | Additive changes, deprecation policy |
| SLA published | Latency / availability commitments |