Promoting inter-service collaboration
In Chapter 1, Architecting for Innovation, we saw that the role of architecture is to enable change so that teams can continuously experiment and uncover the best solutions for their users. We enable continuous change by defining fortified boundaries around things that change together so that we can control the scope and impact of any given change. The key to defining these boundaries is to understand the driving force behind change.
In Chapter 2, Defining Boundaries and Letting Go, we found that people (that is, actors) are the driving force behind change. We identified a set of autonomous service patterns that support the different kinds of actors so that each service is responsible to a single actor. In this chapter, we will dig into the details of the Control Service pattern.
Control services work between the boundary (that is, Backend for Frontend (BFF) and External Service Gateway (ESG)) services as mediators, to promote collaboration between the different actors. They embody the rules and business logic that constitute the policies of the organization. These policies are apt to change frequently as the business adapts to its dynamic environment. Thus, control services are responsible to the stakeholders who own these policies.
Choreography’s pros and cons
One way to understand the value of control services is to look at the pros and cons of the alternative. In an event-driven system, services collaborate by exchanging events. Choreography is by and large the most common form of inter-service collaboration. It is characterized by the lack of a central coordinator. Upstream services emit events, and downstream services react and produce more events. We design and assemble a sequence of these interactions, and the individual services control the flow.
The following diagram depicts a choreography that promotes the flow of data through a system so that we can materialize data in downstream services. Following our food delivery example, when a restaurant updates its menu, other services react to keep their cache synchronized:
Figure 8.1: Data flow choreography
The restaurant’s system invokes the external interface provided by Restaurant ESG to produce a menu-updated event. The ESG does not explicitly know what downstream services will consume the event. We designed the Menu BFF to react to this event so that customers can order from the most up-to-date menu. The Restaurant BFF enables system administrators to make menu corrections. As such, this BFF will both consume and produce the menu-updated event.
This data flow example highlights some of the benefits of the choreography approach. Upstream and downstream services are not explicitly coupled to each other. Upstream services can publish an event and do not expect a response. We can add or remove downstream services over time, and multiple services can play similar roles. Any number of services can participate in the collaboration, so long as they uphold the event contract. Choreography is the best approach for implementing these data-synchronization flows.
The following diagram depicts a choreography that promotes the flow of control through a system so that multiple services can collaborate to perform the steps of a business process. Following our food delivery example, when a customer submits an order, the restaurant needs to receive the order and communicate that the meal is being prepared:
Figure 8.2: Control flow choreography
Checkout BFF produces an order-submitted event. The Restaurant ESG egress flow reacts by invoking the endpoint provided by the specific restaurant. The restaurant’s order system receives the order, and then it invokes the endpoint for the Restaurant ESG ingress flow to send confirmation and produce an order-received event. Tracking BFF and Notification ESG each consume the order-received event and make the information available to the customer.
In this control flow example, the choreography approach works perfectly well up to this point. But the business process is far from over, and things get much more complicated from here. For example, the order-submitted event also needs to trigger the selection of a driver, and we need to track the driver’s status. It might be straightforward to use choreography for this happy path as well, but the driver functionality becomes much more complicated as we include all the alternate paths and error paths.
It wouldn’t be reasonable to put all this logic in Driver BFF. Driver BFF will change with the needs of the drivers, and the functionality for selecting drivers will change with the needs of the business. Furthermore, Tracking BFF and Notification ESG should just be observers of the driver functionality and consume a generic status event that shields them from the details of how the status is determined. Thus, a service that sits in the middle and acts as a mediator between the boundary services may be a better approach when the business logic is more intricate.
Choreography has the advantage of being straightforward to implement. It is perfect for data synchronization and observation flows. It works well for basic control flow scenarios but may not scale well for more complex scenarios. As the highly interrelated control flow logic spreads across the system, choreography becomes harder to understand, trace, debug, test, and maintain.
Instead, we can move control flow logic upstream and consolidate it in dedicated control services. These services consume lower-order events, evaluate rules, and produce higher-order events. They come in many variations, such as business process orchestration, sagas, CEP, ACID 2.0 calculations, and more. It is reasonable to start with choreography and evolve to control services as the optimal solution becomes clear. Ultimately, a solution may warrant a mixture of approaches.
Before we dive into some applications of this pattern, let’s look at the anatomy of a control service.