System Design Cases
DDD Tactical Patterns
DDD Tactical Patterns: aggregates as transactional consistency boundaries, entities with identity, immutable value objects, domain events for cross-aggregate eventual consistency, repository pattern abstracting persistence, factories, application/domain/infrastructure layers. Order aggregate (root + items + Money/Quantity VOs) shows transactional unit; OrderPlaced event triggers Shipment aggregate update in separate TX. Includes scenarios for happy-path aggregate transaction, cross-aggregate via event, repository load/save, and god-aggregate anti-pattern.
Tactical DDD: aggregates, invariants and durable events
Tactical DDD — это набор modelling building blocks, а не шаблон ORM. Entity имеет identity, Value Object определяется значением, Aggregate защищает выбранные invariants внутри consistency boundary, Repository скрывает retrieval/persistence aggregate, а Domain Service нужен для поведения, которое естественно не принадлежит одному объекту.
Что утверждает паттерн — и чего он не гарантирует
- Aggregate — consistency boundary for selected invariants, not an arbitrary object graph or universal distributed transaction.
- Vernon recommends small aggregates and references other aggregates by identity; this is a heuristic guided by true consistency needs.
- Domain Event records something meaningful in the domain; an Integration Event is a versioned cross-context contract and may be derived after commit.
- Outbox plus relay normally yields at-least-once delivery; exactly-once external effects still require idempotency/reconciliation.
Границы и компоненты
| Компонент | Ответственность |
|---|---|
| Application Command Handler | Оркестрирует use case, transaction и retry policy без присвоения domain invariants. |
| Order Aggregate | Проверяет synchronous invariants и меняет state через root. |
| Money Value Object | Сохраняет amount/currency semantics и value equality. |
| Aggregate Repository | Загружает/сохраняет aggregate с optimistic version. |
| Transactional Outbox | Коммитит integration intent в той же local transaction. |
| At-Least-Once Relay | Повторяет publish до подтверждения и допускает duplicates. |
| Integration Event Topic | Передаёт versioned facts между bounded contexts. |
| Idempotent Billing Consumer | Deduplicate по event identity и ведёт reconciliation. |
Сценарии
Enforce a hard invariant
The handler loads one Order version, the aggregate verifies currency, remaining refundable amount and state transition, then repository commits aggregate and event intent together.
Проверяемый исход: Invalid refund/charge state is rejected before any externally visible effect.
Reject a stale aggregate version
Two commands load version 7. One commit advances to 8; the second compare-and-set fails and must reload/re-evaluate business rules.
Проверяемый исход: A lost update is not silently accepted, and retry does not bypass invariants.
Relay after commit or publish crash
A committed outbox row survives a process crash. The relay can publish the same event more than once when acknowledgement is lost; the consumer records event identity atomically with its effect.
Проверяемый исход: No commit-then-publish loss; duplicates are expected and contained by idempotency.
Coordinate a cross-aggregate process
A process spanning Order and Billing uses durable state, compensation and reconciliation. It does not pretend the aggregate transaction covers a remote payment provider.
Проверяемый исход: The state machine exposes pending/failed/compensated outcomes and never calls eventual consistency a hard money invariant.
Failure, concurrency и evolution checklist
- Use optimistic/pessimistic concurrency deliberately and test conflicts.
- Commit aggregate state and outbox intent in one supported local transaction.
- Deduplicate consumer identity in the same transaction as its local effect.
- Model ambiguous payment timeout separately from definite failure; query/reconcile before compensation.
Метрики, units и допущения
- Invariant arithmetic uses decimal minor units or exact decimal types; floating binary money comparisons are out of scope.
- Relay backlog = produced events/s − sustainably published events/s; positive backlog requires capacity or backpressure.
- Idempotency retention must cover maximum broker redelivery/replay horizon plus operational recovery margin, all with explicit time units.
Числа здесь — размерностные формулы или явно помеченные учебные inputs. Паттерн архитектуры сам по себе не задаёт SLA, throughput, latency или fault tolerance.
Связанные темы
Первичные источники
- https://www.domainlanguage.com/ddd/reference/
- https://www.dddcommunity.org/library/vernon_2011/
- https://www.dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_2.pdf
Scope note
Tactical patterns protect only explicitly modelled local invariants. Remote payments, messages and cross-aggregate workflows retain partial-failure, replay and compensation semantics.