Skip to content
Leo Jung
Go back

[DDD Intro #2] A model is not a copy of reality

Edit page

To understand DDD, we first need to rethink the word “model.” Many people think of a model as something that copies reality as accurately as possible. There is a member in reality, so we create Member. There is an order, so we create Order. There is a payment, so we create Payment. We try to include every attribute that exists in reality.

But a domain model is not a copy of reality.

A model is a selective interpretation of reality for a specific purpose. The same reality can produce completely different models depending on the problem being solved. A good model is not the one that contains the most reality. It is the one that explains and handles the current problem well.

The map is not the territory

The map analogy is useful for explaining models. A map is not the actual land. A map reduces, omits, and emphasizes reality.

A subway map does not reflect geographic distance and direction accurately. But it is an excellent model for transferring between subway lines. A hiking map emphasizes elevation and trails. A navigation map for driving emphasizes roads and traffic information. They represent the same area, but the model changes depending on the purpose.

Domain models are the same.

Think about the real person called a “member” in a subscription service. A person has an email address, name, phone number, payment methods, subscription history, and customer support history. If we try to copy reality as-is, we will create a huge User object.

User
- id
- name
- email
- phoneNumber
- password
- paymentMethods
- subscriptions
- loginHistory
- coupons
- customerSupportTickets
- marketingAgreements
- notificationSettings
- ...

At first, this looks convenient. Since everything is in one place, it looks reusable. But over time, this object becomes a huge lump that no one understands precisely.

Why? Because we tried to copy one reality into one model.

Different purposes need different models

Even for the same person, what matters changes by context.

In the authentication context, this person is an account that can log in. What matters is email, password, authentication methods, and lock status.

In the subscription context, this person is a subscriber. What matters is whether they are currently subscribed, which plan they use, when renewal happens, and whether they have access.

In the payment context, this person is a payer. What matters is payment method, billing information, failed payment history, and refund eligibility.

In the customer support context, this person is an inquirer. What matters is inquiry history, processing status, support notes, and compensation policy.

If every context uses the same User model, problems appear. One team modifies User to change login lock status. Another modifies User to add a subscription renewal policy. Another modifies User to add customer support notes.

Eventually, User becomes everyone’s object and no one’s object.

DDD emphasizes the boundary where a model is valid to avoid this situation. A model has meaning within the context of solving a specific problem. Just because there is one person in reality does not mean there must be one model in software.

A good model boldly throws things away

Modeling is less about putting more of reality into code and more about deciding what to leave out.

A subscription renewal model does not need the user’s profile photo. A customer support inquiry model does not need the user’s password hash. A payment retry model does not need to know whether the user agreed to receive marketing messages.

We must throw away what is unnecessary so that what matters can be seen.

For example, in a subscription context, the important model can be as simple as this:

public class Subscription {
    private SubscriptionId id;
    private SubscriberId subscriberId;
    private PlanId planId;
    private SubscriptionStatus status;
    private Period currentPeriod;
    private RenewalPolicy renewalPolicy;

    public void renew(PaymentResult paymentResult) {
        // renewal rules
    }

    public void cancel(CancelReason reason) {
        // cancellation rules
    }

    public boolean allowsAccess(LocalDateTime at) {
        // access eligibility
    }
}

This model does not explain the whole real user. But it is better for dealing with the problem of subscription. Subscription status, access period, renewal policy, and cancellation rules are visible inside the model.

By contrast, a User model that contains every user detail may contain more of reality, but it can make the subscription problem harder to understand.

Data models and domain models are different

Another reason people mistake models for copies of reality is database-centered thinking. We often design tables first and then map those tables to objects.

users
subscriptions
payments
plans

Tables matter. Systems must persist data. But a table structure is not the same thing as a domain model.

A data model is concerned with storage and retrieval. Normalization, indexes, foreign keys, performance, and query patterns matter. A domain model is concerned with meaning and behavior. Rules, state changes, invariants, and responsibilities matter.

This does not mean the two must be completely separate. In practice, when using an ORM, domain objects and persistence models are connected to some degree. But if we confuse them as the same thing, the domain model easily becomes an empty data object.

For example, this code is close to a data-centered model:

subscription.setStatus(SubscriptionStatus.CANCELED);
subscription.setCanceledAt(now);
subscription.setCancelReason(reason);

This code expresses a domain behavior:

subscription.cancel(reason, now);

Both can store similar data. But their meaning is different. The first exposes the procedure of changing fields to the outside. The second asks the subscription to perform cancellation. Whether cancellation is allowed, which time should be recorded, and which event should occur can become the responsibility of Subscription.

A domain model is not a container for data. It is a language for expressing domain rules.

A model is not separate from code

Sometimes modeling is treated only as documentation or diagramming. We talk with domain experts in a meeting room, organize terms, and draw boxes and arrows on a whiteboard. This process is very important.

But in DDD, a model does not stop at the document. It must be reflected in code. If a document says subscription cancellation but the code only has updateStatus(CANCELED), the model is not alive in the code.

A good model must be able to move between conversation and code.

If a domain expert says, “Paid subscribers can keep access until the end of the remaining period even after cancellation before the renewal date,” that concept should be visible in the code too.

subscription.cancelAtPeriodEnd(reason);

Or the policy object might be visible like this:

AccessPeriod accessPeriod = subscription.cancel(policy, reason);

There is no single correct answer. What matters is that important domain concepts have names and responsibilities in code.

Models are discovered

If we expect to create a good model from the start, DDD becomes burdensome. In reality, the first model is usually insufficient because our domain understanding is shallow.

At first, everything may live inside Subscription, but over time concepts such as BillingAgreement, Entitlement, Renewal, and RefundPolicy may split out. At first, one cancel() may look sufficient, but later it may become cancelImmediately(), cancelAtPeriodEnd(), or suspendDueToPaymentFailure().

This change is not failure. It is a signal that the model is evolving along with domain understanding.

The problem is believing the first model is a fixed design. The domain continues to reveal itself. Operational issues, edge cases, policy changes, and conversations with domain experts reveal concepts we did not know before. The model must be refined each time.

Good modeling is not a one-time act of completion. It is the process of continuously finding better names and boundaries.

A model that solves the problem, not a model that resembles reality

A model that copies reality as-is feels intuitive at first. But software does not exist to contain all of reality. Software exists to solve a specific problem.

So when judging a good domain model, we should ask:

Does this model explain the problem we are solving now?
Are the words used in conversations with domain experts visible in the code?
Are important rules gathered inside the model?
Is unnecessary information blurring the core concepts?
Where is the boundary within which this model is valid?

A model closer to reality is not always a better model. Sometimes it must be simpler than reality. Sometimes it must divide reality in a different way. Sometimes the same reality must be represented by several models.

One user in a subscription service can be an Account in the authentication context, a Subscriber in the subscription context, and a Payer in the payment context. This is not duplication. It is modeling according to purpose.

Closing

A model is not a copy of reality. A model is the result of understanding the domain and selectively interpreting reality to solve a specific problem.

That means modeling is not simply finding nouns and turning them into classes. Modeling is deciding what to consider important, what to throw away, what names to use, and within which boundary consistency must be maintained.

In DDD, a good model does not live only in documents. A good model moves in code as domain language. Rules spoken by domain experts appear as object behaviors and names, and important concepts are expressed not as accidental fields but as explicit types and responsibilities.

In the next post, we will cover the most important foundation that makes this model possible: Ubiquitous Language. Good design starts with good language.


Edit page
Share this post:

Previous Post
[DDD Intro #3] Ubiquitous Language: Good design starts with good language
Next Post
[DDD Intro #1] DDD is not a technology, but a perspective