The module opens with a statistic and a diagnosis. Approximately 70% of software projects are not delivered on time, on budget, or according to the client’s requirements — that is, the vast majority of software projects fail.
The term software crisis was introduced in 1968. In the fifty years since, numerous approaches, methodologies and disciplines have been proposed to make software engineering more effective — most recently the Agile Manifesto, extreme programming, test-driven development, high-level languages, DevOps and others. But projects are still failing quite often, and the software crisis is still here.
Research into why software projects fail has shown that effective communication is essential for knowledge sharing and project success. The mechanism of failure is precise:
During the traditional software development lifecycle, the domain knowledge is “translated” into an engineer-friendly form known as an analysis model — which is a description of the system’s requirements rather than an understanding of the business domain behind it. And in any translation, information is lost.
Effect: failure to grasp the business domain → suboptimal implementation of the business software.
Investigating the reasons for common project failures reveals a common theme: communication issues — unclear requirements, uncertain project goals, or ineffective coordination of effort between teams.
Domain-Driven Design was introduced in 2004 by Eric Evans and has been increasingly adopted and further developed by a growing community. The central theme of its tools and practice is exactly this: effective communication.
From the Domain-Driven Design Reference [DDD-Ref], the whole method in three clauses:
Domain-Driven Design is an approach to the development of complex software in which we:
Every keyword in this chapter comes from those three lines: domain, model, domain model, ubiquitous language, bounded context. Chapter 7 then takes the fourth word — models — and asks how to build them in code.
| Term | Definition |
|---|---|
| Domain | A sphere of knowledge, influence or activity. Every software program relates to some activity or interest of its user; the subject area to which the user applies a program is the domain of the software. |
| Model | A system of abstractions that describes selected aspects of a domain and can be used to solve problems related to that domain. A model is a simplification, an interpretation of reality that abstracts the aspects relevant to solving the problem at hand and ignores extraneous details. |
| Domain model | A rigorous, organised and selective abstraction of the domain knowledge. |
| Context | The setting in which a word or statement appears that determines its meaning. Statements about a model can only be understood in a context. |
| Bounded context | A description of a boundary — typically a subsystem, or the work of a particular team — within which a particular model is defined and applicable. |
| Ubiquitous language | A language structured around the domain model and used by all team members within a bounded context, to connect all the activities of the team with the software. |
Why models are needed at all: to create software that is valuably involved in users’ activities, a development team must bring to bear a body of knowledge related to those activities. The breadth of knowledge required can be daunting, and the volume and complexity of information can be overwhelming — models are tools for grappling with this overload. A model is a selectively simplified and consciously structured form of knowledge.
The ubiquitous language, in Evans’ image, is cultivated in the intersection of the jargons. Developers do use technical terminology a domain expert would not understand, and users have specialised jargon that goes well beyond the application. But these are extensions to the language: these dialects should not contain alternative vocabularies for the same domain that reflect distinct models. With a ubiquitous language, conversations among developers, discussions among domain experts, and expressions in the code itself are all based on the same language, derived from a shared domain model.
To align software design with its business domain, DDD distinguishes two levels.
| Strategic design — the what and why | Tactical design — the how | |
|---|---|---|
| Activity | Analyse business domains and strategy, and foster a shared understanding of the business between the different stakeholders | Write code in a way that reflects the business domain, addressing its goals and speaking the language of the business |
| Output | High-level design decisions driven by knowledge of the business domain; decomposing systems into components and defining their integration patterns | The implementation: entities, value objects, aggregates, domain events, services, factories, repositories |
| Space | Problem space | Solution space |
| Covered in | This chapter | Chapter 7 |
The module names the roles precisely, and the distinction between a stakeholder and a domain expert is examinable:
Defining use cases is a process involving interaction with business users (stakeholders) to understand what users would do with the system. It elicits domain terminology, which is the starting point of the shared language, to be discussed then with domain experts to validate our understanding.
A use case can be conceived as a set of scenarios tied together by a common user goal, where a scenario is a sequence of steps describing an interaction between a user and “the system”. So a use case lists the steps required to achieve a goal from the user’s point of view, including the interactions between users and systems: it provides a narrative of how the system is used. The reference book is Alistair Cockburn’s Writing Effective Use Cases (2000); in UML, a use case diagram is a graphical table of contents for the use case set, showing the actors, the use cases and their relationships — which actors carry out which use cases, and which use cases include others.
The domain model is the backbone of the ubiquitous language. It captures the domain experts’ mental models — their thought processes about how the business works to implement its function — reflecting the involved business entities and their behaviour, cause-and-effect relationships, and invariants, and including just enough aspects of the business domain to make it possible to implement the required system.
The ubiquitous language becomes the “one language” to be practised and used by the team in all communications, design and programming activities. Even the code should speak the ubiquitous language. With a ubiquitous language the model is not just a design artefact: it becomes integral to everything the developers and domain experts do together.
The cultivation of a ubiquitous language is an ongoing process: it should be constantly validated and evolved, and continuously reinforced throughout the project. Requirements, tests, documentation and even the source code itself should use this language.
Notice the through-line back to Chapter 1: “contains only the details needed to fulfil its purpose” is the principle of abstraction; “constantly validated and evolved” is incrementality; “the code should speak the language” is the fundamental application of separation of concerns — separating problem-domain concerns from implementation concerns. DDD is not a new set of principles, it is a methodology that operationalises the old ones.
Large problem domains can be partitioned into subdomains, to manage complexity and to separate the important parts from the rest of the system. A subdomain is a fine-grained area of business activity; all of a company’s subdomains form its business domain, the service it provides to its customers.
The running example is an online auction site, distilled into: Membership, Seller, Auction, Listing, Dispute Resolution. DDD then distinguishes three types of subdomain.
| Type | Definition | Notes |
|---|---|---|
| Core | What a company does differently from its competitors — inventing new products or services, or reducing costs by optimising existing processes; the activities from which it gains its competitive advantage | Core subdomains are naturally complex to implement: subdomains that are simple to implement can only provide a short-lived competitive advantage |
| Generic | Business activities that all companies perform in the same way | Generally complex and hard to implement, yet providing no competitive edge. There is no room or need for innovation, so rather than in-house implementations it is more cost-effective to use existing solutions |
| Supporting | Business activities that support the company’s business, which it has to implement in-house but which provide no competitive advantage | Distinctive characteristic: the low complexity of the solution’s business logic |
The summary table the module gives:
| Subdomain type | Competitive advantage | Complexity | Volatility | Implementation | Problem |
|---|---|---|---|---|---|
| Core | Yes | High | High | In-house | Interesting |
| Generic | No | High | Low | Buy / adopt | Solved |
| Supporting | No | Low | Low | In-house / outsource | Obvious |
In the auction example, Seller and Auction are the core domains; Membership and Listing are supporting; Dispute Resolution is generic — it can be served by a commercial off-the-shelf package, being merely a ticketing system to handle customer dispute cases. The consequence for the solution: your best developers focus their attention on the core, models within the supporting domains need only be good enough, and the generic domain is fulfilled by an off-the-shelf commercial application.
From a technical perspective, subdomains resemble sets of interrelated, coherent use cases — involving the same actor and the same business entities, and all manipulating a closely related set of data. This definition doubles as a stopping rule: the definition of subdomains as a set of coherent use cases can be used as a guiding principle for when to stop looking for finer-grained subdomains.
How far to distil? It is definitely necessary for core subdomains, which are the most important, volatile and complex: distilling them as much as possible lets us extract all generic and supporting functionalities and invest the effort on a much more focused functionality. Distillation can be relaxed for supporting and generic subdomains: if drilling down further does not unveil new insights that help make software design decisions, that is a good place to stop — which happens, for example, when all the finer-grained subdomains are of the same type as the original one.
The ubiquitous language is meant to drive software design decisions, so the language must be clear and consistent, free of ambiguity, implicit assumptions and extraneous details. However the real world is complex: on an organisational scale, the domain experts’ mental models can be inconsistent among themselves, and different domain experts can use different models of the same business domain.
The term lead has different meanings in the marketing department and in the sales department.
The DDD solution: divide the ubiquitous language into multiple smaller languages, then assign each one to the explicit context in which it can be applied — its bounded context. The contexts are modelled as an explicit and integral part of the business domain.
Bounded contexts therefore:
This distinction is asked constantly, and the one-liner to remember is: subdomains are discovered, bounded contexts are designed.
One legitimate design is to have a bounded context for each subdomain — bounded contexts aligned with subdomain boundaries. Another is to let a context span several subdomains, driven by the consistency of the ubiquitous language. If the models are still large and hard to maintain, we can decompose them into even smaller bounded contexts. Either way, it is a design decision.
| Kind of boundary | Rule |
|---|---|
| Physical | Each bounded context should be implemented as an individual service or project: it is implemented, evolved and versioned independently of other bounded contexts. |
| Ownership | A bounded context should be implemented, evolved and maintained by one team only. No two teams can work on the same bounded context. The relationship is one-directional: a bounded context is owned by exactly one team, but a single team can own multiple bounded contexts. |
The rationale for the ownership rule is precise: this segregation eliminates implicit assumptions that teams might make about one another’s models. Instead, they have to define communication protocols for integrating their models and systems explicitly.
Bounded contexts are not independent. Systems in general cannot be built out of independent components: the components have to interact with one another to achieve the system’s overarching goals. Although contexts can evolve independently, they have to integrate with one another. The touch points between bounded contexts designed to realise such integration are called contracts.
DDD organises the integration patterns into three families, by the kind of relationship between the teams.
Family: cooperation — for bounded contexts implemented by teams with well-established communication.
In the partnership model, the integration between bounded contexts is coordinated in an ad hoc manner: one team can notify a second team about a change in the interface or API, and the second team will cooperate and adapt — no drama or conflicts. The coordination of integration is two-way: no one team dictates the language used for defining the contracts; the teams work out the differences and choose the most appropriate solution, and both sides cooperate in solving any integration issues that come up. Neither team is interested in blocking the other.
What it requires: well-established collaboration practices, high levels of commitment, and frequent synchronisations between teams. At the technical level, continuous integration of the changes applied by both teams is needed, to further minimise the integration feedback loop.
Caveat: it may not be a good fit for geographically distributed teams, since it presents synchronisation and communication challenges.
Family: cooperation.
Used when the same model of a subdomain, or part of it, needs to be implemented in multiple bounded contexts. The overlapping model couples the lifecycles of the participating bounded contexts: a change made to the shared model has an immediate effect on all of them.
Note the honesty of the presentation: this contradicts the principles of bounded contexts in general, and is to be used only when strictly necessary due to pragmatic issues — communication or collaboration difficulties caused by geographical constraints or organisational politics, the gradual modernisation of a legacy system, and similar.
Family: customer–supplier — a one-direction relationship with two roles: a supplier providing a service for its customers. The service provider is upstream, the customer or consumer is downstream. Unlike the cooperation case, both teams can succeed independently, so in most cases there is an imbalance of power: either the upstream or the downstream team can dictate the integration contract.
In the conformist pattern, the balance of power favours the upstream team, which has no real motivation to support or adapt to its clients’ needs: it just provides the integration contract, defined according to its own model — take it or leave it. Such power imbalances can be caused by integration with service providers external to the organisation, or simply by organisational politics.
If the downstream team accepts the upstream team’s model, the relationship is called conformist: it is the downstream customer that gives up some of its autonomy to conform to the supplier. There can be good motivations — for instance the contract exposed by the upstream team may be an industry standard.
Family: customer–supplier. The balance of power is still skewed toward the upstream service.
Contrary to the conformist case, here the downstream bounded context is not willing to conform. Instead, it translates the upstream bounded context’s model into a model tailored to its own needs, via an anticorruption layer.
The translation of the supplier’s model isolates the downstream consumer from foreign concepts not relevant to its bounded context, thereby simplifying the consumer’s ubiquitous language and model.
When it is the right choice:
Family: customer–supplier, but with the power skewed toward the consumers.
Here the supplier is interested in protecting its consumers and providing the best service possible. To protect them from changes in its implementation model, the upstream supplier decouples the implementation model from the public interface. This decoupling allows the supplier to evolve its implementation and public models at different rates.
The supplier’s public interface is not intended to conform to its own ubiquitous language: it is intended to expose a protocol convenient for the consumers, expressed in an integration-oriented language. As such, the public protocol is called the published language.
In a sense the open-host service is the reversal of the anticorruption layer: instead of the consumer, the supplier implements the translation of its internal model.
Versioning: the integration model’s decoupling allows the upstream bounded context to simultaneously expose multiple versions of the published language, letting consumers migrate to the new version gradually.
Family: separate ways — almost no cooperation.
The last collaboration option is not to collaborate at all. This pattern can arise for different reasons, in cases where the teams are unwilling or unable to collaborate; a common reason is communication difficulties driven by the organisation’s size or internal politics.
When teams have a hard time collaborating and agreeing, it may be more cost-effective to go their separate ways and duplicate functionality in multiple bounded contexts.
Note the boundary of the advice: duplicating the implementation of core subdomains would defy the company’s strategy to implement them in the most effective and optimised way — so separate ways is not a licence to duplicate the parts that give competitive advantage.
After analysing the integration patterns between a system’s bounded contexts, we can plot them on a context map: a visual representation of the system’s bounded contexts and the integrations between them.
This visual notation gives valuable strategic insight on multiple levels:
A context map should be introduced into a project right from the get-go, and updated to reflect additions of new bounded contexts and modifications to existing ones. It can be managed and maintained as code, using a tool such as Context Mapper.
A frequent question asks you to choose an integration pattern from a short scenario. The decision tree the module implies: (1) can the two teams genuinely cooperate? If yes → partnership; if they must literally share a model → shared kernel, but only under duress. (2) If not, who holds the power? Upstream holds it and the downstream can live with the model → conformist; upstream holds it but the downstream has a core subdomain, or the upstream model is a mess or changes often → anticorruption layer; the downstream consumers hold it and the supplier wants to serve them well → open-host service with a published language. (3) If neither cooperation nor integration is worth it → separate ways, duplicating functionality — but never a core subdomain.
Domain knowledge is translated into an engineer-friendly form known as an analysis model, which is a description of the system’s requirements rather than an understanding of the business domain behind it — and in any translation, information is lost. The consequence is failure to grasp the business domain, and therefore suboptimal implementation of the business software.
Domain-Driven Design is an approach to the development of complex software in which we: (1) focus on the core domain; (2) explore models in a creative collaboration of domain practitioners and software practitioners; (3) speak a ubiquitous language within an explicit bounded context.
Domain: a sphere of knowledge, influence or activity — the subject area to which the user applies a program. Model: a system of abstractions that describes selected aspects of a domain and can be used to solve problems related to it; a simplification that abstracts the relevant aspects and ignores extraneous details. Domain model: a rigorous, organised and selective abstraction of the domain knowledge.
A stakeholder tells you what they want the system to do, focusing on the inputs and outputs and communicating business goals; the team captures these as business use cases. A domain expert works with the development team to produce a useful model that can satisfy the needs of a stakeholder and the behaviours of the application. The joint activity is knowledge crunching, and its output is a useful model, simplified as understanding of the problem domain grows.
Consistency — precise and consistent, eliminating the need for assumptions and making the business domain’s logic explicit. And effectiveness as a model of the business domain — containing only the details needed to fulfil its purpose, handling complexity by omitting unnecessary details, i.e. abstraction. Its cultivation is an ongoing process: it must be constantly validated, evolved and reinforced in requirements, tests, documentation and source code.
Core: competitive advantage yes, complexity high, volatility high, implementation in-house, problem “interesting”. Generic: advantage no, complexity high, volatility low, implementation buy/adopt, problem “solved”. Supporting: advantage no, complexity low, volatility low, implementation in-house or outsource, problem “obvious”.
Complex, because a subdomain that is simple to implement can only provide a short-lived competitive advantage — competitors would replicate it immediately. Volatile, because if a problem can be solved on the first attempt it is probably not a good competitive advantage; consequently, solutions for core subdomains are emergent, and change often as the company learns.
Use the definition of a subdomain as a set of interrelated, coherent use cases — involving the same actor and business entities, all manipulating a closely related set of data — as the guiding principle. Distillation is definitely necessary for core subdomains; it can be relaxed for supporting and generic ones, and a good place to stop is when drilling down further does not unveil new insights that help make software design decisions, e.g. when all the finer-grained subdomains are of the same type as the original.
In marketing, a lead is a notification that somebody is interested in a product — the event of receiving the prospective customer’s contact details. In sales, a lead is a much more complex entity representing the entire lifecycle of the sales process: not an event but a long-running process. It demonstrates that on an organisational scale the domain experts’ mental models can be mutually inconsistent, which is why the ubiquitous language must be divided into smaller languages, each assigned to the explicit context where it applies.
Subdomains are discovered; bounded contexts are designed. Subdomains are identified in the business analysis phase, useful for understanding how the organisation works and planning competitive strategy — for the software engineer they arrive as requirements defined by the business. Bounded contexts are designed by software engineers, deciding how to divide the business domain into smaller manageable problem domains; choosing the models’ boundaries is a strategic design decision.
Physical: each bounded context should be implemented as an individual service or project, implemented, evolved and versioned independently. Ownership: a bounded context should be implemented, evolved and maintained by one team only — no two teams work on the same context, though a single team may own several. The rationale: the segregation eliminates implicit assumptions that teams might make about one another’s models, forcing them to define communication protocols explicitly.
Both belong to the cooperation family. In partnership, integration is coordinated ad hoc and two-way: one team notifies the other about an API change and the other adapts; no team dictates the contract; it requires high commitment, frequent synchronisation and continuous integration, and may not fit geographically distributed teams. In shared kernel, the same model of a subdomain is implemented in multiple contexts, coupling their lifecycles — this contradicts the principles of bounded contexts and should be used only when strictly necessary; the shared model must be designed for all participants, kept consistent, kept as small as possible, and every change must trigger integration tests in all affected contexts.
All three are customer–supplier, where the supplier is upstream and the consumer downstream, and both can succeed independently — hence an imbalance of power. Conformist: power favours the upstream, which offers its own model take-it-or-leave-it, and the downstream gives up autonomy and conforms. Anticorruption layer: the power is still upstream but the downstream refuses to conform and translates the upstream model into one tailored to its needs — appropriate when the downstream holds a core subdomain, when the upstream model is inefficient or a legacy mess, or when the supplier’s contract changes often. Open-host service: power is skewed toward the consumers, so the supplier decouples its implementation model from a public interface — the published language — that it can version, evolving the two at different rates. It is the reversal of the anticorruption layer: the supplier does the translating.
When teams are unwilling or unable to collaborate — commonly due to communication difficulties driven by organisation size or internal politics — it may be more cost-effective to go separate ways and duplicate functionality in multiple bounded contexts. What must not be duplicated: core subdomains, since duplicating them would defy the company’s strategy of implementing them in the most effective and optimised way.
High-level design (an overview of the system’s components and the models they implement), communication patterns (which teams collaborate and which prefer less intimate patterns such as anticorruption layer and separate ways) and organisational issues. It should be introduced right from the get-go and kept updated as contexts are added and modified; it can be managed as code with a tool such as Context Mapper.