Domain-Driven Design

Home > Other > Domain-Driven Design > Page 35
Domain-Driven Design Page 35

by Eric Evans


  The answer is not to avoid all integration with other systems. I’ve been on projects where people enthusiastically set out to replace all the legacy, but this is just too much to take on at once. Besides, integrating with existing systems is a valuable form of reuse. On a large project, one subsystem will often have to interface with several other, independently developed subsystems. These will reflect the problem domain differently. When systems based on different models are combined, the need for the new system to adapt to the semantics of the other system can lead to a corruption of the new system’s own model. Even when the other system is well designed, it is not based on the same model as the client. And often the other system is not well designed.

  There are many hurdles in interfacing with an external system. For example, the infrastructure layer must provide the means to communicate with another system that might be on a different platform or use different protocols. The data types of the other system must be translated into those of your system. But often overlooked is the certainty that the other system does not use the same conceptual domain model.

  It seems clear enough that errors will result if you take some data from one system and misinterpret it in another. You may even corrupt the database. But even so, this problem tends to sneak up on us because we think that what we are transporting between systems is primitive data, whose meaning is unambiguous and must be the same on both sides. This assumption is usually wrong. Subtle yet important differences in meaning arise from the way the data are associated in each system. And even if primitive data elements do have exactly the same meaning, it is usually a mistake to make the interface to the other system operate at such a low level. A low-level interface takes away the power of the other system’s model to explain the data and constrain its values and relationships, while saddling the new system with the burden of interpreting primitive data that is not in terms of its own model.

  We need to provide a translation between the parts that adhere to different models, so that the models are not corrupted with undigested elements of foreign models.

  Therefore:

  Create an isolating layer to provide clients with functionality in terms of their own domain model. The layer talks to the other system through its existing interface, requiring little or no modification to the other system. Internally, the layer translates in both directions as necessary between the two models.

  This discussion of a mechanism to link two systems might bring to mind issues of transporting the data from one program to another or from one server to another. I’ll discuss the incorporation of the technical communications mechanism shortly. But such details shouldn’t be confused with an ANTICORRUPTION LAYER, which is not a mechanism for sending messages to another system. Rather, it is a mechanism that translates conceptual objects and actions from one model and protocol to another.

  An ANTICORRUPTION LAYER can become a complex piece of software in its own right. Next I’ll outline some of the design considerations for creating one.

  Designing the Interface of the ANTICORRUPTION LAYER

  The public interface of the ANTICORRUPTION LAYER usually appears as a set of SERVICES, although occasionally it can take the form of an ENTITY. Building a whole new layer responsible for the translation between the semantics of the two systems gives us an opportunity to reabstract the other system’s behavior and offer its services and information to our system consistently with our model. It may not even make sense, in our model, to represent the external system as a single component. It may be best to use multiple SERVICES (or occasionally ENTITIES), each of which has a coherent responsibility in terms of our model.

  Implementing the ANTICORRUPTION LAYER

  One way of organizing the design of the ANTICORRUPTION LAYER is as a combination of FACADES, ADAPTERS (both from Gamma et al. 1995), and translators, along with the communication and transport mechanisms usually needed to talk between systems.

  We often have to integrate with systems that have large, complicated, messy interfaces. This is an implementation issue, not an issue of conceptual model differences that motivated the use of ANTICORRUPTION LAYERS, but it is a problem you’ll encounter trying to create them. Translating from one model to another (especially if one model is fuzzy) is a hard enough job without simultaneously dealing with a subsystem interface that is hard to talk to. Fortunately, that is what FACADES are for.

  A FACADE is an alternative interface for a subsystem that simplifies access for the client and makes the subsystem easier to use. Because we know exactly what functionality of the other system we want to use, we can create a FACADE that facilitates and streamlines access to those features and hides the rest. The FACADE does not change the model of the underlying system. It should be written strictly in accordance with the other system’s model. Otherwise, you will at best diffuse responsibility for translation into multiple objects and overload the FACADE and at worst end up creating yet another model, one that doesn’t belong to the other system or your own BOUNDED CONTEXT. The FACADE belongs in the BOUNDED CONTEXT of the other system. It just presents a friendlier face specialized for your needs.

  An ADAPTER is a wrapper that allows a client to use a different protocol than that understood by the implementer of the behavior. When a client sends a message to an ADAPTER, it is converted to a semantically equivalent message and sent on to the “adaptee.” The response is converted and passed back. I’m using the term adapter a little loosely, because the emphasis in Gamma et al. 1995 is on making a wrapped object conform to a standard interface that clients expect, whereas we get to choose the adapted interface, and the adaptee is probably not even an object. Our emphasis is on translation between two models, but I think this is consistent with the intent of ADAPTER.

  For each SERVICE we define, we need an ADAPTER that supports the SERVICE’S interface and knows how to make equivalent requests of the other system or its FACADE.

  The remaining element is the translator. The ADAPTER’S job is to know how to make a request. The actual conversion of conceptual objects or data is a distinct, complex task that can be placed in its own object, making them both much easier to understand. A translator can be a lightweight object that is instantiated when needed. It needs no state and does not need to be distributed, because it belongs with the ADAPTER(S) it serves.

  Those are the basic elements I use to create an ANTICORRUPTION LAYER. There are a few other considerations.

  • Typically, the system under design (your subsystem) will be initiating action, as implied by Figure 14.8. There are cases, however, when the other subsystem may need to request something of your subsystem or notify it of some event. An ANTICORRUPTION LAYER can be bidirectional, defining SERVICES on both interfaces with their own ADAPTERS, potentially using the same translators with symmetrical translations. Although implementing the ANTICORRUPTION LAYER doesn’t usually require any change to the other subsystem, it might be necessary in order to make the other system call on SERVICES of the ANTICORRUPTION LAYER.

  Figure 14.8. The structure of an ANTICORRUPTION LAYER

  • You’ll usually need some communications mechanism to connect the two subsystems, and they could well be on separate servers. In this case, you have to decide where to place these communication links. If you have no access to the other subsystem, you may have to put the links between the FACADE and the other subsystem. However, if the FACADE can be integrated directly with the other subsystem, then a good option is to put the communication link between the ADAPTER and FACADE, because the protocol of the FACADE is presumably simpler than what it covers. There also will be cases where the entire ANTICORRUPTION LAYER can live with the other subsystem, placing communication links or distribution mechanisms between your subsystem and the SERVICES that make up the ANTICORRUPTION LAYER’s interface. These are implementation and deployment decisions to be made pragmatically. They have no bearing on the conceptual role of the ANTICORRUPTION LAYER.

  • If you do have access to the other subsystem, you may f
