3. Domain-Driven Design – Domain Model

The domain model uses the Ubiquitous Language to provide a rich, visual view of the domain. It primarily consists of entities and their relationships. Let's take a closer look.

Entities

We characterize a domain concept as an Entity when we care about it's individuality, i.e., when it is important to distinguish it from all other entities in the system. An important aspect of an entity is that it will always have a unique identifier. For example, a Brokerage Account has a unique identifier called id which is an integer. An account with an id of 1234 is distinct from any other account in the system.

Brokerage Account

The unique identifier does not have to be a number. It can be any type as long as it is unique for that entity. For example, the unique identifier for a Security is its symbol, such as AAPL, which is a string.

Security

Aside from the unique identifier, an entity may have other attributes that define its characteristics, but those attributes do not have to be unique. For example, two brokerage accounts can have the same name, such as "My Brokerage Account", and that's just fine.

Relationships

Entities can be related to one another. It's valuable to show such relationships in our domain model. While it's easy to get bogged down with different kinds of relationships, I suggest that you start with the simple Association relationship - all it says is that two entities are related in some way. An association is shown by drawing a line between the related entities. For example, the diagram below shows that a Brokerage Account is related to a Position and a Position is related to a Security.

Simple Relationships

An important aspect of a relationship is the multiplicity of each end. We want to say that a brokerage account can have multiple positions, but a position is always associated with one account. This is done by putting a 1 on the Brokerage Account end and a 0..* (zero-to-many) on the Position end.

Enhanced Relationships

Tip: To read multiplicities correctly, read the entity name at one end, then the multiplicity at the opposite end, and finally the entity name at the opposite end.

Using this tip, we can interpret the domain model above as follows:

  1. A Brokerage Account is associated with zero-to-many Positions.
  2. A Position is associated with exactly 1 Brokerage Account.
  3. A Position is associated with exactly one Security.
  4. A Security is associated with zero-to-many Positions (in different accounts).

If the multiplicity on either end of a relationship is not specified, it should be interpreted as unknown. There is no default value for multiplicity. However, people sometimes leave out multiplicities on either or both ends of a relationship just to unclutter domain diagrams. In fact, many diagraming tools allow suppressing multiplicities for the same reason, even if they are specified.

Note that we have laid out the Position entity below and to the right of the Brokerage Account. This has no "official" meaning in domain modeling, but this vertical orientation helps to suggest a parent-child relationship. A good layout can aid in understanding the domain. I recommend that you pay extra attention to it. A bad layout can cause unnecessary confusion. For example, putting Position above Brokerage Account would reduce the clarity of the diagram. When two entities are peers, you may want to show them side-by-side.

While the diagram above shows one-to-many relationships, you can also have one-to-one and many-to-many relationships. For example, Country to Capital is a one-to-one relationship, whereas Student to Course is a many-to-many relationship.

Value Objects

After entities and relationships, the next important concept in domain modeling is that of a Value Object. A Value Object is a grouping of attributes similar to entities, however the major difference is that value objects have no identity. Let's try to understand this concept using an example.

Add an attribute called pricePerShare to our Security entity. An example of such a security is AAPL with a price of $191.94 per share.

Security with price

Note that pricePerShare has no units in the diagram above. This may be okay if our system tracks securities traded only in one country, and hence in one currency. However, this would not be sufficient for a system that tracks securities traded in multiple currencies. This situation requires specifying the currency along with the numeric price. Let's add another attribute to the Security entity called currencyCode, which will store values like USD, GBP, etc.

Security with price and currency

However, since price and currency code are so tightly related, let's group them into a separate value object called Money, denoted by <<value object>>.

Security with money

Note that Money has no identity. While it has multiple attributes, none of them is a unique identifier. Money is just a value ($100 is $100) – it has no identity (at least in our domain).

An alternate way to show the diagram above is to replace the pricePerShare attribute with a relationship between Security and Money called pricePerShare.

Security with relationship to Money

Note: While it shows up in the same place as multiplicity would, pricePerShare is a role. This should be read as: a security has an attribute called pricePerShare of type Money.

Now that we have Money factored out as a standalone concept, it can be reused in other contexts where an amount and currency are needed. For example, the Lot entity described in the previous section can now be modeled as follows:

Lot with relationship to Money

Entities vs. Value Objects

How can you tell if a domain concept is an entity or a value object? In addition to having a unique identity, an entity encapsulates state that can change. Changes may be so extensive that the object might seem very different from what it once was. Yet, it is the same object with the same identity.

On the other hand, a value object is just a value - it quantifies or describes a property of another object, usually an entity. If the property changes, the value object can be completely replaced by another with the new value. For example, if the current balance of a Brokerage Account changes from $1000 to $2000, you can throw away the Money object that represented $1000 and replace it with a new one representing $2000. Said in another way, Money is fungible.

Often, domain model designers will separate out the state of an entity as a value object. This allows the main entity to remain relatively stable and the value object to change as the state changes. For example, a Vehicle entity can be broken into a Vehicle entity and Vehicle State value object as shown below:

Vehicle with relationship to its state

Developers may tend to model value objects as entities because they may need to store value objects as distinct rows in a database table. While this approach may be required to implement value objects in the relational model, don't let it influence your domain model. If you do, your domain model will not reflect true DDD thinking.

For a detailed discussion of these concepts, read Implementing Domain-Driven Design by Vaughn Vernon.

Tips and Tricks

Now that you understand DDD concepts, here are some tips on creating good domain models:

  • Avoid introducing software concepts when creating a domain model. This is not an exercise in software design or database design! The focus should be on understanding the business domain and the functional requirements of the system you're developing. Therefore, the domain model should be created and owned jointly by product managers, designers, engineers, and other disciplines involved in the project.
  • The process of creating a domain model is very compatible with iterative practices. You don't have to create the entire domain model before building. It's probably good to start with a general idea of the various subject areas in your domain and then dive deep into specific areas as needed.
  • Domain-driven Design (DDD) is also compatible with Behavior-driven Development (BDD). While this article covers ubiquitous language and modeling a domain using entities and relationships, BDD focuses more on the behavior of these entities. Together, they complement each other. The behavior of a system cannot be described clearly without understanding the terminology and structure.
  • Do not cram your entire domain model into one diagram. A diagram with hundreds of entities and relationships cannot be understood by normal human beings! Break it up into smaller subject areas. Remember the rule of Seven, Plus or Minus Two – limit the number of entities in your diagrams from 5 to 9!
  • Avoid crossing of relationships. If you can't draw your diagram without crossing relationships, it is probably too complex. Break it into smaller pieces.
  • Engineers: a good domain model should translate directly into the core domain layer of the system without introducing technical concerns like persistence and I/O. We will talk more about this in later sections.

If you've gotten up to this section as a non-technical reader – fantastic! You have enough knowledge to start using domain modeling to solve complex business problems with your team.

If you're an engineer, continue through the remaining sections to understand how to translate your domain model into a working implementation.

In either case, send me your questions and feedback on Twitter and recommend this article to your friends and colleagues wherever you hang out these days – Twitter, LinkedIn, Threads, Slack, Teams, Discord, etc.

In my opinion, there's nothing more fun than hosting a domain modeling party :)

Navigation

  1. Introduction
  2. Ubiquitous Language

  3. Domain Model
  4. Bounded Context

  5. Command-Query Separation

  6. Layered Architecture

  7. Domain Layer

  8. Conclusion