DDD, Microservices and Evolutionary Architectures: Strategic Patterns
Talking about DDD and architectures, in the previous article we explored the distinction between the problem space and the solution space.
At the start of a project, microservices can be too costly. Refactoring an existing system can also prove complex and expensive. On the other hand, a monolithic approach may hinder scalability as the system grows and requirements evolve.
The modular architecture seems to offer the best of both worlds, without the most common drawbacks.
Before diving into how to adopt it, let’s clarify what we mean by a module—and why this concept aligns well with Domain-Driven Design.
Module
A module encapsulates a set of objects that address a well-defined business concept. These objects are highly cohesive and work closely together. A high level of internal coupling is not a problem: we will never separate those objects, as they form a coherent, self-contained functional unit.
The critical point is to keep modules independent from each other. Each module solves a different problem, even if it’s part of the same system. This requires low coupling between modules. Only then will we be able to extract them as microservices in the future without having to rethink the entire system.
This is the strength of modularity: it prepares us for evolution without forcing us to make all the decisions upfront.
Modular Architecture and Domain-Driven Design
At this point, it’s natural to ask how this architecture ties into DDD. Let’s pause for a moment and look at the patterns involved.
Eric Evans, in his iconic book “Domain-Driven Design: Tackling Complexity in the Heart of Software“—commonly known as the Blue Book—introduces several patterns designed to support business-oriented software development, rather than simply focusing on data. He groups them into two main categories: Strategic Patterns and Tactical Patterns.
Both offer valuable tools in different contexts. Strategic patterns help us build the big picture of the system by dividing the overall domain into multiple distinct subdomains.
Business Domain & Ubiquitous Language
Let’s start with an important clarification: the domain is not necessarily something tied to software. In fact, it often isn’t. The domain represents a company’s core business activity. It’s the service it provides—the very thing that sets it apart from competitors in the market.
Take Uber, for example. Its domain is providing a private transportation service that’s easy to use for both drivers and passengers. The domain itself is not a software problem. But thanks to a well-designed app, we experience its real-world value.
According to the Domain-Driven Design definition, the domain is the sphere of knowledge and activity around which the application’s logic is centered.
Software and Business: How to Speak the Same Language?
Business and software speak two different languages. So how can they communicate without creating confusion or delivering the wrong solutions? Eric Evans’ idea is to create a common model—a bridge between developers and stakeholders, built on a clear and shared language. A model is not a perfect replica of the real world. It’s a tool to understand and tackle a complex domain. The closer the model is to reality, the more effective and targeted the resulting decisions will be.
To build this shared model, we need precise language—unambiguous and clear to everyone: developers, clients, and users. This shared language is the first strategic pattern introduced in the Blue Book: it’s called ubiquitous language.
Ubiquitous language
During software development, the language used by domain experts is often “translated” into technical terms to be more understandable for developers. This translation happens in requirements documents, which rarely capture the true nature of the business problem. Each time a term moves from one context to another, it loses important nuances—not only semantic but also behavioral. The intention is good, of course. But the result is a model impoverished of reality, leading to incomplete or, worse, completely wrong solutions.
Finding a common language that eliminates ambiguities is essential. It helps align everyone on what we mean, for example, by coffee. Why specifically coffee? Because, as Italians, when we order it abroad, we know something will go wrong. The barista understands the word but not the meaning we give it. We expect a short, strong espresso served in a small cup. Instead, we receive a long, watered-down drink that only has the color of our coffee. The meaning is lost in translation.
That’s why having a shared dictionary is not enough. We need a true language, as rigorous as technical jargon but understandable to everyone. When we say we need to stay in the problem space as long as possible, this is exactly what we mean: building a clear, shared language. Only this way can everyone involved in the solution truly understand the problem and describe it without ambiguity.
But then, is learning the Domain language enough? No, unfortunately, it is not.
Domain language vs ubiquitous language
In many interpretations of Domain-Driven Design, the domain language is often confused with the ubiquitous language. But there is an important difference.
The domain language is the natural language used every day by those working within the domain. It is spontaneous, often imprecise, full of implicit meanings.
As Giorgio Gaber used to say:
“Words define the world; if there were no words, we would have no way to talk about anything. But the world keeps turning, and words stay still, they wear out, age, lose meaning, lose sense, and all of us keep using them without realizing we’re talking about nothing.”
We may not remember Gaber for his contribution to software, but this phrase clearly speaks to us developers as well. In our work, using worn-out and ambiguous words is a huge problem. We cannot afford it.
Unlike natural language, the ubiquitous language is born with a specific purpose: to eliminate ambiguity and make the domain logic explicit. It is a language built together by stakeholders and designers to support system design. It must be clear, shared, consistent, and precise: every term has only one meaning. It is not only used to describe properties but also the behaviors of the domain. Ambiguity hinders communication, creates misunderstandings, and leads to errors. It must eliminate the need for assumptions and make the business domain logic explicit. For this reason, every word must be carefully chosen. Once it is clear what the ubiquitous language is, we can put it into practice.
Rebecca Wirfs-Brock says:
“A model is a simplified representation of a thing or phenomenon that intentionally emphasizes certain aspects while ignoring others. Abstractions with a specific use in mind.”
George Box adds:
“All models are wrong, but some are useful.”
There is no universal model or universal language for the entire domain. There are only languages suitable for a specific business problem. It is like maps: many exist, each useful for a different need.

