---
title: "07 — Design-Driven Development — TDD and DDD"
uid: design-driven-development-tdd-and-ddd
tags: ["methodologies", "ddd", "domain-modeling", "roadmap:software-architect", "testing", "tdd"]
excerpt: "TDD and DDD both work by forcing a design decision before the code — one from the behavior side, one from the domain side. Design techniques that produce tests and models as byproducts."
date: 2026-08-13T03:27:41+0000
source: https://www.aveshina.my.id/en/blog/design-driven-development-tdd-and-ddd
---

Buzzwords was how TDD and DDD read until I used them and felt the shift. The model that made them land: **both work by forcing a design decision before the code, TDD from the behavior side and DDD from the domain side** [1][2]. They're not testing and modeling techniques bolted on after the fact — they're _design_ techniques that happen to produce tests and models as a byproduct. Once I saw them that way, the reason they're listed under "Architectures" stopped being weird.

The common thread is a refusal to write code without first stating, externally and concretely, what that code is supposed to be. TDD states it as an executable behavior; DDD states it as a shared model of the business. Both make the invisible assumptions in your head visible to everyone else.

## TDD: red, green, refactor

Test-driven development is the process of writing tests for a requirement that **fail until** the software is developed to meet them; once they pass, the cycle repeats to refactor or build the next feature [1]. In theory this ensures software meets requirements in the simplest form and avoids defects [1]. The cycle is three beats:

- **Red** — write a test for the next behavior, watch it fail.
- **Green** — write the minimum code to make it pass.
- **Refactor** — clean up the code with the test still green as a safety net.

The insight I missed for years is that the _red_ step is where the design happens. Writing the test first forces you to call the API you wish existed — its name, its inputs, its outputs — before any implementation exists. If the API is awkward to call from a test, it'll be awkward to call from production code. TDD catches that awkwardness at the cheapest possible moment.

## DDD: the domain leads the design

Domain-driven design structures software around the **business domain** it serves, using a shared language between developers and domain experts called the **ubiquitous language** [2]. Two concepts do most of the work:

- **Bounded contexts** — explicit boundaries within which a model and its ubiquitous language are consistent. The word "order" means different things to the shipping team and the billing team; DDD says those are different bounded contexts, each with its own model, not one god-model trying to please everyone.
- **Aggregates** — clusters of domain objects treated as a single unit for data changes, with one object as the root and a single entry point for modifications.

```figure
<svg viewBox="0 0 680 280" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Bounded contexts around a single shared word. Three overlapping circles labelled Shipping, Billing, Catalog. In the overlap the word Order appears, with three short definitions pointing into each circle: a shipment to deliver, a financial charge, a product listing. A note says: same word, different models — keep them as separate bounded contexts.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">
    <circle cx="240" cy="140" r="100" fill="#ccfbf1" fill-opacity="0.45" stroke="#0d9488" stroke-width="1.5"/>
    <circle cx="380" cy="140" r="100" fill="#e0e7ff" fill-opacity="0.45" stroke="#6366f1" stroke-width="1.5"/>
    <circle cx="310" cy="60" r="70" fill="#fce7f3" fill-opacity="0.45" stroke="#db2777" stroke-width="1.5"/>

    <text x="240" y="200" font-size="12" font-weight="700" fill="#134e4a" text-anchor="middle">Shipping context</text>
    <text x="240" y="216" font-size="10" fill="#0f766e" text-anchor="middle">order = shipment to deliver</text>

    <text x="380" y="200" font-size="12" font-weight="700" fill="#1e1b4b" text-anchor="middle">Billing context</text>
    <text x="380" y="216" font-size="10" fill="#3730a3" text-anchor="middle">order = financial charge</text>

    <text x="310" y="40" font-size="12" font-weight="700" fill="#500724" text-anchor="middle">Catalog context</text>
    <text x="310" y="56" font-size="10" fill="#9d174d" text-anchor="middle">order = product listing</text>

    <text x="310" y="244" font-size="11" fill="#475569" text-anchor="middle" font-style="italic">same word "Order," three consistent models — one per bounded context</text>
  </g>
</svg>
```

The point of DDD is to stop building one model that tries to serve every part of the business — that model ends up serving none of them well. Each bounded context owns a model tailored to its actual responsibility, and integration between contexts is explicit.

DDD is especially useful when building software for complex business domains where getting the model right matters more than technical novelty [2]. It's overkill for a CRUD app and essential for an insurance-claims system.

## Where they meet

TDD and DDD compose. DDD gives you the bounded contexts and the ubiquitous language — the nouns and boundaries of the domain. TDD gives you the red-green-refactor loop inside each context — the behaviors each model must support. Used together, you design a context's model by writing failing tests in the ubiquitous language, then implement until they pass. The two techniques address different axes (domain structure vs. behavior) and don't compete.

## How I use this

For complex domains, I start with DDD — sketching bounded contexts and writing down the ubiquitous language with the domain experts, before any code. Inside each context, I drive the model out with TDD. For simple CRUD-style features, I skip DDD entirely and just TDD the behavior — forcing a bounded-context exercise on a to-do list app would be ceremony with no payoff. The judgment is matching the weight of the technique to the complexity of the domain.

## References

[1] "Test Driven Development (TDD)," roadmap.sh — Software Architect. [Online]. Available: [https://roadmap.sh/software-architect/tdd](https://roadmap.sh/software-architect/tdd)

[2] "Domain Driven Design (DDD)," roadmap.sh — Software Architect. [Online]. Available: [https://roadmap.sh/software-architect/ddd](https://roadmap.sh/software-architect/ddd)

```quiz
Q: In TDD, where does the actual design work happen?
- During the refactor step, after the test passes
- During the red step, when you write a test calling the API you wish existed
correct: 1
explain: Writing the failing test first forces you to design the API — name, inputs, outputs — before any implementation. Awkwardness in calling it from a test is the cheapest moment to catch design problems.

Q: What is the "ubiquitous language" in DDD?
- A programming language used to implement the domain model
- A shared language between developers and domain experts used consistently in code and conversation
correct: 1
explain: The ubiquitous language is a common vocabulary shared between developers and domain experts, used in conversation, docs, and the code itself so the model reflects the real domain.

Q: A bounded context is best described as…
- a single model that serves every part of the business
- an explicit boundary within which a model and its language are consistent
correct: 1
explain: A bounded context keeps a model and its ubiquitous language consistent within one area. Different contexts (Shipping, Billing) can have different models for the same word like "Order."

Q: DDD is most appropriate when…
- the domain is complex and getting the model right matters more than technical novelty
- the app is simple CRUD and the goal is to ship fast
correct: 0
explain: DDD pays off in complex business domains where an accurate model is the point. For simple CRUD it's ceremony without benefit.

Q: How do TDD and DDD relate?
- They compete — pick one or the other
- They compose — DDD gives the domain structure, TDD drives behavior inside each context
correct: 1
explain: DDD provides bounded contexts and the ubiquitous language; TDD drives the model's behavior inside each context via red-green-refactor. They operate on different axes and combine well.
```
