[Book Club] Domain-Driven Design Chapter 14 - Maintaining Model Integrity
These are my notes from a private book club with friends. The earlier chapters cover software design principles that one person can apply alone. This chapter is about collaboration between several teams on a large system. As a software system grows, it may depend on other systems, or it may need several teams to build it. That situation calls for strategic design at a higher level.
DDD draws the boundaries with the Context as the unit. There is no fixed procedure for that division. Besides the split of the domain itself, you also consider how the teams divide the work. My own feeling is that the same project can be cut in different ways at different times and in different circumstances. This chapter covers development inside one Context and collaboration between several Contexts, as a reference for planning.
Bounded Context
A Bounded Context must focus on the current situation, not on the ideal state of the system.
Example:
- Background: an internal project at a shipping company
- Contexts:
- The legacy booking system - it bridges the boundary between the old system and the new one
- The new booking system - it only cares about the content of its own Context. The conversion between old and new sits outside the Context, so the team does not consider it during development.
- The voyage scheduling system - it overlaps to some degree with the concepts in the new booking system, but it belongs to a different Context. The team should revise the duplicated parts first, and share them only after that.
Inconsistency inside a Context
- Duplicate concepts
- False cognates
Integrating Contexts
- Continuous Integration The scope is a single Context.
- Use reproducible merge and build techniques
- Use an automated test suite
- Manage the life cycle of the components
- Use the Ubiquitous Language
- Context Map A Context Map describes the contact points between the models. It states every translation that the communication needs. It also highlights the content that the Contexts share. Describe the current state first, and change it later. Example:
- Voyage scheduling and route calculation
- Route Specification -> Place List
- Node -> Itinerary
- You do not need to map every concept in the two Contexts.
- The tests at the contact points matter a lot.
- Voyage scheduling and route calculation
Organizing the Context Map
- Strategies when you must integrate
- Shared Kernel
- A Shared Kernel stops the Contexts from drifting apart and becoming hard to integrate.
- It also saves the team a lot of effort on a translation layer.
- The team extracts the shared core logic.
- The Contexts need integration, but not as often as Continuous Integration.
- Customer/Supplier
- The downstream component depends on the upstream component. The dependency goes one way.
- The relationship must be a real customer and supplier relationship. The outer component gives feedback, and the inner one adjusts accordingly.
- Tests
- Conformist
- The downstream team can only follow the upstream team.
- The Conformist pattern assumes that the quality of the upstream model is not too bad.
- The result is not optimal, but it lowers the cost of integration.
- Anticorruption Layer
- An Anticorruption Layer stops the system that you must integrate from leaking into the new system. It is defensive.
- Integration with an existing system is a form of value reuse.
- The layer is a mechanism that translates value between different models and protocols.
- It uses a Facade or an Adapter, a translator, and a communication transport mechanism.
- Consider the Conformist pattern when a large-scale translation makes the system close to the external system that it depends on.
- Shared Kernel
- Strategies when you do not need to integrate
- Separate Ways
- Integration costs a lot, and the benefit is small.
- The systems do not call each other, and they share no data during operation. A shared use case does not mean that they must integrate.
- Models that grow in isolation can be very hard to merge later.
- Separate Ways
- Patterns that raise the level of integration
- Open Host Service
- A subsystem must integrate with several systems. A custom integration for each one slows the schedule down and is harder to maintain.
- Design a clean protocol that is easy to extend.
- This pattern fits only when you can describe the resources of the subsystem as one cohesive set of services. The subsystem must also take part in many integrations.
- Published Language
- A direct translation between two Contexts makes one side depend on the other. That dependency can lead to a rigid design.
- A Published Language belongs to neither model, but both models apply the same principles to it.
- System A sends its data to system B in JSON format.
- Open Host Service
Model Integration
- Several Contexts can hold conflicting parts. Accept that the domain models conflict with each other.
- The Context Map reflects the current situation, and you adjust from there.
- During development, watch for the moment when you cross the boundary of the application.
- An external system does not always form a complete Context on its own.
- The Context division inside a system goes from one to many as the system grows.