6
Maintaining the Integrity of Domain Models with Bounded Contexts

WHAT’S IN THIS CHAPTER?

  • The challenges of a single model
  • The importance of the bounded context
  • Carving out and defining boundaries of responsibility in code
  • Protecting the integrity of core parts of the domain
  • Where to define boundaries

In large and complex applications you will find multiple models at play. Each model will be built to represent a distinct area of the problem domain, with each implementation using an appropriate code design pattern suitable for the complexity of the problem. Ideally you will have a model for each subdomain; however, this might not always be the case as some complex subdomains could contain more than a single model and some models could span two or more subdomains. No matter how many models you have you will find that they will need to interact to fulfill the behaviors of a system. It is when models are combined by teams without a clear understanding of what context they apply to that they are prone to become blurred and lose explicitness, as concepts and logic are intermingled.

Therefore it is vital to protect the integrity of each model and clearly define the boundaries of their responsibility in code. This is achieved by binding a model to a specific context, known as a bounded context. A bounded context is defined based on team’s language, and physical artifacts. Bounded contexts enable a model to stay consistent and meaningful, which is vital in managing complexity in the solution space. Diligent use of bounded contexts is essential to being successful with Domain-Driven Design.

The Challenges of a Single Model

At the core of Domain-Driven Design is the need to create explicit, evolvable models in code that align with the shared conceptual models. As new domain insights are gained, they can be incorporated into the model efficiently. However, if a single model is used for an entire system, concepts from one area of the model can be confused with similar-sounding concepts from another area of the system—and even become coupled to them. Therefore, DDD advocates that you break up a large complex system into multiple code models.

A Model Can Grow in Complexity

Large models accommodate many domain concepts and carry out many business use cases. As a consequence, it is easy to make mistakes and group the wrong concepts together. It can also be very difficult to find what you are looking for. The more the system grows, the more severe these problems become, slowing down the speed at which new features and improvements can be added.

As Figure 6.1 highlights, with each new use case and insight incorporated into the model, the number of concepts and dependencies in the model grows, resulting in increased complexity.

images

FIGURE 6.1 A model will grow in complexity.

Multiple Teams Working on a Single Model

Complex code is just one of the problems arising from a single model. Collaboration overhead and organizational inefficiencies are also major problems a monolithic model is likely to cause.

As one team wants to release a new feature, they have to check with other teams that their changes can also be deployed. Either the first team will have to wait, or complex branching strategies will be used. As Chapter 11, “Introduction to Bounded Context Integration,” explains in more detail, complex branching strategies can be a big hindrance to an organization’s ability to frequently and efficiently deliver business value and learn about their customers.

Continually requiring teams to collaborate on the design of new features or to plan releases is an unnecessary inefficiency. As each team works with their own domain expert and tries to drive their model in different directions, dragging other teams along with them is mutually wasteful. The more teams, the more expensive the collaboration overhead, and the more complex the codebase, as Figure 6.2 illustrates.

images

FIGURE 6.2 Complexity in a model increases with multiple teams.

If multiple models are used instead, teams can iterate on their models and deliver new value frequently and efficiently because they do not have to synchronize with other teams or concern themselves with concepts from other teams’ models.

You may be concerned about a team’s duplicating code in each of their models. But focus on the benefits that arise by removing dependencies between teams. Essentially, it is Ok to duplicate code between models because the concepts are not the same.

Ambiguity in the Language of the Model

One of the epiphanies that DDD practitioners have is the realization that some concepts in a system are very similar—they might even have the same name. Yet actually, they mean very different things to different parts of the business. As Figure 6.3 illustrates, the “Ticket” concept means different things to the Sales and Customer Service departments.

images

FIGURE 6.3 Domain terms mean different things in different contexts.

Once you accept that names can have different meanings in different contexts, it’s easier to accept that multiple smaller models are more effective than a single large one. You can also have more meaningful discussions with domain experts. For example, based on Figure 6.3, when talking to the Sales manager about tickets, you know she cares about the cost and location of an event; whereas discussion about tickets with the Customer Service manager will be focused on the severity and category of problems raised by customers.

The Applicability of a Domain Concept

Sometimes, a single physical entity in the problem domain can mistakenly be classified as a single concept in code. This is problematic when the physical entity actually represents multiple concepts, that each mean different things in different contexts. The classic example is a product.

Figure 6.4 shows how products mean different things in different contexts. It is a concept that must be acquired with a profitable margin and acceptable lead time to the Procurement team. Yet to the Sales team a product is a concept with images, size guides, and belongs to a selling category—none of which are relevant to the Procurement team, even though it is the same physical entity in the problem domain.

images

