/Domain-Driven Design
🧩

Domain-Driven Design

Day 2 · Architecture / DDD · 40 min

Entity: Has identity that persists over time. Two entities with same attributes but different IDs are different.

  • Example: Payment — each payment has a unique ID, even if amount/merchant are identical.
Value Object: Defined by its attributes, not identity. Immutable. Two VOs with same attributes are equal.
  • Example: Money(amount: 100, currency: "EUR") — equality by value, not by reference.
Aggregate: Cluster of entities/VOs treated as a unit for data changes. Has a root entity.
  • Example: Payment is the aggregate root. Contains LedgerEntry entities, PaymentStatus value object.
  • Rule: Only modify aggregates through the root. External objects only reference the root.
Repository: Abstracts persistence. Looks like a collection of aggregates.
PaymentRepository.findById(id) → Payment
PaymentRepository.save(payment)

Domain Service: Business logic that doesn't belong to a single entity.

  • Example: SettlementService — orchestrates hold release, on-chain tx, and status update.
Bounded Context: A boundary around a consistent domain model.
  • Payments context: Payment, Settlement, Balance
  • User context: Profile, KYC, Wallet
  • Don't let models leak across boundaries. Use events or DTOs at boundaries.
Ubiquitous Language: The team (devs + business) uses the same terms everywhere.
  • "Authorization", "Settlement", "Hold" mean the same thing in code, docs, and conversations.

Key Points

  • Payment = Aggregate root with LedgerEntry entities
  • Money/PaymentStatus = Value Objects (immutable, equal by value)
  • Repository abstracts persistence behind collection-like interface
  • SettlementService = Domain Service for cross-entity logic
  • Bounded Contexts prevent model leakage between domains
  • Ubiquitous Language aligns code terms with business terms

Navigate