It is evident that none of the available maps can represent the entire planet Earth. On the contrary, each of them contains only the details strictly necessary for the purpose it was designed for. Consequently, for each map, we use a specific language suitable for the context and the problem to solve.
We need to build abstract models of reality to manage complexity. Abstraction allows us to omit what is irrelevant and focus only on the significant elements.
But what does all this have to do with the ubiquitous language? When we define a language suitable for the domain of the problem we want to solve, we are creating a conceptual model. This model reflects a subdomain of the company. The goal is to capture the mental models and processes of domain experts to translate them into software features.
The language, and therefore the model, must faithfully represent the business entities involved, their behaviors, mutual relationships, cause-effect links, and invariants. We will explore these aspects further later on.
Another essential point is evolution. Just as Gaber reminded us, the ubiquitous language changes over time. The reasons are many: the market transforms, needs evolve, and the meanings of terms adapt to the present. For this reason, it is crucial to maintain a constant dialogue with domain experts and avoid assumptions.
Consolidating the language means reducing technical debt related to domain knowledge. It also means building solutions that better fit the business reality. Once a shared language is defined, it must be adopted everywhere: in documentation, in code, and in every project artifact. Only this way can ambiguities and misunderstandings be avoided.
When the common domain language has been defined, we can divide it into smaller languages, each linked to a specific business context. In this way, the Bounded Contexts emerge, or if you prefer, the modules.
How do we identify these boundaries? By looking for points where the same term assumes different meanings in different contexts. Take the example of an article: for logistics, location, stock, and reorder points matter. For the sales department, price, margin, and delivery times are important. We are talking about the same object, but from two distinct Bounded Contexts.
Bounded Context
We have already said it: the model we use to develop a system is not a copy of reality. It is a useful construct to give meaning to a complex context. It is important to clarify: this model will not be valid forever, for the entire lifetime of the system.
Requirements will change. New demands will come from the market. No one, at the start of the project, can foresee them. We developers cannot either, even relying “on our experience,” because every project has its own story. Neither can the client commissioning the software.
Adding properties to a model just “because they might be needed sooner or later” is a bad habit. It is widespread among software writers and, paradoxically, the more senior we are, the more we feel entitled to do it.
No. All good development practices teach us a fundamental principle: build only what is necessary to solve the current problem. Everything beyond that is unnecessary coupling to a wrong model. And sooner or later, we will pay dearly for it.
So? We have to accept it: the universal model, the silver bullet, does not exist. Every model needs precise boundaries. Beyond that limit, its representation of reality loses validity.
For those who love philosophy, this concept recalls the platonic fold:
“The platonic fold is the explosive boundary where the platonic model meets the messy reality, where the gap between what is known and what one thinks is known dangerously widens.”
Bounded Contexts define the perimeter within which a subset of our ubiquitous language and model makes sense. They allow building distinct models according to different problem domains.
Terminology, rules, and concepts expressed within a language have value only in the context in which they were defined. This is exactly what happens in the article example mentioned earlier.
Bounded Context vs Subdomains
It is important to clarify the distinction between Bounded Context and subdomain. At first glance, they may seem equivalent, but they exist on different levels: one concerns the business, the other the system design.
To understand a company’s strategy, it is necessary to analyze its domain. As expected by Domain-Driven Design patterns, this analysis leads to the identification of multiple subdomains. This is how we discover how the organization operates and plans its actions in the market.
Bounded Contexts represent, in the software system, the outcome of this analysis. They are designed by us developers and reflect strategic choices, which is why they belong to the strategic patterns of DDD. Their function is to divide the domain model into simpler components to manage.
Whether each subdomain corresponds to a single Bounded Context or more depends on the complexity involved. A one-to-one mapping can be entirely appropriate in certain scenarios. In others, more elaborate decomposition strategies may be required.
In software design, there are no absolute rules. Every decision is a trade-off between benefits and costs. It is up to us to evaluate the best strategy for the specific context. What matters is not only how we design a solution, but above all why we adopt one choice rather than another.

