Thursday, August 21, 2025

The Two Faces of DDD: Strategy vs. Tactics

In today's post, I will explain what Domain-Driven Design (DDD) is, what groups of patterns it includes, how they differ, and how they can help improve your application's quality. 


What is it?

Domain-Driven Design is a collection of patterns that help you align your software more closely with the business it is intended to support. These patterns focus on reflecting domain knowledge - including its language, rules, and constraints - directly within your application. By doing so, they simplify and enhance collaboration with domain experts and stakeholders.


Strategic vs. Tactical

DDD patterns fall into two main categories:

  • Strategic Patterns: These help define and maintain domain language and software boundaries. You should apply them when you want to define boundaries of services or moduls within your codebase. They guide you in establishing clear boundaries around your models and specifying how communication between them should work.

  • Tactical Patterns: These are closer to the code. They guide how to design and name classes and structures within your application. Tactical patterns help you create cohesive, well-structured code with clearly defined responsibilities.


Why use them?

  • Improved domain understanding: Even using just a few DDD patterns encourages deep engagement with domain concepts and language. This leads to clearer communication, continuous feedback, and a shared understanding of domain constraints.

  • Better alignment between software and domain: DDD patterns help ensure that the domain language permeates the codebase - in APIs, classes, and modules. This reduces the translation gap between code and business language, helping to prevent misunderstandings and bugs.

  • Easier evolution: Well-defined boundaries and responsibilities make it easier to locate and change parts of the code. You'll also have immediate context on what you're modifying and why.

  • Prepared for future modernization: Encapsulation and clearly assigned responsibilities make it easier to merge, split, or redesign components when modernizing the system.

  • Simplified onboarding: Domain knowledge embedded in the code - and properly isolated - makes onboarding easier. New team members can quickly understand specific domain aspects by exploring relevant parts of the code.

  • Improved AI code generation: Clearly defined boundaries create a well-scoped context, which helps AI tools provide more accurate and helpful support for code generation.


What are the costs?

  • Upskilling effort: DDD patterns are not easy to grasp or apply effectively. Two key challenges:

    • To use them well, you must understand the business domain. It’s not just about code and architecture but also about dialogue and shared understanding. Some developers may find this uncomfortable.

    • There is no one-size-fits-all diagram or solution. Patterns serve as guidelines for what to encapsulate and protect, but the actual implementation is up to you.

  • Implementation varies by developer: Even if two teams apply the same DDD pattern to the same problem, their implementations can differ significantly - yet both may be correct. This can make usage of DDD more difficult.

  • Maintenance: As your domain understanding evolves, the code should evolve with it. Without ongoing maintenance, domain models can become artificial and misleading, ultimately harming delivery.

  • Added complexity: In practice, DDD tactical patterns work best when combined with architectures like Ports and Adapters or internal CQRS. These add structure and guidance but also increase the learning curve.

  • Not always the right default: While tactical DDD patterns can theoretically be applied to any service where data and state are modified, I don’t recommend using them as a default. The entry cost can be too high, especially in simpler domains.


When should you use them?

Here are some scenarios where DDD patterns shine:

Ubiquitous Language: Always. After years in the software industry, I have yet to see a situation where shared domain understanding doesn’t add value. It clarifies boundaries and constraints and makes every business conversation more effective.

Strategic Patterns:

  • At project start: To define module and service boundaries.

  • During disruptive changes: To reassess and possibly redefine existing boundaries.

  • When introducing complex processes: To ensure clarity and consistency in modeling.

  • When planning modernization: To set a clear target structure and roadmap.

Tactical Patterns:

  • In the core domain: Apply these where your system delivers the most value, and where changes are most likely.

  • During refactoring-based modernization: While refactoring isn't always the best strategy for legacy code, when it is, tactical DDD patterns help establish a rich domain model. This improves understanding and makes further changes easier.


Conclusion

Domain-Driven Design offers a powerful toolkit of patterns for modeling business logic, structuring services, and encapsulating complexity. Most importantly, DDD significantly narrows the knowledge gap between the technical team and domain experts, enabling better conversations and more valuable feedback loops.

No comments:

Post a Comment