One of the main challenges of designing distributed systems is how to balance the local and global complexity [Khononov]. In some cases, we might want to reduce the local complexity (and cognitive load) by splitting a single system into multiple smaller ones, that are going to be loosely coupled.
This often brings a problem of managing the specification of behaviours of the subsystems in the context of the whole system. Usually, we want to have the specification consistent between the subsystems, but at the same time keep them loosely coupled and independent.
Examples
Let’s imagine you a corporation that decided to buy a GSuite from Google. When the contract is negotiated all details such as discounts, available services, user and storage limits are agreed. At this point, Google wants to ensure that the configuration is correct and that customer isn’t going to run into any problems after enabling the service. E.g. if we want to have access to Google Docs then we also need to have Google Drive enabled and so on. Once it’s all done, then the account configuration can be published and the users can access services defined in the specification.
Another example might be a company offering a loyalty program as a service. As part of the configuration, the administrator can define territories that the program operates in, enable currencies, payment integrations, rates at which points are accrued and so on. The whole program specification needs to be cohesive and valid before it can be enabled for production use. After enabling we are expecting a set of independent components to communicate with each other in a way that delivers a great customer experience based on the defined specification.
Fan-out Specification Context
What these two examples have in common is the pattern of having a central place to define the specification of how the system should behave, and then it is used by multiple loosely coupled or independent subsystems. I call this pattern a Fan-out Specification Context - a context responsible for defining a valid specification of the expected behaviour of the system and providing it to subsystems that are responsible for the execution.
This pattern is closely related to the Specify/Execute/Analyse/Optimise [Tune] or Draft/Execute/Audit [Brandolini] patterns that are already used in the Domain-Driven Design community. These patterns focus on a single path of execution: the Specification/Draft part is all about defining what needs to be done, Execution about doing it, and Analyse/Audit inspects the results.
The Draft/Execute/Audit approach plays well together with the Fan-out Specification and both can be combined to orchestrate the whole set of expected behaviours of the system. On top of that, we can add versioning of the specifications and let the Execution Contexts run specific logic depending conditions such as feature flags, dates etc.
Warning: a Brain Context (anti) pattern
The thing to look out for when implementing the Specification Contexts is to make sure that they do exactly that - define the specification. The execution must be left to each respective service. If the execution of the logic gets moved into the Specification Context (e.g. central “rules engine”), then we might end up with a Brain Context [Tune] which can be considered an anti-pattern. To mitigate the risk of ending up with the Brain Context avoid the temptation of implementing and executing business logic in the Specification Context.