Domain-driven design essentials

A shared language, bounded contexts, entities, value objects, aggregates and domain events: modelling complex business software.

On the roadmap:Software Design · step 3: Architecture styles

Lesson 7 of 7 · 12 min read · quiz · exercise

What DDD is for

Domain-driven design (DDD), introduced by Eric Evans, is an approach for software where the business domain is complex. Its core claim: the hardest part is not technology but understanding and modelling the business, so code should reflect the domain, in the domain's own language, developed with domain experts.

DDD has a strategic side (how to divide a big system) and a tactical side (how to model inside a part).

Strategic design

Ubiquitous language

Developers and domain experts agree on one shared vocabulary, and use it everywhere: conversations, documents and code. If the business says "policy lapse", the code has Policy.lapse(), not status = 3. Misunderstandings shrink because there is no translation layer in people's heads.

Bounded contexts

One model cannot serve a whole large organisation: words mean different things in different places. A bounded context is a boundary inside which a model and its language are consistent.

Figure SDA-SDE-07-1 · Bounded contexts · © forcsguy.com

Each context has its own model, and often its own team, code and database. Contexts relate through explicit patterns: shared kernel, customer-supplier, conformist, and the anti-corruption layer, a translator that protects your model from a messy external one. Contexts are natural candidates for microservices boundaries.

Subdomains

Break the business into subdomains and treat them differently:

  • Core: what makes the business different. Invest your best people and custom design.
  • Supporting: needed but not distinctive. Build simply.
  • Generic: solved problems (authentication, payments). Buy or reuse.

Tactical design: building blocks

Entities

Objects defined by identity, which persists as attributes change: a Customer, an Order. Two customers with the same name are still different.

Value objects

Objects defined only by their values, immutable, and interchangeable: Money(10, "EUR"), Address, DateRange. They carry their own rules (money cannot add different currencies) and remove "primitive obsession".

class Money {
  private constructor(readonly cents: number, readonly currency: string) {}
  static of(cents: number, currency: string) { if (cents < 0) throw new Error("negative"); return new Money(cents, currency); }
  add(o: Money) { if (o.currency !== this.currency) throw new Error("currency mismatch"); return new Money(this.cents + o.cents, this.currency); }
}

Aggregates

A cluster of entities and value objects treated as one unit for consistency, with a single aggregate root through which everything is accessed. The root enforces invariants ("an order total equals the sum of its lines").

class Order {                                     // aggregate root
  private lines: OrderLine[] = [];
  addLine(product: ProductId, qty: number, price: Money) {
    if (this.status !== "draft") throw new Error("Order is locked");   // invariant
    this.lines.push(new OrderLine(product, qty, price));
  }
}

Rules of thumb: keep aggregates small, change one aggregate per transaction, and refer to other aggregates by id, not by object reference.

Domain events

Facts that happened in the domain, named in the past tense: OrderPlaced, PaymentReceived. They let other parts react without tight coupling and carry changes between aggregates and contexts, usually with the outbox pattern for reliable delivery.

Repositories and services

  • A repository loads and saves aggregates, hiding persistence (a port in hexagonal architecture).
  • A domain service holds business logic that does not belong to one entity (for example a pricing calculation across several aggregates).
  • An application service (use case) orchestrates: load, call domain methods, save, publish events.

Factories

Create complex aggregates in a valid state.

How DDD fits with the rest

  • The domain model sits at the centre of clean or hexagonal architecture, free of framework code.
  • Event-driven designs and CQRS often pair with DDD.
  • Bounded contexts guide microservice boundaries.

When to use it

DDD pays off where the domain is complicated and changes: insurance, logistics, finance, healthcare. For simple CRUD applications it is overkill; a plain data model is fine.

Starting small

  1. Talk to domain experts and write down their words as a glossary.
  2. Draw the contexts and how they exchange information (a context map).
  3. Model one core subdomain deeply, with aggregates and invariants as tests.
  4. Use event storming (a workshop mapping events on sticky notes) to discover the domain together.
  5. Keep refining the model as understanding improves.

Real-world reading

Check your understanding

Question 1 of 3

0 correct so far

What is a bounded context?

Go deeper

  • Domain-Driven Design (the 'blue book') and Domain-Driven Design Distilled by Eric Evans; Vaughn Vernon: the original and a short introduction; both worth buying

© 2026 forcsguy.com. Original lesson text and diagrams. Books and sites linked above belong to their authors.