Escrito por Rafael Ritter,

5 minutos de leitura

DDD Model Integrity Patterns

The main goal of strategic Domain-Drive Design is to help dealing with large models, and its challenge is to achieve modularity without compromising the benefits of integrating different business operations.

Compartilhe este post:

A navigation map for model integrity patterns, by Eric Evans

 

Strategic Design

Domain-Driven Design (DDD) tackles software complexity at different levels. Strategic design is the subset of DDD that operates at a higher level. Its main goal is to help dealing with large models, and its challenge is to achieve modularity without compromising the benefits of integrating different business operations. Because in large systems such design decisions must be negotiated between teams, strategic design does not take into account only technical considerations, but it often becomes the intersection between design and politics.

Defining explicit context boundaries is not only one of the essential aspects of DDD, but also, along with core domain distillation and large-scale structures, one of the keystones of strategic design. While it is theoretically possible to have a single, consistent model for the whole enterprise, this approach is rather complex and generally impractical. Therefore, letting each team develop its own models and explicitly limiting their applicability is the viable way to go. This pattern is commonly known as bounded context.

 

Bounded Context

A bounded context sets explicit boundaries in terms of team organization, code base, database schema and so on. In different contexts, different concepts, rules and terminology may be used. This helps in reducing cognitive load and avoiding confusion when shifting attention from one context to another. Furthermore, the correct split of the enterprise system in bounded contexts is fundamental to maintaining model integrity. A bad split of contexts, mixing up elements of distinct models, may cause two kinds of problems:

  • Duplication of concepts: When there are two model elements (and corresponding implementations) that represent the same concept.
  • False cognates: When two people use the same term (or implemented object) thinking they are talking about the same thing, but they are not.

 

Even though bounded contexts do contribute to modularity, this pattern differs from modules/packages. Objects from different bounded contexts will indeed be placed in different modules, but a single bounded context will also be composed of multiple modules, which will help organize the elements within that model.

 

Continuous Integration

Decoupling distinct models is necessary but not sufficient. Still there will be multiple people working in the same context, which might produce clashing understandings of the same model. In order to ensure consistency within a single context, applying the well-known process of continuous integration can be of great help. Domain-driven continuous integration implies not only an integration of the implementation, using an automated merge/build/test pipeline, but also an integration of model concepts. Concepts can be integrated by the constant exercise of the ubiquitous language in discussions between team members about the model and the application.

 

Context Map

Maintaining integrity within a single model is also very important, but still not enough. Everyone in the project has to be aware of other contexts as well, and relationships between contexts must also be clear to everyone. That’s where the context map comes into play. The context map identifies every model in the project and defines their bounded contexts, naming each of them and making their names part of the ubiquitous language. The point of the context map is not to envision the ideal scenario, but to map the current situation of the project. Once you can see the actual situation, changes and improvements in the state of affairs will become clearer.

The context map can take many forms. It can be a diagram, a textual description or even a discussion between the team. The important thing is that it is shared and understood clearly by everyone.

 

Relationships between Bounded Contexts

Several patterns describe the relationships between bounded contexts and their models. Which one to use depends heavily on the situation and the needs present. Here is a list of some of the most relevant among these patterns:

  • Shared Kernel: When two teams agree to share part of the domain model, along with the corresponding design and code. The benefit of this approach is reducing duplication and facilitating the integration between the two subsystems, but the price to pay is increased coupling, because the shared kernel cannot be changed so freely, since such changes affects both teams and have to be discussed and decided together.
  • Customer/Supplier Development Teams: This is the relationship between an upstream and a downstream subsystem (the downstream system depends on the upstream). These subsystems naturally make up two distinct bounded contexts. Because it is a customer/supplier relationship, the needs of the downstream should be negotiated with the upstream, and it should be explicit which tasks will be prioritized and which will be deferred, so that the expectations of what will be delivered during the iteration process are aligned.
  • Conformist: In this relationship, the downstream adopts the model and the ubiquitous language of the upstream team. Similar to sharing a kernel, the benefit of conforming is facilitating integration by eliminating the complexity of translation between bounded contexts, but the drawback is again increased coupling, since the dependency on the upstream is deepened and the downstream system is limited to the capabilities of the upstream model.
  • Anticorruption Layer: The goal of the anticorruption layer is to avoid corrupting the model of the downstream system when it needs to integrate with an upstream system that has a different model, usually a legacy system or a system with a complicated and messy interface. The idea is to create a layer whose responsibility is to translate from one model to another, providing clients with an interface in terms of their own domain model. That interface can be implemented as a set of services. Internally, one way of implementing the anticorruption layer is by combining a set of façades, adapters and translators.
  • Separate Ways: Sometimes the value of an integration is overestimated and its cost is underestimated. Going separate ways is a possibility that should be seriously considered in such cases. This pattern simply defines a bounded context that is completely decoupled from the rest.
  • Open Host Service: This pattern defines a protocol that gives access to the system in question as a set of services and opens it so that all who need to integrate with it can use it. Open host services can be implemented as microservices or Service-Oriented Architecture (SOA) services. In fact, the motivation behind this pattern is the same behind SOA’s preference for intrinsic interoperability over custom integration: [2] simplifying communication between subsystems and avoiding integration complexity. The adoption of the open host service pattern implies that some part of the model will be shared: the service interface. As a result, downstream systems become coupled to the model of the open host, and downstream teams are forced to learn the language used by the host team.
  • Published Language: This one is essentially a shared domain model in the form of a language definition. One of the problems that this pattern solves is the exchange of information between businesses, which are unlikely to adopt each other’s model. By publishing a language, a world of possibilities opens up for software development. The published language allows for the creation of generic tools that interoperate with multiple systems, instead of working only with a single one. If you wish to make use of such tools, just write your data according to the published language format. An example of published language is Portable Game Notation (PGN). [3] PGN is a published language for recording chess games. Chess games in PGN format are produced and consumed by most chess software. As with some other relationships, this pattern also simplifies integration and interoperability between systems.

 

Conclusion

Maintaining model integrity is no easy task. Several patterns from DDD aim to achieve this goal, which is one of the main goals of strategic design. Whether it is maintaining integrity within a single bounded context through the application of continuous integration or between different contexts by explicitly limiting their applicability and establishing their relationships, these patterns and techniques help a great deal in keeping models pure, and therefore potent. Thus, take them into account when dealing with large models and systems. You are most likely already applying some of them. Understanding them and mapping them explicitly will certainly help you make better choices regarding your software and improve it continuously.

 

References

Evans, Eric. Domain-Driven Design: Tackling Complexity in the Heart of Software.
SOA Manifesto
Wikipedia: Portable Game Notation

 

Leia outros conteúdos do autor.

Compartilhe este post: