Design Patterns

Domain-Driven Design (DDD) Design Pattern

I spent six months building a system where developers and domain experts used different words for the same things. We called it a "record." They called it...

24 Mar 2024

Domain-Driven Design (DDD) Design Pattern

I spent six months building a system where developers and domain experts used different words for the same things. We called it a "record." They called it a "case." We said "status update." They said "disposition." Every conversation required translation. Every translation introduced bugs.

Domain-Driven Design (DDD) starts with one rule: talk the same language as your business. Then it gives you patterns for modeling complex domains in code that actually mirrors how the business thinks.

The Core Ideas

Ubiquitous Language. Developers and domain experts share one vocabulary. If the business calls it an "order," the code has an Order class. Not TransactionRecord. Not PurchaseEntity. The same words appear in conversations, documentation, and code.

Bounded Contexts. A large domain gets split into smaller contexts, each with its own models and rules. "Customer" means something different in Billing vs. Shipping. That's fine — each context owns its own definition. The boundary is explicit.

Entities. Objects with identity. Two User objects with the same name are not the same user — they have different IDs. Entities have lifecycles. They change over time.

Value Objects. Objects defined by their attributes, not their identity. A Money(100, "USD") is the same as another Money(100, "USD"). They're immutable. Use them for things like addresses, date ranges, and currency amounts.

Aggregates. Clusters of entities and value objects treated as a single unit. An Order aggregate might contain OrderLines and a ShippingAddress. You load and save the whole aggregate together. The aggregate root (Order) enforces all the business rules for its cluster.

Repositories. The interface between your domain model and your database. They abstract away storage details. Your domain logic never calls SQL — it calls orderRepository.findById(id).

Domain Events. Things that happened in the domain. OrderPlaced, PaymentReceived, InventoryReserved. They're facts. Other parts of the system react to them. This keeps bounded contexts loosely coupled.

When DDD Pays Off

  • Complex business rules that change frequently.
  • Multiple teams working on different parts of the same product.
  • Domain experts are available and willing to collaborate.
  • The cost of getting the model wrong is high (financial systems, healthcare, logistics).

When It Doesn't

  • Simple CRUD apps with little business logic. DDD adds ceremony you don't need.
  • Teams without access to domain experts. DDD without domain collaboration is just over-engineering.
  • Prototypes and MVPs. Move fast first, model deeply later.

The benefit: Code mirrors the business. New requirements map naturally to the model. Conversations between developers and domain experts are productive because they share the same language.

The cost: Significant upfront investment. Modeling sessions take time. The learning curve is real — aggregates, bounded contexts, and eventual consistency are not trivial concepts. A poorly applied DDD is worse than no DDD at all.

I use DDD for systems where the domain is the competitive advantage — where getting the model right directly affects the business. For straightforward data-in, data-out apps, simpler architectures serve better.

Keep reading