by Eric Evans
These responsibilities must be considerably broader than those typically assigned to individual objects, as examples will illustrate shortly. As individual MODULES and AGGREGATES are designed, they are factored to keep them within the bounds of one of these major responsibilities. This named grouping of responsibilities by itself could enhance the comprehensibility of a modularized system, since the responsibilities of MODULES could be more readily interpreted. But combining high-level responsibilities with layering gives us an organizing principle for a system.
* * *
The layering pattern that serves best for RESPONSIBILITY LAYERS is the variant called RELAXED LAYERED SYSTEM (Buschmann et al. 1996, p. 45), which allows components of a layer to access any lower layer, not just the one immediately below.
* * *
Therefore:
Look at the conceptual dependencies in your model and the varying rates and sources of change of different parts of your domain. If you identify natural strata in the domain, cast them as broad abstract responsibilities. These responsibilities should tell a story of the high-level purpose and design of your system. Refactor the model so that the responsibilities of each domain object, AGGREGATE, and MODULE fit neatly within the responsibility of one layer.
This is a pretty abstract description, but it will become clear with a few examples. The satellite communications simulator whose story opened this chapter layered its responsibility. I have seen RESPONSIBILITY LAYERS used to good effect in domains as various as manufacturing control and financial management.
The following example explores RESPONSIBILITY LAYERS in detail to give a feel for the discovery of a large-scale structure of any sort, and the way it guides and constrains modeling and design.
Example: In Depth: Layering a Shipping System
Let’s look at the implications of applying RESPONSIBILITY LAYERS to the cargo shipping application discussed in the examples of previous chapters.
As we rejoin the story, the team has made considerable progress creating a MODEL-DRIVEN DESIGN and distilling a CORE DOMAIN. But as the design fleshes out, they are having trouble coordinating how all the parts fit together. They are looking for a large-scale structure that can bring out the main themes of their system and keep everyone on the same page.
Here is a look at a representative part of the model.
Figure 16.3. A basic shipping domain model for routing cargoes
Figure 16.4. Using the model to route a cargo during booking
The team members have been steeped in the domain of shipping for months, and they have noticed some natural stratification of its concepts. It is quite reasonable to discuss transport schedules (the scheduled voyages of ships and trains) without referring to the cargoes aboard those transports. It is harder to talk about tracking a cargo without referring to the transport carrying it. The conceptual dependencies are pretty clear. The team can readily distinguish two layers: “Operations” and the substrate of those operations, which they dub “Capability.”
“Operational” Responsibilities
Activities of the company, past, current, and planned, are collected into the Operations layer. The most obvious Operations object is Cargo, which is the focus of most of the day-to-day activity of the company. The Route Specification is an integral part of Cargo, indicating delivery requirements. The Itinerary is the operational delivery plan. Both of these objects are part of the Cargo’s AGGREGATE, and their life cycles are tied to the time frame of an active delivery.
“Capability” Responsibilities
This layer reflects the resources the company draws upon in order to carry out operations. The Transit Leg is a classic example. The ships are scheduled to run and have a certain capacity to carry cargo, which may or may not be fully utilized.
True, if we were focused on operating a shipping fleet, Transit Leg would be in the Operations layer. But the users of this system aren’t worried about that problem. (If the company were involved in both those activities and wanted the two coordinated, the development team might have to consider a different layering scheme, perhaps with two distinct layers, such as “Transport Operations” and “Cargo Operations.”)
A trickier decision is where to place Customer. In some businesses, customers tend to be transient: they’re interesting while a package is being delivered and then mostly forgotten until next time. This quality would make customers only an operational concern for a parcel delivery service aimed at individual consumers. But our hypothetical shipping company tends to cultivate long-term relationships with customers, and most work comes from repeat business. Given these intentions of the business users, the Customer belongs in the potential layer. As you can see, this was not a technical decision. It was an attempt to capture and communicate knowledge of the domain.
Because the association between Cargo and Customer can be traversed in only one direction, the Cargo REPOSITORY will need a query that finds all Cargoes for a particular Customer. There were good reasons to design it that way anyway, but with the imposition of the large-scale structure, it is now a requirement.
Figure 16.5. A query replaces a bidirectional association that violates the layering.
Figure 16.6. A first-pass layered model
While the distinction between Operations and Capability clarifies the picture, order continues to evolve. After a few weeks of experimentation, the team zeroes in on another distinction. For the most part, both initial layers focus on situations or plans as they are. But the Router (and many other elements excluded from this example) isn’t part of current operational realities or plans. It helps make decisions about changing those plans. The team defines a new layer responsible for “Decision Support.”
“Decision Support” Responsibilities
This layer of the software provides the user with tools for planning and decision making, and it could potentially automate some decisions (such as automatically rerouting Cargoes when a transport schedule changes).
The Router is a SERVICE that helps a booking agent choose the best way to send a Cargo. This places the Router squarely in Decision Support.
The references within this model are all consistent with the three layers except for one discordant element: the “is preferred” attribute on Transport Leg. This attribute exists because the company prefers to use its own ships when it can, or the ships of certain other companies with which it has favorable contracts. The “is preferred” attribute is used to bias the Router toward these favored transports. This attribute has nothing to do with “Capability.” It is a policy that directs decision making. To use the new RESPONSIBILITY LAYERS, the model will have to be refactored.
Figure 16.7. Refactoring the model to conform to the new layering structure
This factoring makes the Route Bias Policy more explicit while making Transport Leg more focused on the fundamental concept of transportation capability. A large-scale structure based on a deep understanding of the domain will often push the model in directions that clarify its meaning.
This new model now smoothly fits into the large-scale structure.
Figure 16.8. The restructured and refactored model
A developer accustomed to the chosen layers can more readily discern the roles and dependencies of the parts. The value of the large-scale structure increases as the complexity grows.
Note that although I’m illustrating this example with a modified UML diagram, the drawing is just a way of communicating the layering. UML doesn’t include this notation, so this is additional information imposed for the sake of the reader. If code is the ultimate design document for your project, it would be helpful to have a tool for browsing classes by layer or at least for reporting them by layer.
How Does This Structure Affect Ongoing Design?
Once a large-scale structure has been adopted, subsequent modeling and design decisions must take it into account. To illustrate, suppose that we must add a new feature to this already layered design. The domain experts have just told us that routin
g restrictions apply for certain categories of hazardous materials. Certain materials may not be allowed on some transports or in some ports. We have to make the Router obey these regulations.
There are many possible approaches. In the absence of a large-scale structure, one appealing design would be to give the responsibility of incorporating these routing rules to the object that owns the Route Specification and the Hazardous Material (HazMat) code—namely the Cargo.
Figure 16.9. A possible design for routing hazardous cargo
Figure 16.10
The trouble is, this design doesn’t fit the large-scale structure. The HazMat Route Policy Service is not the problem; it fits neatly into the responsibility of the Decision Support layer. The problem is the dependency of Cargo (an Operational object) on HazMat Route Policy Service (a Decision Support object). As long as the project is committed to these layers, this model cannot be allowed. It would confuse developers who expected the structure to be followed.
There are always many design possibilities, and we’ll just have to choose another one—one that follows the rules of the large-scale structure. The HazMat Route Policy Service is all right, but we need to move the responsibility for using the policy. Let’s try giving the Router the responsibility for collecting appropriate policies before searching for a route. This means changing the Router interface to include objects that policies might depend on. Here is a possible design.
Figure 16.11. A design consistent with layering
A typical interaction is shown in Figure 16.12 on the next page.
Figure 16.12
Now, this isn’t necessarily a better design than the other. They both have pros and cons. But if everyone on a project makes decisions in a consistent way, the design as a whole will be much more comprehensible, and that is worth some modest trade-offs on detailed design choices.
If the structure is forcing many awkward design choices, then in keeping with EVOLVING ORDER, it should be evaluated and perhaps modified or even discarded.
Choosing Appropriate Layers
Finding good RESPONSIBILITY LAYERS, or any large-scale structure, is a matter of understanding the problem domain and experimenting. If you allow EVOLVING ORDER, the initial starting point is not critical, although a poor choice does add work. The structure may well evolve into something unrecognizable. So the guidelines suggested here should be applied when considering transformations of the structure as much as when choosing from scratch.
As layers get switched out, merged, split, and redefined, here are some useful characteristics to look for and preserve.
• Storytelling. The layers should communicate the basic realities or priorities of the domain. Choosing a large-scale structure is less a technical decision than a business modeling decision. The layers should bring out the priorities of the business.
• Conceptual dependency. The concepts in the “upper” layers should have meaning against the backdrop of the “lower” layers, while the lower-layer concepts should be meaningful standing alone.
• CONCEPTUAL CONTOURS. If the objects of different layers should have different rates of change or different sources of change, the layer accommodates the shearing between them.
It isn’t always necessary to start from scratch in defining layers for each new model. Certain layers show up in whole families of related domains.
For example, in businesses based on exploiting large fixed capital assets, such as factories or cargo ships, logistical software can often be organized into a “Potential” layer (another name for the “Capability” layer in the example) and an “Operations” layer.
• Potential. What can be done? Never mind what we are planning to do. What could we do? The resources of the organization, including its people, and the way those resources are organized are the core of the Potential layer. Contracts with vendors also define potentials. This layer could be recognized in almost any business domain, but it is a prominent part of the story in those businesses, such as transportation and manufacturing, that have relatively large fixed capital investments that enable the business. Potential includes transient assets as well, but a business driven primarily by transient assets might choose layers that emphasize this, as discussed later. (This layer was called “Capability” in the example.)
• Operation. What is being done? What have we managed to make of those potentials? Like the Potential layer, this layer should reflect the reality of the situation, rather than what we want it to be. In this layer we are trying to see our own efforts and activities: What we are selling, rather than what enables us to sell. It is very typical of Operational objects to reference or even be composed of Potential objects, but a Potential object shouldn’t reference the Operations layer.
In many, perhaps most, existing systems in domains of this kind, these two layers cover everything (although there could be some entirely different and more revealing breakdown). They track the current situation and active operational plans and issue reports or documents about it. But tracking is not always enough. When projects seek to guide or assist users, or to automate decision making, there is an additional set of responsibilities that can be organized into another layer, above Operations.
• Decision Support. What action should be taken or what policy should be set? This layer is for analysis and decision making. It bases its analysis on information from lower layers, such as Potential or Operations. Decision Support software may use historical information to actively seek opportunities for current and future operations.
Decision Support systems have conceptual dependencies on other layers such as Operations or Potential because decisions aren’t made in a vacuum. A lot of projects implement Decision Support using data warehouse technology. The layer becomes a distinct BOUNDED CONTEXT, with a CUSTOMER/SUPPLIER relationship with the Operations software. In other projects, it is more deeply integrated, as in the preceding extended example. And one of the intrinsic advantages of layers is that the lower layers can exist without the higher ones. This can facilitate phased introductions or higher-level enhancements built on top of older operational systems.
Another case is software that enforces elaborate business rules or legal requirements, which can constitute a RESPONSIBILITY LAYER.
• Policy. What are the rules and goals? Rules and goals are mostly passive, but constrain the behavior in other layers. Designing these interactions can be subtle. Sometimes a Policy is passed in as an argument to a lower level method. Sometimes the STRATEGY pattern is applied. Policy works well in conjunction with a Decision Support layer, which provides the means to seek the goals set by Policy, constrained by the rules set by Policy.
Policy layers can be written in the same language as the other layers, but they are sometimes implemented using rules engines. This doesn’t necessarily place them in a separate BOUNDED CONTEXT. In fact, the difficulty of coordinating such different implementation technologies can be eased by fastidiously using the same model across both. When rules are written based on a different model than the objects they apply to, either the complexity goes way up or the objects get dumbed down to keep things manageable.
Figure 16.13. Conceptual dependencies and shearing points in a factory automation system
Many businesses do not base their capability on plant and equipment. In financial services or insurance, to name two, the potential is to a large extent determined by current operations. An insurance company’s ability to take on a new risk by underwriting a new policy agreement is based on the diversification of its current business. The Potential layer would probably merge into Operations, and a different layering would evolve.
One area that often comes to the fore in these situations is commitments made to customers.
• Commitment. What have we promised? This layer has the nature of Policy, in that it states goals that direct future operations, but it has the nature of Operations in that commitments emerge and change as a part of ongoing business activity.
Figure 16.14. Conceptual depe
ndencies and shearing points in an investment banking system
The Potential and Commitment layers are not mutually exclusive. A domain in which both are prominent, say a transportation company with a lot of custom shipping services, might use both. Other layers more specific to those domains might be useful too. Change things. Experiment. But it is best to keep the layering system simple; going beyond four or possibly five becomes unwieldy. Having too many layers isn’t as effective at telling the story, and the problems of complexity the large-scale structure was meant to solve will come back in a new form. The large-scale structure must be ferociously distilled.
Although these five layers are applicable to a range of enterprise systems, they do not capture the salient responsibilities of all domains. In other cases, it would be counterproductive to try to force the design into this shape, but there may be a natural set of RESPONSIBILITY LAYERS that do work. For a domain completely unrelated to those we’ve discussed, these layers might have to be completely original. Ultimately, you have to use your intuition, start somewhere, and let the ORDER EVOLVE.
Knowledge Level
[A KNOWLEDGE LEVEL is] a group of objects that describes how another group of objects should behave. [Martin Fowler, “Accountability,” www.martinfowler.com]
KNOWLEDGE LEVEL untangles things when we need to let some part of the model itself be plastic in the user’s hands yet constrained by a broader set of rules. It addresses requirements for software with configurable behavior, in which the roles and relationships among ENTITIES must be changed at installation or even at runtime.