FIGURE 6.4 The same concept should be understood within different contexts.

When a physical entity, such as a product, actually represents multiple domain concepts, it is often modeled as a single concept by developers. Unfortunately, it’s very easy to fall into the trap of thinking that because a product can be a physical item that it should be modeled as a single class in code. This leads to coupling, as each model shares the same product class, as shown in Figure 6.5.

images

FIGURE 6.5 A single view of an entity in the domain for all subdomains can quickly become a problem.

As discussed previously, when multiple contexts are coupled, code can become excessively complex and the collaboration overhead between teams can become excessively costly. The shared class, in this example product, also violates the Single Responsibility Principle (SRP), since there are four contexts that all want it to change for completely different reasons.

When there are no boundaries in the code, it is too easy for coupling to occur, as with the product shown in Figure 6.5. A better solution that reduces the coupling would be for each context—Promotion, Allocation, Loyalty, and Shipping—to have its own model. Each model would then contain a unique representation of a product that only satisfies the needs of the model’s context. Figure 6.6 shows the multiple responsibilities of the shared Product class, indicating which model each of them should really belong in.

images

FIGURE 6.6 The product, an implementation of the god object antipattern.

Integration with Legacy Code or Third Party Code

Another reason to prefer smaller models is that integrating with legacy code or third parties can be less problematic. Adding new features to a monolithic codebase can be painful when there is lots of legacy code. You want to add clean, new, insightful models that you created with domain experts, but the limitations of legacy code can constrain the expressiveness of your design. But if you have smaller models, not all of them will need to touch the legacy code.

A number of patterns, discussed in Chapter 11, show how it is easier to apply DDD to legacy systems when you have multiple smaller models to work with.

Your Domain Model Is not Your Enterprise Model

Having a single model of the entire system is useful in some scenarios, including business information (BI) and reporting. However, the enterprise model is not the best solution for creating an evolvable domain model that explicitly expresses domain concepts. Nor is an enterprise model suitable for iterative development processes that aim to deliver business value frequently delete repetition.

Figure 6.7 shows how you can have the best of both worlds—a unique model for each context and an enterprise model for BI.

images

FIGURE 6.7 The difference between an enterprise model and a domain model.

Use Bounded Contexts to Divide and Conquer a Large Model

A bounded context defines the applicability of a model. It gives clarity on what a model is used for, where it should be consistent, and what it should ignore. A bounded context ensures that domain concepts outside a model’s context do not distract from the problem it was designed to solve. A bounded context makes it explicit to teams what the model is responsible for and what it is not.

Context is an important term in Domain-Driven Design. Each model has a context implicitly defined within a subdomain. When you talk about a product in the context of the fulfillment subdomain, you don’t need to call it a product that can be fulfilled; likewise, when talking in the context of shopping, it’s not a saleable product. It’s simply a product in a defined context.

When communicating with domain experts or other members of the development team, you should ensure that everyone is aware of the context you are talking in. The context defines the scope of the model, limiting the boundaries of the problem space, enabling the team to focus without distractions.

In Chapter 4, “Model-Driven Design,” you are introduced to the concept of the ubiquitous language (UL) and the importance of models defined in a context that are free from linguistic ambiguity. The context refers to the specific responsibility of the model, which helps to decompose and organize the problem space. A bounded context takes the idea of a model in context further by encapsulating it within a boundary of responsibility. This boundary is a concrete technical implementation, as opposed to the context that is more abstract. The bounded context enforces communication in such a manner as to not lessen the purity of the model.

A bounded context is first and foremost a linguistic boundary. When talking with domain experts, if you feel a sentence requires a context, this is a big hint that you need to isolate that model within a bounded context.

images

FIGURE 6.8 Putting terms into context and identifying multiple models.

Figure 6.9 shows how a product can be a smaller more focused concept when applied to a specific context. It is important when developing the application that you isolate models within bounded contexts to avoid the blurring of responsibilities that can lead to code that resembles a BBoM.

images

FIGURE 6.9 Define each model within its own context.

Defining a Model’s Boundary

The need for bounded contexts is clear in larger systems, but the process of identifying bounded contexts and their boundaries is challenging. Fortunately, it’s not an up-front decision you have to get perfectly correct. As you learn more about the domain, you can adjust the boundaries of your bounded contexts.

There are two aspects of a problem domain that you can use as a guide to identifying bounded contexts—terminology and business capabilities. As you’ve seen previously in this chapter, the same term can have different semantics in different contexts. If you can delineate a domain model based on a change in the meaning of a word or phrase, you will very likely have identified the boundary of a bounded context. Business capabilities are often easy to discern but can be misleading. For example, if a business has a Sales department and a Customer Service department, there is very likely to be a sales and customer bounded context. But that’s not always true, so it’s important not to blindly model business capabilities.

