Playbook
DDD- Designing Software Around the Business, Not the Database (Ddd Designing Software Around The Business Not The Database)
What is Domain-Driven Design? A guide to the limits of data-driven design, the power of ubiquitous language, and when DDD is a worthwhile investment.
One domain, growing decisions
Keep one e-commerce ordering system in mind throughout this chapter. In year one, Order is a simple record carrying selected products and a total. CRUD is sufficient.
Year 1 → create, list, update orders
Year 2 → promotions and discounts
Year 3 → returns and partial payments
Year 4 → instalments, inventory reservation, shipment
Year 5 → fraud control and country-specific tax
Each need adds more than a field to Order; it adds a decision. A domain is not a software problem. It is the business problem being solved. Here it is the work of safely accepting, pricing, paying for, returning, and delivering an order.
Start with the problem: why does code lose the language of the business?
Early products are often simple: draw a table, add an endpoint, persist a record. That is not wrong. But when pricing, returns, risk, delivery, and permissions multiply, the value of a system is no longer in its tables. It is in the correctness of the decisions those rules enforce.
Database → Table → CRUD → Service layer → scattered business rules
Business → Shared language → Domain model → Code → visible decisions
DDD does not reject technology. It changes the starting point of design: not the schema, but the meaning of the work.
Concepts at first use
📦 Domain
The business area a system solves: for example ordering, credit, or insurance.
📦 Domain model
A representation of the business rules, concepts, and transitions in code.
📦 Ubiquitous Language
The same term used by domain experts, product, and engineering for the same concept.
📦 Core Domain
The business area that differentiates the product and carries the highest decision complexity.
PlaceOrder carries a business intention, not merely a technical call. If it succeeds, OrderPlaced is a business fact. When names fail to carry the language of the work, every feature requires a new round of translation.
Why does the same order break across layers?
When product says “Preferred Customer” while engineering sees only Status = 1, one concept has been split into two languages. Which meaning will the next promotion rule follow? Ubiquitous language reduces that ambiguity before implementation begins.
Likewise, order.Status = Paid is not a safe behaviour on its own:
order.Status = Paid
↓
Was payment actually verified? → unknown
Was inventory reserved? → unknown
Could the order already be cancelled?→ unknown
order.ConfirmPayment(payment)
↓
verifies payment evidence
applies required business rules
rejects invalid transitions
The point is not to ban setters. It is to give a business rule one owner. Team size changes the economics too: in a short-lived application built by one or two people, CRUD simplicity wins; in a product evolved for years by more than ten people, shared language and explicit ownership pay back faster.
Why CRUD works wonderfully for a long time
When a team is small, the product is singular, and rules are few, Controller → Service → Repository → Database is fast and understandable. For simple admin screens, short-lived prototypes, and basic record entry, that simplicity is a real advantage. DDD is not an ideology against CRUD.
The breaking point is not more rows in a table. It is one model being forced to represent different business intentions. An Order suddenly serves promotion, tax, discount, return, inventory reservation, shipment, and risk control.
Database-first
OrderRow → status = 2 → UpdateOrderStatus()
Business-first
Order → ConfirmPayment() → ReserveInventory() → StartFulfilment()
The first approach changes data; the second exposes a business decision. The issue is not whether code is short or long. The issue is a model that hides why the business acts.
Ubiquitous Language: removing the translation tax
If a domain expert says “preferred customer” while the code says UserStatus = 1, the system holds two competing realities. The gap creates misunderstandings, duplicates a rule across services, and turns meetings into recurring translation sessions.
❌ InsertNewSchoolYear()
✓ OpenNewSchoolYear()
❌ UserStatus = 1
✓ customer.MarkAsPreferred()
❌ OrderRow
✓ LoanApplication / Order / Subscription
A ubiquitous language is not replacing every technical name with a business term. The model needs to be accurate enough to explain the decisions in its context. Like a map that shows what matters for a journey rather than every detail of the world, a domain model represents the decisions that matter.
The anemic model: making objects data and behaviour procedural
An anemic domain model is a structure where service classes operate on entities full of public setters. OrderService, PricingService, DiscountService, and ShipmentService gradually carry the same rules in different places. The object transports data while behaviour lives outside it.
❌ order.Status = Paid
❌ order.Total = -10
✓ order.ConfirmPayment(payment)
✓ order.ApplyDiscount(discount)
A rich model does not mean placing everything in an entity. It means keeping rules that must never be broken next to the behaviour that can protect them. An order, for example, must not enter shipment before its payment is confirmed. That rule needs one owner.
The cost of DDD and the right context
DDD requires analysis, shared-language workshops, deliberate naming, and team discipline. That cost does not pay back for simple data entry. The decision should follow domain complexity and rate of change, not technical enthusiasm.
| Signal | CRUD is usually better | DDD is a meaningful investment |
|---|---|---|
| Business rules | Few and stable | Layered, critical, and changing |
| Product life | Short prototype | Long-lived product |
| Cost of error | Low | Financially or operationally high |
| Team language | Unambiguous | One term has multiple meanings |
As rule count grows, the cost of change is rarely linear. When one rule is duplicated in k services, a change costs at least O(k); when rules interact, test scenarios grow faster. DDD does not erase this cost. It makes ownership and boundaries visible.
The first step: observe the language, not rewrite everything
- Select the workflow where mistakes are most expensive.
- Record the verbs and nouns used by domain experts.
- Resolve conflicting terms inside one context.
- Model new behaviour as a business intention, not a field update.
- Bring together only the behaviour needed to prevent invalid states in that boundary.
The database, framework, and API are adapters in this process; they do not own the domain decision.
Associations that create false confidence
❌ DDD = layered architecture
✓ DDD = a discipline for modelling a complex business domain
❌ DDD = many interfaces
✓ DDD = finding the right owner for decisions and rules
❌ Every project needs DDD
✓ The investment pays back in complex, changing domains
❌ Aggregate = a group of entities
✓ Aggregate = the transaction boundary that protects consistency
❌ Repository = one for every table
✓ A repository is for aggregate roots that are meaningful in the domain
Decision checklist
- Which violated rule causes the most expensive business error?
- Do domain experts and code use the same word for the same concept?
- Is a state change
status = 2, or a meaningful business behaviour? - Does a rule have one owner, or is it duplicated in services?
- Is this domain complex enough for analysis cost to pay back over time?
If these questions do not have clear answers, do not start by choosing entities or frameworks. Make the problem, language, and boundary clear first.
What should remain with you
- DDD is not against databases; it recognises that business rules are more valuable than stored data.
- Ubiquitous language is not meeting notes; it is the contract for code, APIs, and decisions.
- Anemic models distribute behaviour into services and enlarge the risk of invalid states.
- DDD is not for every project; it pays back where complexity and change create cost.
The value of software is not in storing data. It is in preserving correct business decisions over time.
The next chapter brings this mindset into code: Value Objects, Entities, and Aggregate boundaries.
Before moving to tactical patterns
Where do these business decisions live in code? Tactical DDD patterns such as Value Objects, Entities, and Aggregates answer that question. They are not the starting point, however; they are tools for protecting the language, rules, and boundaries made clear here.
FAQ
Frequently asked questions
What is Domain?
The business area a system solves: for example ordering, credit, or insurance.
What is Domain model?
A representation of the business rules, concepts, and transitions in code.
Is it true that "DDD = layered architecture"?
DDD = a discipline for modelling a complex business domain
Engineering Principles Learned
- Design should begin with business decisions and shared language, not tables.
- Behaviour belongs at the domain boundary where it can prevent invalid state.
- DDD investment must be justified by change rate, error cost, and domain complexity.
Continue reading
Continue reading
Related articles
DDD in Code: Entity, Value Object, and Aggregate
How do DDD tactical patterns work? Explore Value Objects, Entities, Aggregates, Domain Services, Application Services, and Repository boundaries through a…
Related articles
From CRUD (Create, Read, Update, Delete) to CQRS (Command Query Responsibility Segregation): The Problem Is Not Code, It Is the Model
What is CQRS, what is the difference between CRUD and CQRS, and when should CQRS be used? A guide to why one model stops being enough in larger systems.
Related articles
From Layers to Features: Why Did Vertical Slice Emerge?
Why does layered architecture slow change as a system grows? An architectural guide to Vertical Slice as a decision about feature ownership, behaviour…