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.

A software model shaped by business language rather than database tables

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

  1. Select the workflow where mistakes are most expensive.
  2. Record the verbs and nouns used by domain experts.
  3. Resolve conflicting terms inside one context.
  4. Model new behaviour as a business intention, not a field update.
  5. 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

  1. DDD is not against databases; it recognises that business rules are more valuable than stored data.
  2. Ubiquitous language is not meeting notes; it is the contract for code, APIs, and decisions.
  3. Anemic models distribute behaviour into services and enlarge the risk of invalid states.
  4. 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

Related articles

Related articles

Paylaş