Skip to content
Home
Domain-Driven Design: Strategic and Tactical Patterns

Domain-Driven Design: Strategic and Tactical Patterns

Software Design Software Design 8 min read 1681 words Beginner ExcellentWiki Editorial Team

Domain-Driven Design (DDD) is an approach to software development that emphasizes understanding and modeling the business domain. Instead of starting with database schemas or technical architecture, DDD starts with conversations with domain experts — the people who understand the business. The result is a software model that mirrors the business’s mental model, making the code more intuitive, maintainable, and aligned with business goals.

Ubiquitous Language

The cornerstone of DDD is the ubiquitous language — a shared vocabulary used by developers, domain experts, project managers, and stakeholders. Every term in the codebase should match the term used in business conversations.

A bank loan application should use terms like “Application”, “CreditCheck”, “Collateral”, and “Underwriting” consistently across all discussions, documentation, and code. When developers say “the Application aggregate” and domain experts say “the loan application”, they mean exactly the same thing.

Building a ubiquitous language requires active effort. Correct domain experts when they say “the thing that happens after approval” — push for precise terminology. Write down the language. Challenge inconsistencies. The language evolves as understanding deepens.

Bounded Contexts

A bounded context is a logical boundary around a domain model. Within a context, the ubiquitous language is consistent. Across contexts, the same term may have different meanings.

In an e-commerce system, the “Product” in the Catalog context has different attributes than the “Product” in the Fulfillment context. In Catalog, a product has a description, category, and images. In Fulfillment, it has dimensions, weight, and warehouse location. These are separate models in separate bounded contexts.

Identifying Bounded Contexts

Look for:

  • Different meanings: A term that means different things to different teams
  • Different lifecycles: Entities that are created, modified, and deleted in different parts of the system
  • Different teams: Organizational boundaries often align with bounded contexts
  • Different systems: External systems or legacy integrations create natural boundaries

Context Mapping

The relationships between bounded contexts are documented with context maps:

  • Partnership: Two contexts cooperate closely on a shared goal
  • Shared kernel: Contexts share a subset of the domain model
  • Customer-supplier: One context depends on the other
  • Conformist: One context blindly adopts the other’s model
  • Anticorruption layer: A translation layer protects one context from another’s model
  • Open-host service: One context provides a protocol for others to access its functionality
  • Published language: A well-documented interchange format between contexts

Core, Supporting, and Generic Domains

DDD classifies parts of the system into three categories:

  • Core domain: The most important, differentiating part of the system. This is where you invest the most modeling effort. For a payment processing company, the payment engine is core.
  • Supporting subdomain: Necessary but not differentiating. User authentication is supporting — essential but not what makes the business unique.
  • Generic subdomain: Off-the-shelf functionality. Email sending, logging, and reporting are generic — use existing solutions rather than building custom models.

Building Blocks

Entities

Entities are objects with a distinct identity that persists over time, even if their attributes change. Two entities with the same attributes are still different entities.

public class Order {
    private final OrderId id;
    private OrderStatus status;
    private Money total;
    private List<OrderLine> lines;

    // Identity-based equality
    @Override
    public boolean equals(Object o) {
        return o instanceof Order && this.id.equals(((Order) o).id);
    }
---

Value Objects

Value objects are immutable objects defined by their attributes. Two value objects with the same attributes are interchangeable.

@Value
public class Money {
    BigDecimal amount;
    Currency currency;

    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new CurrencyMismatchException();
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }
---

Always prefer value objects over primitives. EmailAddress is better than String. Money is better than BigDecimal. PhoneNumber is better than String. Value objects make the domain explicit and prevent primitive obsession.

Aggregates

An aggregate is a cluster of domain objects treated as a unit. Each aggregate has a root entity (the aggregate root) that enforces invariants for the entire cluster.

public class Order implements AggregateRoot {
    private OrderId id;
    private List<OrderLine> lines;
    private ShippingAddress address;

    public void addProduct(Product product, int quantity) {
        // Enforce invariant: cannot add to a shipped order
        if (this.status == OrderStatus.SHIPPED) {
            throw new IllegalStateException("Cannot modify shipped order");
        }
        lines.add(new OrderLine(product, quantity));
        recalculateTotal();
    }
---

The aggregate root is the only entry point for modifications. External objects hold references only to the root. This consistency boundary ensures that invariants are always enforced.

Domain Events

Domain events capture something meaningful that happened in the domain:

public class OrderPlaced implements DomainEvent {
    private final OrderId orderId;
    private final CustomerId customerId;
    private final Instant occurredAt;
    private final List<OrderLine> lines;
---

Domain events are published when the aggregate changes state. Other parts of the system (or different bounded contexts) can react:

order.place(); // publishes OrderPlaced event
// -> Inventory context reserves items
// -> Billing context charges the customer
// -> Notification context sends confirmation email

Domain Services

When an operation doesn’t naturally belong to an entity or value object, it becomes a domain service:

public class PricingService {
    public Money calculatePremium(Policy policy, RiskAssessment assessment) {
        // Complex calculation involving multiple entities
        Money baseRate = policy.getBaseRate();
        double riskFactor = assessment.getRiskFactor();
        return baseRate.multiply(riskFactor);
    }
---

Strategic Design

Event Storming

Event storming is a workshop technique for exploring a domain. Domain experts and developers gather around a wall, writing domain events on sticky notes and organizing them chronologically. This quickly reveals the core business flow, pain points, and bounded context boundaries.

Hexagonal Architecture

DDD pairs naturally with hexagonal architecture (ports and adapters). The domain model sits at the center, isolated from infrastructure concerns. Repositories, databases, and web controllers are adapters around the core.

When to Use DDD

DDD is an investment. It pays off when:

  • The domain is complex with non-obvious rules
  • There are multiple bounded contexts with different models
  • The business is the competitive differentiator
  • You need to maintain the system for years

It may be overkill for:

  • Simple CRUD applications
  • Generic functionality (logging, email, reporting)
  • Prototypes or throwaway code

Getting Started

Start with Event Storming to understand the domain. Identify the core domain. Build a ubiquitous language. Define bounded contexts. Model entities, value objects, and aggregates for the core domain. Use domain events to express side effects. Keep infrastructure concerns out of the domain model. Refine the model continuously as understanding deepens. DDD is a journey — the model evolves as the business evolves.

Strategic Design with Bounded Contexts

Strategic DDD focuses on the relationship between bounded contexts. A bounded context defines the boundary within which a particular domain model applies — the same term may have different meanings in different contexts (e.g., “Customer” in Sales vs Support). Map relationships using context maps: partnership, shared kernel, customer-supplier, conformist, anticorruption layer, open-host service, and published language.

Ubiquitous Language

The ubiquitous language is a shared vocabulary between developers and domain experts. Every term should have a precise meaning within its bounded context. Use the language in code (class names, method names, variable names), conversations, documentation, and tests. When you find a term that domain experts do not recognize, you have discovered a concept that needs alignment or removal.

Architecture Decision Records

Architecture Decision Records (ADRs) document significant architectural decisions and their rationale. Each ADR captures: the context (why the decision was needed), the decision (what was chosen), the alternatives considered (what was rejected and why), and the consequences (tradeoffs and implications). ADRs are stored in version control alongside the code, creating a historical record of design evolution. Benefits include: onboarding new team members faster, preventing repeated debates about past decisions, and providing rationale for future architects questioning why things are the way they are. Start an ADR repository with a lightweight template. Write ADRs for significant decisions that affect system architecture, technology choices, or design patterns. Review ADRs as a team before implementation.

Conway’s Law in Practice

Conway’s Law states that organizations design systems that mirror their communication structures. Teams that communicate frequently will produce tightly coupled components. Teams that communicate infrequently will produce loosely coupled components that communicate through well-defined interfaces. Use this law deliberately: align team boundaries with service boundaries (inverse Conway maneuver). If you want microservices, structure your teams around services. If you want a monolith, have one team. The organization chart and the architecture diagram should be congruent.

FAQ

What is the first step to learn this? Start by understanding the core principles and practicing with small, focused exercises before tackling complex projects.

Can I use this in production? Yes, these techniques are battle-tested in production environments. Always test thoroughly in your specific context.

Where can I find more resources? Official documentation, community forums, and the excellentwiki.com related articles provide comprehensive learning paths.

For a comprehensive overview, read our article on Api Design Principles.

For a comprehensive overview, read our article on Clean Code Guide.

Related Concepts and Further Reading

Understanding domain driven design requires familiarity with several interconnected ideas and principles that together form a complete picture. Exploring these related concepts deepens your knowledge and provides context that makes the core material more meaningful and applicable. Each concept builds on the others, creating a web of understanding that supports deeper learning and practical application. Taking time to explore how these elements connect reveals patterns that accelerate comprehension and retention of new information.

The relationship between domain driven design and adjacent fields is worth particular attention. Many of the most important insights emerge at the boundaries between disciplines, where ideas from different areas combine to create new approaches and solutions that neither field could produce alone. Exploring these connections pays dividends in both breadth and depth of understanding, revealing patterns and principles that might otherwise remain hidden from view. Cross-disciplinary knowledge is increasingly valued as problems become more complex and interconnected.

For those looking to go beyond introductory material, several excellent resources provide deeper treatment of specific aspects of domain driven design. Academic journals, industry publications, authoritative reference works, and online courses each offer different perspectives and levels of detail. The key is to match your reading to your current learning goals and build knowledge progressively, focusing on quality over quantity in your study materials. A well-chosen resource that matches your current level is worth more than dozens of resources that are too basic or too advanced.

Section: Software Design 1681 words 8 min read Beginner 756 articles in section Report inaccuracy Back to top