🌏 閱讀中文版本
I’ve watched too many teams treat DDD vs. TDD like a religious war.
Then a senior engineer stopped me mid-review: “What problem are you actually trying to solve?” That’s when I realized I wasn’t even talking about the same thing.
That conversation taught me one thing: these three tools aren’t even at the same table. One handles semantic misalignment, one handles interface disputes, one handles “change one line, break three others.” Comparing them is like asking whether a screwdriver or a wrench is better.
The real question is: which box is your sprint stuck in right now?
The Wrong Battlefield: Why No Single Methodology Wins
D, S, T map to three different dimensions: domain modeling, interface definition, and implementation quality. It’s not pick one.
DDD: The Tool for Domain Complexity
DDD’s problem is concrete: get business experts and engineers speaking the same language.
refundOrder and cancelTransaction mean different things to the business, but codebases routinely treat them as synonyms. DDD’s ubiquitous language forces developers and domain experts to sit down together, align those terms, and map them onto entities, value objects, and aggregate roots.
On a CRUD system, the marginal return is low. But when business rules are complex enough that even the domain experts can’t articulate them clearly — that’s when D’s value actually compounds. The prerequisite: a sustainable cadence of interviews and model validation. Without it, D is just vocabulary homework.
SDD: Contracts That Keep Systems Talking
Here, SDD refers specifically to Spec-driven Development — defining interface contracts between modules, services, or APIs before writing code.
I’ve watched frontend and backend teams argue on Slack all afternoon over mismatched field definitions. When the interface contract is the single source of truth, that cost disappears. S doesn’t address how to write business logic — it addresses what data looks like and how it moves.
In a multi-service environment without explicit contracts, integration cost scales quickly with service count and dependency depth. OpenAPI and Protobuf aren’t hype — they’re how distributed teams stay synchronized.
TDD: The Premium You Pay for Safe Refactoring
TDD isn’t testing. It’s design. It’s the coverage that lets you hit deploy at midnight without sweating.
Writing a failing test first forces you to clarify what the feature actually needs to solve. The resulting code trends toward low coupling and high cohesion. The refactor phase is TDD’s soul — it’s the room to clean things up after the requirement is met.
TDD isn’t a moat — it’s a premium. You pay time upfront; you buy freedom when refactoring later. On stable-requirement projects, that premium’s marginal return gets outpaced by other quality investments.
TDD is a development habit, not an architecture pattern. It applies on top of any architecture — including D or S — but it doesn’t define what the system looks like.
The Counterintuitive Part: TDD Locks In Your Current Understanding, Not Just Bugs
TDD plays a different role at different stages — treating it uniformly misses the point.
When the model is still shifting, binding tests tightly to implementation details locks your current understanding into the system. When that understanding changes, the lock becomes a cost. At that stage, TDD isn’t a premium — it’s a constraint.
Once the model stabilizes, it flips: TDD provides the confidence to refactor boldly. Every change has a test net underneath — that’s when you can move.
My own rule: while the model is still shifting, I write contract tests only (locking external behavior), not unit tests tied to implementation. Fill in the rest once the model settles.
Whether to use TDD — and when — depends on the team’s read on error cost and refactor frequency, not on project type.
Trade-offs: When Does Each Approach Pay Off?
Choosing D, S, or T isn’t pick one — it’s about where you’re stuck right now.
Scenario A: Enterprise Apps with Complex Business Logic
Characteristics: complex business rules, large domain model, large team, frequent collaboration with domain experts.
Approach: D as primary, T as support.
Why: Domain model accuracy directly affects business value. Aggregate roots and bounded contexts help the team draw clear ownership lines. Complex business logic also needs T to ensure changes don’t break established rules.
Where S fits: if most complexity is internal to a single domain model, S is lower priority. But any cross-team API, external integration, or compliance data exchange brings S back in.
Scenario B: Distributed Microservices
Characteristics: many services, independent deployments, latency-sensitive, requires consistent data contracts.
Approach: S as primary, T as support.
Why: In microservices, inter-service communication is the system. S locks interface consistency and versioning, keeping integration cost from compounding. T keeps each service’s internal logic sound.
Where D fits: when service boundaries aren’t settled yet, D’s bounded contexts help with the split. Once boundaries are clear, lightweight modeling is enough. Applying full DDD to every service is a path toward over-engineering.
Scenario C: Fast-Moving MVPs or Internal Tools
Characteristics: fast-changing requirements, small team, simple business logic, velocity matters most.
Approach: lightweight contracts + targeted tests on high-risk core logic.
Why: Full D at MVP stage slows velocity. A simplified S (basic RESTful API conventions) is enough for team coordination. T only on core logic — enough to keep technical debt from burying you before you find PMF.
I’ve seen MVP teams force DDD, and three weeks later the backlog is still debating what to name bounded contexts. That’s not rigor — that’s unclear thinking.
Where D fits: marginal return declines quickly. Forcing a domain model onto simple CRUD logic slows the rhythm without adding value.
Key Insight: D resolves semantics. S resolves collaboration. T resolves risk. Evaluate each axis independently — that’s when the marginal return of each methodology becomes visible.
Decision Framework: Three Axes, Evaluated Independently
These three dimensions are independent. Don’t follow a single path — score each axis on its own.
Axis 1: Domain Complexity → determines D investment
- Domain experts can’t articulate the rules clearly → full D (aggregate roots, domain events)
- Rules are clear but entity relationships are complex → simplified D (entities and value objects)
- Simple CRUD → skip D
Axis 2: Collaboration Boundaries → determines S investment
- Multi-team, multi-service, strict versioning → strict S (OpenAPI/Protobuf + contract tests)
- Single team across services → standard S (OpenAPI is enough)
- Monolith, small team → lightweight REST conventions
Axis 3: Error and Refactor Cost → determines T investment
- High error cost, frequent refactoring → full T (unit + integration + domain event simulation)
- Moderate change frequency → unit tests + integration tests
- Low change frequency, low error cost → manual testing + T for core logic
The three axes don’t move together. Your domain complexity might be low while collaboration boundaries are complex (typical multi-team internal tool): D low, S high, T medium. Or domain complexity high with only one team (typical core engine): D high, S low, T high.
The matrix is a reference, not a recipe.
Key Insight: T Is Cross-Context Infrastructure
When refactor cost or error cost is high, T is worth treating as a baseline quality investment.
In high-complexity contexts, T’s marginal return is highest — it reduces refactor risk. In low-complexity contexts, the return is lower, but T still provides baseline quality assurance. The difference is whether full coverage is worth the investment, not whether to write tests at all.
Market Signals and Where This Is Heading
Current market discussion of D, S, and T often fixates on “which is more advanced.” From an engineering-practice standpoint, these three methodologies are converging, not competing.
My own bet: AI will turn T from a discipline into a default, but it won’t help with the domain modeling part.
As microservices and the API economy mature, S adoption is rising. OpenAPI, GraphQL, and Protobuf are becoming standard tools; interface contracts are becoming the foundation of team coordination.
D is shifting from “full tactical implementation” toward “selective core concepts.” Many teams no longer pursue complete DDD — they pick core concepts (aggregate roots, domain events) to handle complex logic.
The market signal isn’t clear yet: AI-assisted programming’s long-term effect on T is still in the observation phase. Check back in 2-3 years when the toolchain matures and team-level empirical data accumulates — the signal will be clearer then.
Where to Start
Architecture has no standard answer — only the trade-offs that make sense right now.
Open your next sprint’s backlog: are you stuck on semantic misalignment, interface disputes, or “change one line, break three others”? Put your investment there. The other two can wait until they actually hurt.
Where is your sprint stuck today?
Sources
- Martin Fowler: Domain-Driven Design — classic DDD definitions and core concepts
- Eric Evans: Domain-Driven Design Reference — original definitions of ubiquitous language and bounded contexts
- OpenAPI Specification — SDD interface contract standard
- Protobuf Documentation — SDD serialization and contract definition
- Kent Beck: Test-Driven Development by Example — original TDD thesis and the red-green-refactor cycle
- ThoughtWorks: TDD: How it Benefits Software Development — TDD practice and marginal return analysis
- Microservices.io: Pattern – Microservice Architecture — engineering evidence for contracts and boundaries in microservice contexts