Most software starts out looking deceptively simple.
A business asks for a system to record customers, process transactions, generate reports, or manage accounts. The first version may be straightforward: a few tables, some APIs, and a handful of business rules.
Then the business grows.
A “customer” turns out to have different meanings depending on the department. A transaction can have several states. A seemingly simple rule depends on the type of account, the customer’s history, and the date on which something happened. Eventually, developers spend more time figuring out what the business actually means than writing code.
This is the kind of problem Domain-Driven Design (DDD) is intended to address.
DDD isn’t primarily a programming technique. It is an approach to designing software around the business domain—the real-world problem the software exists to solve.
To make the ideas concrete, this series will use a fictional 401(k) recordkeeping platform as a running example. Financial recordkeeping gives us a useful domain because the software has to deal with business rules, money, historical records, investments, contributions, distributions, corrections, and auditability.
The goal isn’t to build a complete retirement-plan system or provide tax or legal advice. Instead, we’ll use a simplified but realistic domain to explore how developers can model complex business systems.
What Is Domain-Driven Design?
Domain-Driven Design is an approach to software development that puts the business domain and its rules at the center of the design.
The term was popularized by Eric Evans in Domain-Driven Design: Tackling Complexity in the Heart of Software.
The central idea is relatively simple:
Software should model the important concepts and behavior of the business rather than merely mirror the structure of a database.
That distinction matters.
Imagine you’re building a system for a company that provides 401(k) recordkeeping services.
A database designer might initially think in terms of tables:
Plans
Participants
Accounts
Contributions
Investments
Transactions
DistributionsLoans
There is nothing inherently wrong with those tables.
But they don’t tell us much about the actual business.
For example, what exactly is a contribution?
Is it an employee contribution? An employer match? A correction? A contribution that has been received but not yet processed? Does it have an effective date? What happens if it exceeds a plan’s rules? How does it affect the participant’s account?
Those are domain questions.
DDD starts with those questions rather than starting with the tables.
Why Simple CRUD Applications Eventually Become Complicated
CRUD—create, read, update, delete—is perfectly reasonable for many applications.
Suppose you have a basic employee directory:
Create Employee
Read Employee
Update Employee
Delete Employee
The domain may be simple enough that the database model and the business model are nearly identical.
Financial recordkeeping is different.
Consider this requirement:
An employee contributes $500 to their 401(k).
At first glance, this sounds like:
INSERT Contribution
But the real system may need to determine:
- Is the person eligible to participate?
- Is the contribution allowed under the plan’s terms?
- What type of contribution is it?
- How should it be attributed to the participant?
- When was it effective?
- When was it received?
- How should it be allocated among investments?
- Does an employer contribution accompany it?
- Does the transaction need to be corrected later?
- How should the activity appear in the participant’s history?
The IRS describes a 401(k) recordkeeping system as tracking and properly attributing contributions, earnings and losses, investments, expenses, and benefit distributions. It also notes that participant records can include account balances, contributions, earnings, loans, compensation data, statements, and notices.
That’s much more than CRUD.
The complexity isn’t primarily caused by the number of tables.
It’s caused by the business rules.
Start With the Domain, Not the Database
This is one of the most important ideas in DDD.
Instead of asking:
What tables do we need?
start by asking:
What does the business actually do?
For our fictional retirement recordkeeping platform, we might discover concepts such as:
Plan Sponsor
↓
Retirement Plan
↓
Participant
↓
Participant Account
↓
Contribution
↓
Investment
↓
Transaction
↓
Account Balance
And there are other concepts around that model:
Vesting
Distribution
Loan
Beneficiary
Investment Allocation
Earnings
At this stage, we aren’t deciding which concepts become database tables, classes, aggregates, or microservices.
We’re simply trying to understand the business.
That distinction will become increasingly important as the series progresses.
The Domain Model
A domain model is a representation of the important concepts, relationships, rules, and behavior within a particular business domain.
For our 401(k) example, a simplified model might look like this:
Retirement Plan
│
▼
Participant
│
▼
Participant Account
/
/
▼ ▼
Contributions Investments
│ │
└───────┬───────┘
▼
Transactions
│
▼
Account State
This isn’t an implementation diagram.
It’s a way of thinking about the business.
As we learn more about the domain, this model will change.
For example, we may eventually discover that “Participant Account” is doing too much and needs to be separated from other concepts. We may discover that investment management has its own language and rules. We may discover that contribution processing and recordkeeping need different boundaries.
That’s normal.
A domain model isn’t something you draw once and then freeze forever.
It evolves as the team’s understanding of the business evolves. Martin Fowler’s discussion of ubiquitous language in DDD similarly emphasizes that the language and model should evolve as the team’s understanding of the domain grows.
DDD Is About Business Behavior, Not Just Business Objects
One common misunderstanding is that DDD means identifying nouns and turning them into classes.
If the business has:
Participant
Account
Contribution
Investment
you don’t automatically have a good domain model just because you created four classes.
The more interesting question is:
What can these concepts do, and what rules govern their behavior?
For example, instead of simply storing:
Contribution
amount = 500
we might eventually need the domain to understand concepts such as:
Contribution
├── type
├── amount
├── effective date
├── status
└── allocation
And behavior might include:
Create Employee
Read Employee
Update Employee
Delete Employee
The exact design will come later in the series.
The important point is that the domain model should express business behavior and constraints, not simply hold data.
A 401(k) Example: Why Business Rules Matter
Suppose Acme Retirement Services receives a contribution for a participant.
A simplistic application might do this:
SaveContribution()
UpdateBalance()
A domain-oriented application asks more questions.
What type of contribution is this?
Is it allowed by the plan?
Has it already been processed?
Is it being corrected?
Should it be allocated according to the participant’s current investment election?
Should it affect vesting?
Should a domain event be generated after it is accepted?
Should another system be notified?
And importantly, what should happen if something goes wrong?
For example, the IRS notes that plan administration involves applying the plan’s terms to participation, contributions, distributions, vesting, and other operations. Plan sponsors also have responsibilities around maintaining participant account records and addressing errors.
Those aren’t database concerns.
They’re domain concerns.
Ubiquitous Language: Getting Everyone to Mean the Same Thing
One of the most important DDD concepts we’ll explore later is ubiquitous language.
The basic idea is that developers and domain experts should use a common, precise vocabulary when discussing the system.
This sounds trivial until you encounter ambiguous terminology.
Consider the word account.
In a financial organization, it could mean:
- a participant account
- a bank account
- an investment account
- a trust account
Similarly, “contribution” could refer to several different business concepts.
Even “balance” can be ambiguous:
- current account balance
- vested balance
- investment balance
- available balance
If developers, product managers, and retirement-plan specialists use the same word to mean different things, the software will eventually reflect that ambiguity.
DDD tries to eliminate it.
For our series, we’ll establish a shared vocabulary such as:
Participant — a person participating in the retirement plan.
Participant Account — the financial account representing that participant’s position in the plan.
Contribution — money contributed to the plan.
Investment Option — an investment available through the plan.
Transaction — a financial activity affecting the account.
Distribution — money paid from the account to the participant or another recipient under the applicable rules.
These definitions may seem obvious.
They’re not.
As the system becomes more sophisticated, we’ll discover where those definitions need refinement.
The Domain Is Bigger Than the Application
Another useful DDD principle is recognizing that your application may interact with several different parts of the business.
Our fictional recordkeeping platform might interact with:
Payroll
│
▼
Contribution Processing
│
▼
Recordkeeping
/
/
▼ ▼
Investments Distributions
│
▼
Loans
Payroll may care about wages and deductions.
Investment systems may care about securities, prices, units, trades, and valuations.
Recordkeeping may care about participant balances and transaction history.
Plan administration may care about plan rules, eligibility, contribution formulas, and amendments.
All of these systems can exchange information while having different models of the world.
That observation leads directly to one of the major concepts we’ll explore in the next few articles: bounded contexts.
DDD Doesn’t Mean Microservices
This misconception is worth addressing early.
DDD and microservices are related, but they are not the same thing.
You can use Domain-Driven Design in:
- a monolith
- a modular monolith
- a distributed application
- microservices
- an existing legacy system
DDD is primarily about understanding and modeling the domain.
Microservices are an architectural approach for structuring and deploying software.
If we eventually decide that our retirement platform should have separate services for contributions, investments, and distributions, that decision should come after we understand the domain boundaries.
Not before.
Otherwise, you can end up with something like:
ParticipantService
AccountService
ContributionService
InvestmentService
TransactionService
DistributionService
and assume you’ve done DDD.
You may simply have created a distributed CRUD application.
DDD Doesn’t Mean Everything Needs an Object
Another misconception is that every business noun must become a domain entity.
It doesn’t.
Some concepts have identity and a lifecycle.
Others are better represented as values.
For example, in our financial domain, Participant may have identity and continuity over time.
But Money doesn’t need an identity.
You don’t care whether today’s $500 USD is the same object in memory as yesterday’s $500 USD.
You care about its value and currency.
That distinction eventually leads us to concepts such as entities and value objects, which we’ll explore in detail later.
Why Financial Recordkeeping Is Such a Useful DDD Example
A 401(k) system gives us a domain where mistakes in modeling can have meaningful consequences.
A recordkeeping system isn’t just displaying a current number.
It may need to maintain a history of contributions, earnings, investments, distributions, loans, and other participant records over many years. The IRS specifically notes that retirement-plan records can cover many years of transactions and that accurate records may be required during an audit.
That changes how we think about the software.
Consider this question:
A participant’s balance is $100,000. How did it become $100,000?
A simple application might only store:
Balance = $100,000
A domain-oriented system needs to understand the events and financial activity behind that number.
Perhaps:
Employee Contributions +$60,000
Employer Contributions +$15,000
Investment Gains +$30,000
Fees -$2,000
Distribution -$3,000
-----------------------------------
Current Position $100,000
This is intentionally simplified, but it illustrates the difference.
The current state isn’t necessarily the whole story.
The history matters.
That will become particularly important later when we discuss domain events, event sourcing, CQRS, and temporal data.
The Three Questions DDD Keeps Asking
As we work through this series, three questions will repeatedly come up.
1. What does the business mean?
If the business says “account,” what exactly does that mean?
If it says “contribution,” what type?
If it says “balance,” which balance?
This is where language and domain modeling matter.
2. What rules must always be true?
For example:
A contribution shouldn’t simply be accepted because an API request was received.
There may be business conditions that need to be satisfied first.
Those conditions are domain rules.
3. What should change together?
Suppose a distribution is approved.
Which information needs to change as one consistent business operation?
Which information can be updated later?
Which other systems need to be notified?
Those questions eventually lead us to aggregates, domain events, and bounded contexts.
What DDD Is Really Trying to Solve
At its heart, DDD is about managing business complexity.
It isn’t primarily about:
- writing more classes
- using a particular framework
- creating microservices
- replacing SQL
- using event sourcing
- adopting CQRS
- following a specific architecture
Those techniques can be useful.
But they aren’t the point.
The point is to build software that reflects how the business actually works.
For a retirement recordkeeping platform, that means the software should understand concepts such as participants, plans, contributions, investments, transactions, vesting, distributions, and loans—and the relationships and rules that connect them.
For another business, the domain might be completely different.
A shipping company may care about shipments, routes, carriers, customs, and delivery events.
An insurance company may care about policies, claims, coverage, premiums, and underwriting.
A healthcare system may care about patients, encounters, diagnoses, orders, and clinical workflows.
The technology can be similar.
The domain is what changes.
Where We Go From Here
We’ve deliberately kept the model simple.
Our fictional 401(k) platform currently looks something like this:
Plan Sponsor
│
▼
Retirement Plan
│
▼
Participant
│
▼
Participant Account
/
/
▼ ▼
Contributions Investments
│ │
└────────┬────────┘
▼
Transactions
│
▼
Account State
There are still many unanswered questions.
Should Participant Account own its transactions?
Is a contribution an entity or a value?
Should investments have their own bounded context?
Where should vesting rules live?
What happens when two systems disagree about the meaning of an account?
How do we represent a correction without destroying historical information?
How do we model a financial transaction that happened years ago?
And when should something become an aggregate?
Those questions are where Domain-Driven Design becomes much more interesting.
In the next article, we’ll start with one of the most fundamental DDD practices: Ubiquitous Language.
We’ll take the terminology in this 401(k) example and examine how developers and domain experts can build a shared language—and why getting a word like “account,” “contribution,” or “balance” wrong can eventually lead to the wrong software model.
Further Reading: The Brilliant Jerk Paradox: Is Your High Performer an Asset or a Cultural Liability?
Discover more from TACETRA
Subscribe to get the latest posts sent to your email.