Types of Subdomains
Designing a software system with a business-oriented approach requires a clear distinction between the types of subdomains that emerge during the analysis. Just like in the departments of a company, not all subdomains have the same strategic weight. It depends on the nature of the business. This differentiation must also be reflected in the software design.
In Domain-Driven Design, subdomains are classified into three main categories:
Core subdomains
These are the subdomains that distinguish the company from its competitors. This is where true innovation and competitive advantage are concentrated. For this reason, they represent the most strategic area also for software development.
The best resources are invested in these subdomains: expert teams, advanced development practices, systematic use of patterns. The reason is simple: an easy-to-build core subdomain can offer only a temporary advantage. To be truly effective, it must tackle and solve complex problems.
Generic subdomains
This category includes subdomains common to all companies, which do not generate any competitive advantage. Typical examples are authentication or authorization functionalities. In these cases, it is often convenient to rely on already proven external solutions rather than developing custom components.
Supporting subdomains
As the name suggests, these subdomains serve a supporting role for the core business. They do not provide direct strategic advantages but are essential for the proper functioning of the entire system.
Often, there are no off-the-shelf solutions to meet the specific needs of these areas. Consequently, they are generally developed in-house, although they remain secondary compared to the core subdomains.
Integration patterns
We have seen how dividing a business model into subdomains allows us to obtain different models that can evolve independently from one another, regardless of whether a single team or multiple teams work on them. This property of Bounded Contexts is what has made them a pattern for defining the scope of a microservice.
That said, Bounded Contexts are not independent by themselves. Just as a system is not simply the sum of its components, but the way they are assembled and interact with each other, likewise our software is not just the collection of identified Bounded Contexts, but the way these communicate and exchange information with each other.
Consequently, there will always be points of contact between one Bounded Context and another, and these points of contact — just like in real life — are governed by contracts.
The reason why we need to define contracts to enable different Bounded Contexts to communicate is simple: each Bounded Context has its own specific ubiquitous language, which is not valid for the others; otherwise, we wouldn’t have separated them.
When integration of information is necessary, it’s crucial to decide which language to adopt for the integration itself. In Domain-Driven Design, this issue is addressed by the strategic pattern Context Mapping, which divides types of communication into three groups that represent the kind of collaboration involved: cooperation, customer-supplier, separate ways.
Cooperation
Cooperation refers to Bounded Contexts that have a close and ongoing relationship. The success of one directly depends on the success of the other, and vice versa. There is stable and shared communication, based on common goals.
In this scenario, the involved teams work closely together and jointly agree on integration rules. Several patterns fall into this group, which we will explore in the following sections.
Partnership
In this model, integration is handled in an ad hoc manner. One team notifies the other of changes made to its model, and the second team adapts without conflicts to the new changes. Integration happens in both directions: no team or Bounded Context imposes the rules, but each party works independently while maintaining a smooth flow of information.
This type of collaboration requires continuous synchronization and a high level of communication. Therefore, it is more effective when teams are geographically close, facilitating quick dialogue and alignment.
Shared kernel
When a part of a subdomain is shared among multiple subdomains, it can be useful to create a shared model across the different Bounded Contexts. However, it is essential that the shared model remains consistent in all involved contexts. This represents coupling between contexts, and as always, the pros and cons must be carefully evaluated before proceeding.
There are some guidelines that can help in making this decision. Here is a brief list of questions to consider:
- Will consuming the shared model lead to loss of capabilities?
- Will consumers gain new capabilities?
- Will they be able to focus more on their strategic choices?
- Will dependence on the shared model slow down the process?
- Will consumers have the option to refuse migration to the shared model?
- Will the development team of the shared model be sufficiently responsive?
- Will the number of dependent models become problematic?
- Will the migration cost be too high?
As always, there are no universal laws: the choice depends on the specific context.
Customer-supplier
The second group of collaboration patterns is the customer-supplier. As shown in the image, in this model one of the Bounded Contexts (the supplier) provides a service to another Bounded Context (the customer).
Unlike the cooperation pattern, where the success of both teams is interdependent, in this case the upstream and downstream teams can succeed independently from each other. This often leads to a power imbalance, where one of the teams (usually the supplier) can dictate the rules for the integration contract.
The following patterns belong to this group.
Conformist
In some cases, the upstream team has no motivation to support the downstream teams and simply provides an integration contract that others must accept as-is (take it or leave it). This happens when integrating with an external service over which we have no negotiating power.
If the downstream team is forced to accept the integration contract as it is, the relationship between the two teams is called conformist.
Anticorruption Layer
In the Anticorruption Layer pattern, the upstream team still imposes the integration contract rules, but unlike the conformist pattern, the downstream team cannot accept the contract for various reasons: frequent changes, inefficiency in managing the contract, or the need to protect its own Bounded Context.
The Anticorruption Layer acts as an intermediary between the external contract and our Bounded Context, translating and adapting external rules to ensure the integrity and independence of the internal context.
Open-Host Service
In the Open-Host Service pattern, decision-making power is skewed towards the upstream team, but unlike the previous pattern, it is the upstream team that protects its consumers by separating the internal implementation of the contract from the external one. This decoupling allows the upstream Bounded Context to evolve its implementation model without impacting the external integration.
In some cases, it may be necessary to maintain multiple versions of the external contract to allow consumers to adapt to new specifications. In this situation, the upstream Bounded Context can expose multiple versions of the integration contract. A classic example is the exposure of multiple versions of REST APIs by a Bounded Context.
Separate Ways
There is a third type of collaboration that involves no interaction at all. Let’s look at the cases that fall into this group.
Communication Issues
When teams struggle to communicate or collaborate, sometimes due to internal organizational politics, it may be preferable for each to go its own way. In these cases, duplicating some functionalities across multiple bounded contexts can be more effective.
Generic Subdomains
Some functionalities are easier to duplicate than to integrate, especially within generic subdomains, which are typically easy to develop or purchase and integrate. A common example is logging.
Model Differences
When the differences between the models of different bounded contexts are too significant, duplicating functionalities may be more convenient than investing time in integration. However, this approach is not advisable for core subdomains, as duplicating functionalities at this level could conflict with the company strategy, which aims to optimize processes by focusing its main efforts precisely on these subdomains.
Context Map
Context mapping is a visual representation of the bounded contexts and their relationships, providing valuable strategic insights. Specifically, it offers an overview of the implemented components and models (High-level design), but also a view of the collaboration styles among teams, highlighting those that work closely together and those that prefer to follow independent paths (Communication patterns). Additionally, it helps identify organizational issues—for example, when all collaboration relationships are of the Separate Ways or Anticorruption Layer type.
Conclusions
Starting from modular architecture, we have seen how Domain-Driven Design supports the strategic aspect of software development.
Although the application of these patterns is often associated with building microservices systems, as we have observed, it is by no means a requirement. As I often repeat, in 2003—the year Eric Evans’ book was published—microservices were not even a concept, and the main architecture was the layered architecture. Nevertheless, all the patterns we explored are applicable in any context. For a practical example of these concepts in action, you can check out this project.
My personal treatise on Domain-Driven Design continues in the next article with an in-depth look at evolutionary architectures.