ind that a little refactoring over there can make your job easier. In particular, try to write more explicit interfaces for the functionality you’ll be using, starting with automated tests, if possible.

  • Where integration requirements are extensive, the cost of translation goes way up. It may be necessary to make choices in the model of the system under design that keep it closer to the external system, in order to make translation easier. Do this very carefully, without compromising the integrity of the model. It is only something to do selectively when translation difficulty gets out of hand. If this approach seems the most natural solution for much of the important part of the problem, consider making your subsystem a CONFORMIST pattern, eliminating translation.

  • If the other subsystem is simple or has a clean interface, you may not need the FACADE.

  • Functionality can be added to the ANTICORRUPTION LAYER if it is specific to the relationship of the two subsystems. An audit trail for use of the external system or trace logic for debugging the calls to the other interface are two useful features that come to mind.

  Remember, an ANTICORRUPTION LAYER is a means of linking two BOUNDED CONTEXTS. Ordinarily, we are thinking of a system created by someone else; we have incomplete understanding of the system and little control over it. But that is not the only situation where you need a little padding between subsystems. There are even situations in which it makes sense to connect two subsystems of your own design with an ANTICORRUPTION LAYER, if they are based on different models. Presumably, in such a case, you will have full control over both sides and typically can use a simple translation layer. However, if two BOUNDED CONTEXTS have gone SEPARATE WAYS yet still have some need of functional integration, an ANTICORRUPTION LAYER can reduce the friction between them.

  Example: The Legacy Booking Application

  In order to have a small, quick first release, we will write a minimal application that can set up a shipment and then pass that to the legacy system through a translation layer for booking and support operations. Because we built the translation layer specifically to protect our developing model from the influence of the legacy design, this translation is an ANTICORRUPTION LAYER.

  Initially, the ANTICORRUPTION LAYER will accept the objects representing a shipment, convert them, pass them to the legacy system and request a booking, and then capture the confirmation and translate it back into the confirmation object of the new design. This isolation will allow us to develop our new application mostly independently of the old one, though we’ll have to invest quite a bit in translation.

  With each successive release, the new system can either take over more functions of the legacy or simply add new value without replacing existing capabilities, depending on later decisions. This flexibility, and the ability to continually operate the combined system while making a gradual transition, probably makes it worth the expense to build the ANTICORRUPTION LAYER.

  A Cautionary Tale

  To protect their frontiers from raids by neighboring nomadic warrior tribes, the early Chinese built the Great Wall. It was not an impenetrable barrier, but it allowed a regulated commerce with neighbors while providing an impediment to invasion and other unwanted influence. For two thousand years it defined a boundary that helped the Chinese agricultural civilization to define itself with less disruption from the chaos outside.

  Although China might not have become so distinct a culture without the Great Wall, the Wall’s construction was immensely expensive and bankrupted at least one dynasty, probably contributing to its fall. The benefits of isolation strategies must be balanced against their costs. There is a time to be pragmatic and make measured revisions to the model, so that it can fit more smoothly with foreign ones.

  There is overhead involved in any integration, from full-on CONTINUOUS INTEGRATION inside a single BOUNDED CONTEXT, through the lesser commitments of SHARED KERNELS or CUSTOMER/SUPPLIER DEVELOPER TEAMS, to the one-sidedness of the CONFORMIST and the defensive posture of the ANTICORRUPTION LAYER. Integration can be very valuable, but it is always expensive. We should be sure it is really needed. . . .

  Separate Ways

  We must ruthlessly scope requirements. Two sets of functionality with no indispensable relationship can be cut loose from each other.

  Integration is always expensive. Sometimes the benefit is small.

  In addition to the usual expense of coordinating teams, integration forces compromises. The simple specialized model that can satisfy a particular need must give way to the more abstract model that can handle all situations. Perhaps some completely different technology could provide certain features very easily, but it is difficult to integrate. Maybe some team is just so hard to get along with that nothing works very well when other teams try to collaborate with them.

  In many circumstances, integration provides no significant benefit. If two functional parts do not call upon each other’s functionality, or require interactions between objects that are touched by both, or share data during their operations, then integration, even through a translation layer, may not be necessary. Just because features are related in a use case does not mean they must be integrated.

  Therefore:

  Declare a BOUNDED CONTEXT to have no connection to the others at all, allowing developers to find simple, specialized solutions within this small scope.

  The features can still be organized in middleware or the UI layer, but there will be no sharing of logic, and an absolute minimum of data transfer through translation layers—preferably none.

  Example: An Insurance Project Slims Down

  One project team had set out to develop new software for insurance claims that would integrate into one system everything a customer service agent or a claims adjuster needed. After a year of effort, team members were stuck. A combination of analysis paralysis and a major up-front investment in infrastructure had found them with nothing to show an increasingly impatient management. More seriously, the scope of what they were trying to do was overwhelming them.

  A new project manager forced everyone into a room for a week to form a new plan. First they made lists of requirements and tried to estimate their difficulty and assign importance. They ruthlessly chopped the difficult and unimportant ones. Then they started to bring order to the remaining list. Many smart decisions were made in that room that week, but in the end, only one turned out to be important. At some point it was recognized that there were some features for which integration provided little added value. For example, adjusters needed access to some existing databases, and their current access was very inconvenient. But, although the users needed to have this data, none of the other features of the proposed software system would use it.

  Team members proposed various ways of providing easy access. In one case, a key report could be exported as HTML and placed on the intranet. In another case, adjusters could be provided with a specialized query written using a standard software package. All these functions could be integrated by organizing links on an intranet page or by placing buttons on the user’s desktop.

  The team launched a set of small projects that attempted no more integration than launching from the same menu. Several valuable capabilities were delivered almost overnight. Dropping the baggage of these extraneous features left a distilled set of requirements that seemed for a while to give hope for delivery of the main application.

  It could have gone that way, but unfortunately the team slipped back into old habits. They paralyzed themselves again. In the end, their only legacy turned out to be those small applications that had gone their SEPARATE WAYS.

  Taking SEPARATE WAYS forecloses some options. Although continuous refactoring can eventually undo any decision, it is hard to merge models that have developed in complete isolation. If integration turns out to be needed after all, translation layers will be necessary and may be complex. Of course, this is something you will face anyway.

  Now, turning back to more cooperative relationships, let’s look at ways to scale up integration. .
. .

  Open Host Service

  Typically for each BOUNDED CONTEXT, you will define a translation layer for each component outside the CONTEXT with which you have to integrate. Where integration is one-off, this approach of inserting a translation layer for each external system avoids corruption of the models with a minimum of cost. But when you find your subsystem in high demand, you may need a more flexible approach.

  When a subsystem has to be integrated with many others, customizing a translator for each can bog down the team. There is more and more to maintain, and more and more to worry about when changes are made.

  The team may be doing the same thing again and again. If there is any coherence to the subsystem, it is probably possible to describe it as a set of SERVICES that cover the common needs of other subsystems.

  It is a lot harder to design a protocol clean enough to be understood and used by multiple teams, so it pays off only when the subsystem’s resources can be described as a cohesive set of SERVICES and when there are a significant number of integrations. Under those circumstances, it can make the difference between maintenance mode and continuing development.

  Therefore:

  Define a protocol that gives access to your subsystem as a set of SERVICES. Open the protocol so that all who need to integrate with you can use it. Enhance and expand the protocol to handle new integration requirements, except when a single team has idiosyncratic needs. Then, use a one-off translator to augment the protocol for that special case so that the shared protocol can stay simple and coherent.

 

‹ Prev