Domain-Driven Design (DDD) is a software development approach that places the business domain at the center of all design decisions. Instead of starting from the database or the technology, DDD proposes starting by deeply understanding the problem we are solving and modeling it faithfully in code. This lesson lays the conceptual foundations you will need to tackle the rest of the module: what DDD really is, how a domain is broken down, what a domain model is, and the crucial difference between strategic and tactical design. Understanding these foundations is essential because DDD is neither a library nor a framework, but a way of thinking that affects the entire architecture of your applications.

Contents

  1. What is Domain-Driven Design?
  2. Domain, subdomains, and their classification (core, supporting, generic)
  3. The domain model
  4. Strategic design versus tactical design
  5. When to apply DDD (and when not to)

  1. What is Domain-Driven Design?

DDD was formalized by Eric Evans in his book Domain-Driven Design: Tackling Complexity in the Heart of Software (2003). Its central premise is simple to state but hard to master: software must reflect the business domain it automates.

The domain is the sphere of knowledge and activity around which the application revolves. For an insurance company, the domain includes concepts such as policies, premiums, claims, coverages, or the insured. DDD holds that the greatest value of a complex application lies not in its technology, but in how well its code captures the rules and the language of that domain.

The three pillars of DDD are:

  • Close collaboration with domain experts. Developers cannot properly model what they do not understand. Constant conversations with those who know the business are needed.
  • A shared and precise language (the Ubiquitous Language, which we will see in lesson 06-02) used both when speaking and in the code.
  • An explicit domain model that evolves as understanding of the problem improves.

DDD is neither code-first nor database-first: it is model-first.

  1. Domain, subdomains, and their classification

A large domain is rarely uniform. It breaks down into subdomains, smaller and more cohesive areas of the business. Identifying and classifying subdomains is one of the first strategic exercises in DDD, because it tells us where to invest effort and where not to.

There are three types of subdomains:

Subdomain type Description Recommended strategy Example (insurance company)
Core What differentiates the business from the competition. It is the reason the company exists. Invest the best talent and apply DDD in depth. Build it custom. Premium calculation and pricing, risk assessment
Supporting Necessary for the business but not a differentiator. Simpler development, can be outsourced. Lightweight DDD. Document management of policies
Generic A problem common to many businesses, already solved by the market. Buy a commercial solution or use an existing service. Don't reinvent it. Authentication, sending emails, standard billing

The practical key is this: not everything deserves the same effort. Applying rich DDD modeling to a generic subdomain (for example, sending emails) is a waste of resources. Modeling effort should be concentrated on the core subdomain.

graph TD
    D[Domain: Insurance Company] --> C[Core Subdomain:<br/>Pricing and Risks]
    D --> S1[Supporting Subdomain:<br/>Document Management]
    D --> S2[Supporting Subdomain:<br/>Customer Service]
    D --> G1[Generic Subdomain:<br/>Authentication]
    D --> G2[Generic Subdomain:<br/>Email Notifications]

In this graph TD (top-down) Mermaid diagram:

  • The D node represents the complete domain of the insurance company.
  • The --> arrows indicate decomposition into subdomains.
  • The core subdomain (where the company competes) is visually distinguished from the supporting and generic ones.

The goal of this map is communicative: it helps the organization agree on where the real value lies.

  1. The domain model

A domain model is a rigorous and selective abstraction of domain knowledge. It is neither a database diagram nor just any UML class diagram: it is a system of concepts, rules, and behaviors that captures how the business works.

Characteristics of a good domain model:

  • It is behavior, not just data. An anemic model that only has getters and setters is not DDD. The model must contain the business rules.
  • It uses the language of the business. Classes and methods are named the way domain experts name things.
  • It evolves. As more is learned about the domain, the model is refined.

Let's compare an anemic model with a rich model:

// ANEMIC MODEL (to avoid): only data, no rules
public class Policy {
    private BigDecimal premium;
    private boolean active;

    public BigDecimal getPremium() { return premium; }
    public void setPremium(BigDecimal premium) { this.premium = premium; }
    public boolean isActive() { return active; }
    public void setActive(boolean active) { this.active = active; }
}

In the previous example:

  • The Policy class is a mere data container.
  • Any external code can modify premium or active without any restriction.
  • The business rules (when can a policy be activated? can the premium be negative?) live scattered across external services, not in the model. This is known as the Anemic Domain Model and it is an anti-pattern in DDD.
// RICH MODEL (DDD): the data is protected and the behavior lives here
public class Policy {
    private final BigDecimal premium;
    private PolicyStatus status;

    public Policy(BigDecimal premium) {
        if (premium == null || premium.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("The premium must be positive");
        }
        this.premium = premium;
        this.status = PolicyStatus.DRAFT;
    }

    public void activate() {
        if (this.status != PolicyStatus.DRAFT) {
            throw new IllegalStateException("Only a draft policy can be activated");
        }
        this.status = PolicyStatus.ACTIVE;
    }
}

Here the change is profound:

  • The constructor validates that the premium is positive: it is impossible to create an invalid Policy.
  • The premium field is final: it cannot be changed after creation (partial immutability).
  • The activate() method encapsulates the business rule: only a policy in the DRAFT state can be activated. The rule lives inside the model, not outside.
  • The state only changes through methods with business names, not through generic setters.

This is the heart of DDD: the model protects its own rules (invariants).

  1. Strategic design versus tactical design

DDD operates on two complementary levels. Confusing them is one of the most common mistakes when starting out.

Aspect Strategic design Tactical design
Question it answers How do we divide the domain into parts? How do we model each part in code?
Level Architecture, organization, teams Classes, objects, patterns
Key concepts Subdomains, Bounded Contexts, Ubiquitous Language, Context Mapping Entities, Value Objects, Aggregates, Repositories, Domain Services and Domain Events
Audience Architects, leaders, business Developers
Covered in Lessons 06-02 and 06-04 Lesson 06-03

Strategic design draws the map: it decides the boundaries, the languages, and how the different parts of the system relate to each other. Tactical design provides the implementation patterns within each part. The strategic level is more important: good tactical design within poorly drawn boundaries still produces a bad system.

  1. When to apply DDD (and when not to)

DDD has a cost: it requires time, collaboration with experts, and a learning curve. It is not free and it does not always pay off.

Apply DDD when:

  • The domain is complex (many business rules, not just CRUD).
  • The project is strategic and long-lived.
  • You have access to domain experts willing to collaborate.

Avoid or reduce DDD when:

  • The application is a simple CRUD with hardly any rules.
  • It is a throwaway prototype or a proof of concept.
  • The subdomain is generic and a commercial solution exists.

Practical rule: the complexity of the approach should be proportional to the complexity of the domain.

Common Mistakes and Tips

  • Confusing DDD with a specific architecture. DDD does not require using microservices, hexagonal architecture, or event sourcing. These are approaches that combine well with it, but DDD is independent of them.
  • Starting with the tactical patterns. Many teams jump straight to "making aggregates and repositories" without having done the strategic work. The result is poorly placed boundaries. Always start with the strategic level.
  • Creating anemic models. If your domain classes only have getters/setters, you are not doing DDD, you are doing procedures disguised as objects.
  • Applying DDD to everything. Reserve the effort for the core subdomain. For generic parts, buy or reuse.
  • Tip: talk to domain experts in their language and listen to the nouns and verbs they use: they are the clues to your model.

Exercises

Exercise 1. For an e-commerce platform, identify at least one subdomain of each type (core, supporting, generic) and justify the classification.

Exercise 2. Review the anemic Policy class from section 3. Identify which business rules remain unprotected and propose at least two behavioral methods that should exist in a rich model.

Exercise 3. Indicate whether you would apply full DDD, lightweight DDD, or no DDD to each case, justifying your answer: (a) an internal tool to record employees' vacation; (b) the premium calculation engine of an insurance company; (c) the SMS notification sending module.

Solutions

Solution 1. A reasonable example:

  • Core: the product recommendation engine, if it is what differentiates the platform from the competition.
  • Supporting: management of the product catalog (necessary, but not a differentiator against competitors).
  • Generic: the payment gateway (a problem solved by services such as Stripe or Redsys; not reinvented).

Solution 2. Unprotected rules: the premium can be null or negative; the active state can be changed to any value without control of valid transitions. Proposed behavioral methods: a constructor that validates the premium (as in the rich model), and activate() and cancel() methods that control the allowed state transitions instead of a setActive(boolean).

Solution 3. (a) No DDD or very lightweight: it is a simple CRUD with few rules. (b) Full DDD: it is the core subdomain, complex and strategic. (c) No DDD: generic subdomain; it is advisable to use an existing service or library.

Conclusion

In this lesson we have established the foundations of Domain-Driven Design: we have seen that DDD places the business domain at the center, that a domain breaks down into core, supporting, and generic subdomains—each with its own strategy—that the domain model must be rich in behavior and not a mere data container, and that DDD operates on two levels, strategic and tactical. We have also learned to judge when it is worth applying.

From here we will delve into each level. In the next lesson, "Strategic Design: Bounded Contexts and Ubiquitous Language", we will address how to draw the boundaries of the model and how to build the shared language that maintains consistency between business and code.

Application Architecture Course

Module 1: Fundamentals of Application Architecture

Module 2: Design Principles and Tactics

Module 3: Architectural Styles and Patterns

Module 4: Distributed Architectures and Microservices

Module 5: Event-Driven Architectures and Messaging

Module 6: Domain-Driven Design (DDD)

Module 7: Data and Persistence

Module 8: Cloud Architecture and Deployment

Module 9: Quality, Security and Observability

Module 10: Evolution, Governance and Case Studies

© Copyright 2026. All rights reserved