Outside of the problem domain, team structure and location can also be a big influence on context boundaries, as can integrating with legacy or third-party systems.

Size, though, is not a guideline for delineating bounded contexts. No absolute or relative value can tell you how many classes or lines of code you need. A bounded context’s size is dependent mostly on aspects of the problem domain. Some bounded contexts may, therefore, be large while others are small.

To summarize, context boundaries can be influenced by the following:

  • Ambiguity in terminology and concepts of the domain
  • Alignment to subdomains and business capabilities
  • Team organization and physical location
  • Legacy code base
  • Third party integration

Define Boundaries around Language

It’s important to be explicit about what context you’re using when talking with domain experts, because terminology can have different meanings in different contexts. As repeated throughout this chapter, multiple models will be at play in your domain. You need to enforce linguistic boundaries to protect the validity of a domain term. Therefore, linguistic boundaries are bounded context boundaries. If the concept of a product has multiple meanings inside the same model, then the model should be split into at least two bounded contexts, each having a single definition of the product concept. This was discussed previously and illustrated in Figure 6.11. Equally, the same term can refer to multiple concepts, as was the case with the ticket example illustrated in Figure 6.3. That is also an example of a linguistic boundary that should be the boundary of a bounded context.

Align to Business Capabilities

An organization is an ecosystem of interdependent services, each with its own vocabulary. Hence business capabilities are often strong indicators of linguistic boundaries. As mentioned previously, a Sales department and Customer Service department can have completely different definitions of a ticket concept. Accordingly, you should look to business capabilities as potential context boundaries.

Be careful when using business capabilities to delineate bounded contexts. Sometimes business capabilities do not align perfectly with the problem domain. You can end up with a system that mirrors an organization’s communication structure, but does not faithfully represent the domain. Conway’s Law even implies that a system will inevitably reflect an organization’s communication structure:

Any organization that designs a system (defined more broadly here than just information systems) will inevitably produce a design whose structure is a copy of the organization’s communication structure.”

You can use Conway’s Law as a guide in two ways. Firstly, you can be cognizant of Conway’s Law so that you don’t just model the organization’s structure. Alternatively, you can remodel your organization based on the desired architecture of your system. Either approach is going to require a big effort, so it’s not a decision you should take without careful planning.

Create Contexts around Teams

A single team should be responsible for a bounded context, whether that crosses one or many applications or departments. So structure teams around bounded contexts; form product and services groups rather than trying to mirror the departmental structure of the business. Ensure that teams are responsible for a bounded context from presentation through domain logic and to persistence.

The main rationale for aligning teams with bounded contexts is that independence allows teams to both move faster and make better decisions. Teams can move faster if they are in full control of product and technical decisions. They can also iterate much more rapidly if they don’t have to worry about affecting other teams.

A single team can stay focused on its business priorities; therefore, when a decision needs to be made or someone has a suggestion, everyone can quickly huddle together and decide on the best way forward. Conversely, different teams might have different business priorities and arrangements that affect their ability to work together efficiently. For example, one team might have to wait until the other team becomes available before they can schedule a meeting and start making decisions or iterating on concepts.

Remember, communication between teams is sometimes a good thing, so don’t completely avoid it; just limit it to when it’s useful. One example of useful cross-team communication is knowledge and skill sharing.

Try to Retain Some Communication between Teams

Although having completely independent teams is a productivity win, it’s important to ensure that communication between teams still occurs for knowledge and skill-sharing benefits. Ultimately, bounded contexts combine at run time to carry out full use cases, so teams need a big-picture understanding of how their bounded context(s) fit into the wider system. Established patterns for this problem involve having regular sessions in which teams share with other development teams what they are working on, how they have implemented it, or any technologies that have helped them achieve their goals. Another excellent pattern is cross-team pair programming. This involves moving a developer to a different team for a few days to learn about that part of the domain. You can spawn many novel approaches based on these two concepts of having group sessions and moving people around.

Making an effort to ensure that teams communicate efficiently really pays off when breaking changes need to occur. And in every system, you do always get them. At some point, the contract between bounded contexts needs to change to meet the needs of the business. Having teams communicate to work out the best overall solution can sometimes be the most efficient option.

Diagrams and lightweight documentation help teams share knowledge quickly, especially when new members join. You’ll see how context maps and other types of diagrams facilitate knowledge sharing in Chapter 7, “Context Mapping.”

Context Game

