Domain-Driven Design (DDD) is an approach to designing modular software systems by managing the complexities of business domains. The core idea is that a business domain’s strategy and needs should be the driving forces behind the system’s design decisions. For the design to reflect business strategy and needs, you must first acquire that knowledge.
Domain Analysis
DDD provides a set of tools for exploring business domains. It all starts with domain analysis and the identification of subdomains. Subdomains are cohesive units of functionality needed to succeed in the overarching business domain. The goal is not only to identify the business building blocks but also to evaluate their strategic importance. Subdomains are categorized into three types:
- Core subdomains are the company’s “secret sauce”–the unique capabilities that provide its competitive advantage.
- Generic subdomains are solved problems: functionalities already implemented and offered by third-party products and services.
- Supporting subdomains do not have ready-made implementations, but unlike core subdomains, they have low strategic value.
Once subdomain boundaries are identified, a deeper understanding of the business domain and the intended system behavior is needed. This is achieved through the practice of ubiquitous language: forming a shared understanding and a shared language between software engineers and business domain experts.
Strategic Design
Traditionally, software systems were designed around a single model of the business. This model aimed to cover the entire domain and enable the implementation of all possible functionalities. However, such all-encompassing models quickly became huge, unmanageable, and complex–defying their original purpose.
DDD advocates a different approach: working with multiple models, each designed to address a specific aspect of business functionality. Models are driven by semantic conflicts in potential ubiquitous languages. To limit the applicability of each model, a clear boundary is required: its bounded context.
A bounded context is an explicit area where a model can be applied. In DDD, it serves as a boundary for a model, the project/service it is implemented in, and its ownership–the team responsible for its evolution and maintenance. These are strategic design decisions, shaping the system’s coarse-grained components and the allocation of engineering teams’ responsibilities.
Although bounded contexts encapsulate models and aim to reduce dependencies among them, they are not completely independent and have to interact with each other. To that end, DDD describes a set of integration patterns suitable for different levels of collaboration and communication among teams. These include shared kernel, partnership, conformist, anti-corruption layer, open-host service, and separate ways. These patterns differ in their cross-team collaboration levels and types of communication interfaces, ranging from ad-hoc to formal integration approaches.
Once the required models, their boundaries, and the appropriate integration patterns are established, the next step is implementing the models’ functionality. This is the tactical design aspect of DDD.
Tactical Design
A number of business logic implementation patterns can be used depending on the complexity of the models:
- Transaction script and active record patterns are suitable for relatively simple functionalities. These are commonly found in supporting subdomains and for integrating solutions for generic subdomains.
- Domain model and event-sourced domain model are used for implementing more intricate functionalities–those belonging to core subdomains. These patterns rely on finer-grained sub-patterns to represent model components, such as aggregate, value object, domain event, domain service, repository, and others.
Business logic implementation patterns influence the choice of an application architecture pattern: layered architecture, ports-and-adapters (hexagonal architecture), or CQRS (Command-Query Responsibility Segregation). These patterns define how a bounded context’s internal components are structured and integrated.
From Domain to Design
The domain-driven design methodology provides a comprehensive set of patterns and practices for acquiring domain knowledge and using it as the foundation for strategic and tactical design decisions. All these tools have something in common: coupling.
DDD and Balanced Coupling
Many of the discussed patterns and practices directly relate to the dimensions of coupling:
- Bounded contexts serve as physical boundaries (medium distance, shared lifecycle) for components that share a model of the business domain (model coupling).
- Bounded context integration patterns determine the level of knowledge sharing. Some allow more knowledge exchange (ad-hoc), while others are more formal integration methods. This reflects the communication and collaboration dynamics of engineering teams (the organizational aspect of distance).
- Some integration patterns encapsulate implementation models, relying on integration-specific models (contract coupling) when high distance exists–both in terms of abstraction levels and organizational factors.
- The aggregate and value object patterns co-locate(low distance) behaviors sharing transactional boundaries (high degree of functional coupling)
- Supporting subdomains are where DDD allows for pragmatism (“Not all of a large system will be well-designed” — Eric Evans) due to their low volatility.
Ultimately, in the balanced coupling model, DDD’s subdomains are often used to evaluate the volatility levels of software components.
Learn More
- Balancing Coupling in Software Design, Chapter 7: Integration Strength
- Balancing Coupling in Software Design, Chapter 9: Volatility
- Balancing Coupling in Software Design, Chapter 11: Rebalancing Coupling
- Balancing Coupling in Software Design, Chapter 13: Balanced Coupling in Practice
- Learning Domain-Driven Design
- Implementing Domain-Driven Design
- Domain-Driven Design: Tackling Complexity in the Heart of Software