In the beginning, you assume one indivisible business domain and start processing requirements to learn as much as possible about it and build the ubiquitous language. As you proceed, you learn how the organization works, which processes are performed, how data is used and, last but not least, you learn how things are referred to.
Especially in a large organization, the same term often has different meanings when used by different people, or different terms are used to mean the same thing. When this happens, you probably crossed the invisible boundaries of a subdomain. This probably means that the business domain you assumed to be one and indivisible is, in reality, articulated in subdomains.
In DDD, a subdomain in the problem space is mapped to a bounded context in the solution space.
A bounded context is an area of the application that requires its own ubiquitous language and its own architecture. Or, put another way, a bounded context is a boundary within which the ubiquitous language is consistent. A bounded context can have relationships to other bounded contexts.
Subdomains and bounded contexts are concepts that sometimes appear to be similar and can be confusing. However, both concepts can be easily understood by looking at the difference between a domain and domain model, which is probably easier to grasp. The domain represents the problem to solve; the domain model is the model that implements the solution to the problem. Likewise, a subdomain is a segment of the domain, and a bounded context is a segment of the solution.
Without flying too high conceptually, consider a simple booking system. The front-end web site is certainly a subdomain. Is it the only one? Most likely, the system needs a back-office panel to put content on the site and perhaps extract statistics. This probably makes for another subdomain.
In the current draft of the top-level architecture, we have two candidate bounded contexts.
There are two additional aspects vital to investigate: the boundaries of each bounded context and their relationships.
Marking boundaries of contexts
Sometimes it’s relatively easy to split a business domain into various subdomains, each representing a bounded context to render with software.
But is it splitting or is it partitioning? There is a huge difference between the two.
In the real world, you don’t often see business domains that can be easily partitioned in child domains with nearly no overlapping functions and concepts. So in our experience, it is more a case of splitting than just partitioning. The problems with splitting a business domain are related to marking the boundaries of each context, identifying areas of overlap, and deciding how to handle those areas.
As mentioned, the first concrete clue that you have a new subdomain is when you find a new term used to express a known concept or when the same term is found to have a second meaning. This indicates some overlapping between subdomains. (See Figure 5-3.)
FIGURE 5-3 Two business contexts with some overlapping.
The business domain is made of a subdomain (say, club site) that, among other features, offers the booking of courts. The booking of courts involves members and payments. The back office is a distinct but related subdomain. Both subdomains deal with members and payments, even though each has a different vision of them.
The first decision to be made is whether you need to treat those subdomains separately and, if so, where you draw the boundaries.
Splitting a domain into bounded contexts
Working on a single, all-encompassing model is always dangerous, and the level of complexity grows as the number of entities and their relationships grow. The resulting graph can be crowded; entities and related code can become quite coupled, and it doesn’t take much to serve up the perfect Big Ball of Mud.
Splitting is always a good idea, especially when this leads you to creating software subsystems that reflect the structure of the organization. The back-office system, for example, will be used by different people than the club site.
Let’s say you go for distinct bounded contexts.
How would you deal with overlapping logic? The concept of “club member” exists in both contexts, but in the back-office context the club member has nearly no behavior and is a mere container of personal and financial data. In the club-site context, on the other hand, the member has some specific behaviors because she can book a court or add herself to an existing booking. For doing so, the Member entity will just need an ID, user name, and possibly an email address.
In general, having a single, shared definition of an entity will have the side effect of padding the definition with details that might be unnecessary in some of the other contexts. With reference to Figure 5-3, family members are not necessary to book a court from the club site, but they are relevant to calculating the yearly fee.
The fundamental point to resolve when conceptual overlapping is detected is which of the following options is more appropriate:
- A single bounded context that includes all entities
- A distinct bounded context with a shared kernel of common entities
- A distinct bounded context with distinct definitions of common entities
The options are graphically summarized in Figure 5-4.
FIGURE 5-4 Resolving the conceptual overlapping of contexts.
There’s also a fourth option. Is the entire model entirely inadequate and in need of refinement so that in the end you can have partitions instead of subsets?
That’s what it means to mark the boundaries of bounded contexts.
By the way, it’s not us dodging the issue by not taking a clear stand on a particular option. It’s that, well, it just depends. It depends on other information about the domain. It depends on time and budget. It depends on skills. It also depends on your personal view of the domain.
That’s what makes it so fun to mark the boundaries of bounded contexts.
Bounded context and the organization
The number of contexts and relationships between bounded contexts often just reflect the physical organization of the enterprise. It is common to have a bounded context for each business department such as human resources, accounting, sales, inventory, and the like.
Different development teams are typically assigned to each bounded context, and different artifacts are generally produced, scheduled, and maintained.
The overlapping of concepts is quite natural in business domains; speaking in general, the best way to handle such overlapping is to use different bounded contexts, as shown in the third option of Figure 5-4.
Just sharing entities between development teams, as a common kernel, might prefigure risky scenarios, where changes of team 1 might break the code of team 2 and compromise the integrity of the model. Shared kernels work great if an effective shared kernel exists—such as different organizations just using the same entities.
Otherwise, it’s the first step toward a true mess.
Bounded contexts are often related to each other. In DDD, a context map is the diagram that provides a comprehensive view of the system being designed. In the diagram, each element represents a bounded context. The diagrams in Figure 5-4 are actually all examples of a context map.
Connections between elements of a context map depict the relationship existing between bounded contexts. DDD defines a few relational patterns.
Relational patterns identify an upstream context and downstream context. The upstream context (denoted with a u) is the context that influences the downstream and might force it to change. Denoted with d, the downstream context is passive and undergoes changes on the upstream context. Table 5-1 lists DDD relational patterns.
TABLE 5-1. DDD relational patterns
DDD relational pattern
Anticorruption layer (ACL)
Indicates an extra layer of code that hides to the downstream context any changes implemented at some point in the upstream context. More on this later.
The downstream context just passively conforms to whatever model the upstream context comes up with. Typically, the conformist pattern is a lighter approach than ACL and the downstream context also receives data it might not need.
Two contexts are in a classic upstream/downstream relationship, where the supplier is the upstream. The teams, however, work together to ensure that no unnecessary data is sent. This aspect marks the difference with Conformist.
Two contexts are developed independently; no code is shared, but both contexts are upstream and downstream at the same time. There’s a sort of mutual dependency between the two, and one can’t just ignore the other for delivery and change.
Two contexts share a subset of the model. Contexts are therefore tightly coupled, and no team can change the shared kernel without synchronizing with the other team.
Figure 5-5 is the graphical representation of a context map. Each block represents a bounded context. The Sales block is connected to the upstream External Service block, and an ACL ensures that changes in the service don’t force changes in the Sales context. The upstream and downstream contexts are labeled with the u and d marks.
FIGURE 5-5 A sample context map showing some of the DDD relational patterns.
Context mapping is part of the strategic design of the solution. It doesn’t produce code or deployable artifacts, but it can be immensely helpful to grab a better understanding of the system.
Many DDD experts advise that because software ultimately mirrors the structure of business organizations, a context map should ideally reflect the organization of the enterprise. Sometimes, it turns out that the ideal context map for the system to build doesn’t actually reflect the real organization. When this happens—and it does happen—well, things are not going to be easy!
Relationships between bounded contexts pose the problem of how the development of one context influences the other over time. The safest way of dealing with related contexts is by creating an anticorruption layer (ACL).
It’s the safest way because all the changes required to keep the contexts in sync when one undergoes changes are isolated in the anticorruption layer, as shown in Figure 5-6.
FIGURE 5-6 The anticorruption layer is an interfacing layer that separates two connected contexts.
The interface that the ACL exposes to the downstream context (the club site in this case) is an invariant. The ACL, in fact, absorbs the changes in the upstream context (Weather Forecasts service in this case) and does any conversion work that might be required. Updating the ACL when the upstream context changes usually requires less work and is less obtrusive than updating the club-site context.
The ACL is particularly welcome when one of the bounded contexts encapsulates a chunk of legacy code or just an external service that none of the teams building the system has control over.
Giving each context its own architecture
Each bounded context is a separate area of the overall application. You are forced to use DDD strategic modeling to implement each bounded context, and not only because you identified the bounded context using a DDD methodology. As an architect, you should validate the context map and then focus on each context separately.
For example, the Core Domain area of the application might be implemented using a Domain Model approach. The club-site context can be an ASP.NET MVC application with a layered back end that uses an application layer on top of MVC controllers. The application layer uses services in the Core Domain context for changing the state of the application. Finally, a simpler subsystem like Back Office can be efficiently given a data-driven design and result in a simple two-layer architecture with only presentation and data access. (Concretely, this could be a Web Forms application using DataGrids.)
Another option might be separating the front end of the club site from, say, the booking module. You could use ASP.NET MVC for the booking module and a CMS (for example, WordPress) for the few pages with news, photos, and static content.
Mixing multiple supporting architectures in the realm of a single system is far from wrong.
Common supporting architectures
The process of identifying business contexts already reveals a lot about the nature of the domain and subdomains. To an expert eye that knows about technologies and frameworks, a good candidate solution appears immediately for a given context.
Just as a quick glossary, Table 5-2 lists the most commonly used supporting architectures you might find in the industry.
TABLE 5-2. A list of supporting architectures.
Canonical segmentation based on presentation, business, and data layers. The architecture might come in slightly different flavors, such as an additional application layer between the presentation and business layers and with the business layer transformed into a domain layer by the use of a DDD development style.
Layered architecture is just another name for a multilayer architecture. We’ll be using the term layered architecture instead of multilayer in the rest of this chapter and throughout the book.
Segmentation that is in many ways similar to that of a multilayer architecture except that now multiple tiers are involved instead of layers. (More on the possible downsides of a layer-to-tier mapping in a moment.)
Classic two-layer (or two-tier) architecture that consists only of presentation plus data access.
Layered architecture based on a presentation layer, an application layer, a domain layer, and an infrastructure layer, designed in accordance with the DDD development style. In particular, the model is expected to be a special type of object model.
Command-Query Responsibility Segregation (CQRS)
Two-fold layered architecture with parallel sections for handling command and query sides. Each section can be architected independently, even with a separate supporting architecture, whether that is DDD or client/server.
Layered architecture that is almost always inspired by a CQRS design that focuses its logic on events rather than plain data. Events are treated as first-class data, and any other queryable information is inferred from stored events.
The context is a standalone application or service that exposes an API to the rest of the world. Typical examples are autonomous web services (for example, Web API host) and Windows services. Yet another example is an application hosting a SignalR engine.
As we write this chapter, another architectural style is gaining in popularity: micro-services. At first, micro-services don’t sound like a completely new idea and are not really presented like that. There’s a lot of service-oriented architecture (SOA) in micro-services, such as the fact that services are autonomous and loosely coupled. However, micro-services also explicitly call out for lightweight HTTP mechanisms for communication between processes. For more information on micro-services, you can check out the Martin Fowler’s site at http://martinfowler.com/articles/microservices.html.
The reason why we mention micro-services here is that, abstractly speaking, the overall idea of micro-services weds well with identifying business contexts, discovering relationships, and giving each its own architecture and autonomous implementation. Micro-services, therefore, can be yet another valid entry in Table 5-2.
Layers and tiers might not be interchangeable
Layers and tiers are not the same. A layer is a logical container for different portions of code; a tier is a physical container for code and refers to its own process space or machine. All layers are actually deployed to a physical tier, but different layers can go to different tiers.
That’s precisely the point we want to raise here.
In Table 5-2, we listed multilayer architecture and multitier architecture. Admittedly, they look the same except that one separates blocks of code logically and the other physically. We suggest, however, that you consider those architectures as different options to be evaluated individually to see if they fit in the solution.
The error that many system integrators made in the past was to deploy a multilayer architecture as a multitier architecture. In doing so, they matched layers to tiers one-to-one. This led to segregating in different tiers the presentation layer of a Web Forms application and the business layer using WCF, Web services or even, in the old days, .NET Remoting. It apparently looked like a better architecture, but it created latency between tiers and had a deep impact on the performance of the system. In addition, system maintenance (for example, deploying updates) is harder and more expensive in a multitier scenario.
Tiers are heavy but can be used to scale the application. However, just having tiers doesn’t automatically ensure your application is faster. Generally speaking, we tend to prefer the deployment of the entire application stack on a single tier, if that’s ever possible.