To demonstrate the importance of modeling in context and to reveal multiple models within the domain, you can employ another facilitating game. The Context Game, pioneered by Greg Young (http://codebetter.com/gregyoung/2012/02/29/the-context-game-2/), helps to make it clear where an additional model is required to map the problem space effectively.

You can introduce the game into knowledge-crunching sessions when you think you have an overloaded or ambiguous term. Split the group into smaller groups of developers and business experts. You should split the business experts by department or business responsibility. Give them 20 minutes to come up with a definition of what the term or concept means to them in their part of the business, using the developers to capture the knowledge. Then bring the whole team together to present their views on the concept.

You will find that different parts of the business have different views on the shared terminology. Where the business functions have a difference of opinion is where you need to draw your context lines and create a new model. This was shown in Figure 6.8 with the product concept existing in many different contexts.

The Difference between a Subdomain and a Bounded Context

Subdomains, introduced in Chapter 3, “Focusing on the Core Domain,” represent the logical areas of a problem domain, typically reflecting the business capabilities of the business organizational structure. They are used to distinguish the areas of importance in an application, the core domain, from the less important areas, the supporting and generic domains. Subdomains exist to distill the problem space and break down complexity.

Domain models are built to fulfill the uses cases of each of the subdomains. Ideally there would be a one-to-one mapping between models and subdomains, but this is not always the case. Models are defined based on team structure, ambiguity in language, business process alignment, or physical deployment. Therefore a subdomain could contain more than a single model and a model could span more than a single subdomain. This is often the case within legacy environments.

Models need to be isolated and defined within an explicit context in order to stay pure and focused. As you’ve learned, this context is known as the bounded context. Unlike a subdomain, a bounded context is a concrete technical implementation that enforces boundaries between models within an application. Bounded contexts exist in the solution space and are represented as explicit domain models in a context.

Implementing Bounded Contexts

A bounded context owns the vertical slice of functionality from the presentation layer, through the domain logic layer, on to the persistence, and even to the data storage.

Applying the concept of bounded contexts to the system shown previously in Figure 6.5 results in a system with each bounded context looking after its own presentation, domain logic, and persistence responsibilities, as shown in Figure 6.10. In this improved architecture, the concept of a product can exist in each bounded context and only contain attributes and logic prevalent to that context alone. Changes in any bounded context no longer have undesired effects on others because the subdomains are now isolated.

images

FIGURE 6.10 A layered architecture pattern per bounded context and not per application.

A closer inspection, as shown in Figure 6.11, shows the product concept existing in two models but defined by the context that it is within.

images

FIGURE 6.11 The Product class in different contexts.

Not all bounded contexts need to share the same architectural pattern. If a bounded context contains a supporting or generic domain with a low logic complexity, you might want to favor a more create, read, update, and delete (CRUD) style of development. If, however, the domain logic is sufficiently complex, it’s best to create a rich object-oriented model of the domain. Once bounded contexts are separated you can go a step further and apply different architectural patterns, as shown in Figure 6.12.

images

FIGURE 6.12 You can apply different architectural patterns to the different bounded contexts.

Figure 6.12 shows how you can use different architectural patterns within each bounded context of an application. The various bounded contexts are pulled together using a composite UI to display to the user. Figure 6.13 shows that the bounded context encapsulates the infrastructure, data store and user interface as well as the domain model.

images

FIGURE 6.13 The anatomy of a bounded context.

Fundamentally, autonomy is a key characteristic of bounded contexts that isolates teams from external distractions, and isolates models from unrelated concepts. In Part II you will see practical examples of implementing and integrating autonomous bounded contexts using scalable integration approaches, including event-driven architecture with messaging and REST.

The Salient Points

  • Trying to use a single model for a complex problem domain will often cause code to result in a Big Ball of Mud.
  • A monolithic model increases collaboration overhead amongst teams and reduces their efficiency at delivering business value.
  • For each model at play within an application, you must explicitly define its context and communicate it to other teams.
  • A bounded context is a linguistic boundary. It isolates models to remove ambiguity within UL.
  • A bounded context protects the integrity of the domain model.
  • Identifying and creating bounded contexts is one of the most important aspects of Domain-Driven Design.
  • There are no rules for defining the boundaries of a model and therefore bounded contexts. Instead you should base bounded contexts around linguistic boundaries, team organization, subdomains and physical deployments.
  • Subdomains are used in the problem space to partition the problem domain. Bounded contexts are used to define the applicability of a model in the solution space.
  • A single team should own a bounded context.
  • Architectural patterns apply at the bounded context level, not at the application level. If you don’t have complex logic in a bounded context, use a simple create, read, update, and delete (CRUD) architecture.
  • Speak a ubiquitous language within an explicitly bounded context.
  • A bounded context should be autonomous—owning the entire code stack from presentation through domain logic and onto the database and schema.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.133.180