Domain-Driven Design urges teams to move beyond “just make it work” and instead develop a deep, shared understanding of the problem space, captured directly in code. It treats models as deliberate, strict simplifications of reality that express the essentials precisely and form a ubiquitous language used by both domain experts and developers. DDD shines where domain complexity is the critical challenge, and that precision has strong security side effects: it clarifies intended behavior, closes ambiguous loopholes, and helps systems respond safely to unusual or malicious inputs rather than relying on ad hoc fixes.
DDD’s core building blocks reinforce correctness and resilience. Entities are defined by identity and continuity through time; they coordinate operations on their internals and uphold invariants, which reduces opportunities for inconsistent or tampered state. Value objects have no identity, are immutable, and form conceptual wholes that encapsulate constraints (and relevant logic), enforcing correctness at the source and making them safe to pass around. Aggregates define transactional consistency boundaries with a single root that controls access, ensuring invariants cannot be bypassed and that changes remain coherent—an essential guardrail for integrity and security.
Bounded contexts make explicit that a model—and its ubiquitous language—only holds within a defined semantic boundary; the same term can legitimately mean different things across contexts. Treating cross-context communication as a translation problem, not a reuse shortcut, prevents DRY-driven model sharing from smuggling conflicting semantics and creating vulnerabilities (for example, invariant violations when Finance and Shipping needs diverge). Context maps, upstream/downstream relationships, and alignment with organizational communication structures help teams isolate models, make mappings explicit, and maintain precise, secure integrations across system boundaries.
Figure 3.1. A model train looks like the real, original train.
Figure 3.2. One possible model of people and pets
Figure 3.3. Joe, age 34, shoe size 9, and his dog Zarphac together with Jane, age 28, shoe size 6, no pet
Figure 3.4. The same model as before but another representation
Figure 3.5. To build a bike-riding robot, you need a deep understanding of how to make a right turn.
Figure 3.6. Two different models of people—good for different things
Figure 3.7. The domain model forms a language in common.
Figure 3.8. The model is ubiquitous; it uses "quantity" consistently all over the place.
Figure 3.9. Fundamental building blocks of a domain model
Figure 3.10. The attributes of the customer change, but the identity remains the same.
Figure 3.11. Some entities need to be globally unique.
Figure 3.12. Some entities only have local identities.
Figure 3.13. Entities coordinate operations.
Figure 3.14. A value object should form a well-defined concept.
Figure 3.15. Value objects should enforce their own invariants.
Figure 3.16. The company domain model
Figure 3.17. The company modeled as an aggregate.
Figure 3.18. Ubiquitous language is present everywhere, at all times, to promote clarity and common understanding.
Figure 3.19. Raw domain model
Figure 3.20. A refined domain model with less redundancy
Figure 3.21. Final domain model
Figure 3.22. Two contexts with the same concept but with different semantics
Figure 3.23. A unified order model that’s shared between the Shipping context and the Finance context.
Figure 3.24. Interaction between Finance and Shipping
Figure 3.25. The Shipping context is downstream of the Finance context.
Summary
- Building domain models is a good way to promote deep learning about the domain.
- A domain model should be a strict and unambiguous representation of the domain that captures only the most important aspects.
- When creating a domain model, you make a choice among many possible models.
- The domain model forms a language for communicating about the system.
- Entities, value objects, and aggregates are the basic building blocks for your domain model.
- Entities have an identity that’s consistent during their life cycle and can contain other entities or value objects.
- The uniqueness of entities always has a scope and that scope depends on your model.
- A value object doesn’t have an identity but rather is defined by its value.
- A value object must always be immutable and should form a conceptual whole.
- An aggregate is a conceptual boundary that groups together other model objects and is responsible for upholding invariants among those objects.
- An aggregate always has an aggregate root and, in code, that root is typically the same as the aggregate.
- The aggregate root has global identity because this is the only part of the aggregate that other parts of the model can hold a reference to.
- The ubiquitous language is spoken by everyone on the team, including domain experts, to ensure a common understanding.
- The domain model is bound by the semantics of the ubiquitous language.
- The bounded context is the context in which the semantics of the model hold. As soon as the semantics change, the model breaks and the boundary of the context is found.
- Using Conway’s Law is a good starting point when trying to find the boundary of a context.
- Data crossing a semantic boundary is of special interest from a security perspective because this is where the meaning of a concept could implicitly change.
FAQ
When should I apply Domain-Driven Design (DDD), and when is it not the right tool?
- Use DDD when your system’s critical complexity is the business/domain (most business apps, logistics, baggage handling, even technical domains like compilers).
- DDD adds less value when the critical complexity is primarily technical (for example, extreme I/O throughput/latency in a network router).
- Always identify the critical complexity first; if it’s the domain, invest in modeling—it also improves security by removing ambiguity.
In DDD, what exactly is a “model”?
- A model is a deliberately simplified set of domain concepts, relationships, and rules that capture what matters for a purpose.
- It is not a diagram; diagrams, code, and schemas are representations of the model.
- There’s no single “true” model—choose the model that best serves the problem and purpose.
What does it mean for a model to be “strict,” and why does that matter for security?
- Strict means precise and unambiguous: terms, attributes, relationships, and behaviors are clearly defined.
- Strictness enables consistent behavior across the system, supports enforceable invariants, and reduces “almost-synonym” drift.
- Security benefits: fewer logical loopholes, fewer integrity bugs, and clearer places to enforce rules.
How deep does my domain understanding need to be, and why?
- Deeper than practitioner “know-how.” You are building a machine that must act correctly without improvisation.
- Collaborate with domain experts to iteratively evolve models; there is no ready-made model in their heads.
- Deeper understanding exposes edge cases and invariants that, if missed, become security weak points.
What’s the difference between Entities and Value Objects?
- Entities: defined by identity (not attributes), identity persists through state changes, coordinate operations on their internals, and maintain invariants. Prefer generated IDs; identity can be local, global, or external.
- Value Objects: defined by value (no identity), immutable, form a conceptual whole, can reference entities, and explicitly enforce constraints.
What makes a good Value Object, and how does it help security?
- Key traits: immutability, well-defined concept (whole value), value-based equality, encapsulated domain logic, and self-enforced invariants (e.g., Age range).
- Security benefits: prevents invalid states from existing, centralizes constraints, safe to pass around, and reduces accidental misuse.
What is an Aggregate, and what rules govern it?
- An aggregate defines a boundary around closely related model objects with a single root entity.
- Only the root is externally referenced; internal entities have local identity and are accessed via the root.
- Invariants inside the aggregate must hold within a transaction; cross-aggregate invariants are eventually consistent.
- Security benefits: invariants can’t be bypassed, and state changes are controlled at the root.
What is the Ubiquitous Language, and how should I use it?
- A shared, precise vocabulary derived from the model, used in conversations, stories, UI, code, and database names.
- Use it consistently when discussing the system; awkward phrasing often signals model gaps or needed refactoring.
- Don’t police casual expert-to-expert talk; insist on precision when specifying or implementing system behavior.
What are Bounded Contexts, and how do I find them?
- A bounded context is the semantic boundary within which the model’s meanings hold.
- Find boundaries where the meaning of terms changes (e.g., “Order” in Shipping vs Finance).
- Practical cues: analyze the language, look for divergent invariants, and consider team/communication structures (Conway’s Law).
How should different contexts interact, and why can sharing models be risky?
- Do not share a unified model across contexts just to satisfy DRY; DRY is about semantics, not syntax.
- Crossing a boundary implies semantic translation; define explicit mappings and responsibilities (upstream/downstream).
- Use a context map to visualize interactions and highlight concepts needing translation; this prevents broken invariants and security issues (like order tampering by misinterpreted “price”).
Secure by Design ebook for free