Microservices — When to React Vs. Orchestrate
Most of us are familiar with the main concepts of microservices and their benefits; however, there is often less consensus on how to properly implement them. As you build an application that uses microservices, you’ll need to make decisions on how the microservices interact. A common question that comes up during these discussions is, “Should I use orchestration or a reactive approach in my application? And is it possible to use both?”
As with any technological decision, there are pros and cons to each approach that need to be evaluated with your specific project in mind. In this article, we will discuss each of these patterns in detail, along with some examples of when it’s best to use them.
Orchestration Benefits and Tradeoffs
Orchestration is the traditional way of handling interactions between different services in Service-Oriented Architecture (SOA). With orchestration, there is typically one controller that acts as the “orchestrator” of the overall service interactions. This typically follows a request/response type pattern.
For example, if three services needed to be called in a particular order, the orchestrator makes a call to each one, waiting for a response before calling the next.
- Provides a good way for controlling the flow of the application when there is synchronous processing. For example, if Service A needs to complete successfully before Service B is invoked.
- Couples the services together creating dependencies. If service A is down, service B and C will never be called.
- If there is a central shared instance of the orchestrator for all requests, then the orchestrator is a single point of failure. If it goes down, all processing stops.
- Leverages synchronous processing that blocks requests. In this example, the total end-to-end processing time is the sum of time it takes for Service A + Service B + Service C to be called.
Reactive Benefits and Tradeoffs
When building a microservices architecture, we want to avoid creating dependencies within each microservice; meaning each service should be able to stand on its own. Reactive architecture patterns solve for some of the orchestration challenges listed above.
I tend to think of reactive architecture as an event-driven architecture pattern applied to microservices. Instead of having a central orchestrator that controls the logic of what steps happen when, that logic is built into each service ahead of time.
Think of it as coordination or choreography. The services know what to react to and how, ahead of time, like a marching band performing their big number after months of practice. Services use an event stream for asynchronous communication of events. Multiple services can consume the same events, do some processing, and then produce their own events back into the event stream, all at the same time. The event stream does not have any logic and is intended to be a dumb pipe.
The asynchronous nature of a reactive architecture removes the blocking, or waiting, that happens with orchestration (request/response) type processing. Services can produce events and keep processing. Using an event stream for this enables communication between producer and consumers to be decoupled — the producer doesn’t need to know if the consumer is up and running before they produce an event, or if the consumer received the event that was produced.
Also, in some cases, producers may want to direct commands to a specific service and receive acknowledgement that the consumer received it. Additionally, consumers/producers may want to consume/produce events from/to the event stream. This is a valid pattern and often you will find both approaches used together in a reactive architecture.
- Enables faster end-to-end processing as services can be executed in parallel/asynchronously.
- Easier to add/update services as they can be plugged in/out of the event stream easily.
- Aligns well with an agile delivery model as teams can focus on particular services instead of the entire application.
- Control is distributed, so there is no longer a single orchestrator serving as a central point of failure.
- Several patterns can be used with a reactive architecture to provide additional benefits. For example, Event Sourcing is when the Event Stream stores all of the events and enables event replay. This way, if a service went down while events were still being produced, when it came back online it could replay those events to catch back up. Also, Command Query Responsibility Segregation (CQRS) can be applied to separate out the read and write activities. This enables each of these to be scaled independently. This comes in handy if you have an application that is read-heavy and light on writes or vice versa.
- Async programming is often a significant mindshift for developers. I tend to think of it as similar to recursion, where you can’t figure out how code will execute by just looking at it, you have to think through all of the possibilities that could be true at a particular point in time.
- Complexity is shifted. Instead of having the flow control centralized in the orchestrator, the flow control is now broken up and distributed across the individual services. Each service would have its own flow logic, and this logic would identify when and how it should react based on specific data in the event stream.
Many developers have found that a one-size-fits-all approach doesn’t work well in software application architecture. I believe this is especially true with reactive and orchestration patterns. What do you do if your use case falls in the gray area with some characteristics of both reactive and orchestration patterns? For example, perhaps you have a mix of synchronous and asynchronous processing; either synchronous blocks of asynchronous activities or vice versa. In these situations, I believe there are one or more hybrid patterns that can add value and should be considered for your project.
One design principle to consider when using a hybrid approach is to keep the main interaction between services reactive to achieve higher levels of asynchronous processing. Doing the opposite (using orchestration patterns between services and asynchronous patterns within a service) limits the overall amount of asynchronous processing you can do. Below, we will walk through two different hybrid patterns that use reactive patterns along with orchestration.
Hybrid #1 — Reactive Between and Orchestration Within
The first hybrid pattern uses reactive between services and orchestration within a service. In this example, Services A, B, and C are reactive to one another. Service A consumes an event that triggers it to orchestrate calls to additional Services D, E, and F. Those additional service calls could be asynchronous or synchronous. Service A then produces an event with the result from those three service calls.
- The services are decoupled (but not the services within Service A).
- Asynchronous processing is enabled by leveraging events between services.
- The overall flow is distributed. Each service contains its own flow logic.
- Within Service A there is coupling with Services D, E, and F.
- Depending on the design, within Service A there could be synchronous processing that blocks requests.
Hybrid #2 — Reactive Between and a Coordinator to Drive Flow
The second hybrid pattern uses reactive between services and a coordinator to drive the flow. In this example, the coordinator is like a reactive orchestrator. It uses the concepts of commands and events — commands being things that need to be done, and events being things that have been done. The coordinator produces commands to the event stream and the respective microservices that are preprogrammed for the commands consume the command, perform some processing, and then produce events to the event stream. In this example, services A and C kick off at the same time. The coordinator consumes events from the event stream and reacts to the events it cares about as necessary.
- The services are decoupled (but there is a degree of coupling between the services and the coordinator).
- Asynchronous processing is enabled by leveraging events between services.
- The overall flow can be seen in one place in the reactive coordinator.
- The coordinator does have coupling with the services — specifically with needing to know what commands a service needs in order to react.
- If the coordinator goes down, the entire reactive system can be impacted.
When to Use Orchestration Vs. Reactive Vs. Hybrids
So should everything be reactive going forward? Is orchestration a thing of the past? In working through different use cases, I’ve found there are valid scenarios where each pattern makes sense. Here we will walk through some hypothetical conditions where pure reactive, pure orchestration, or a hybrid pattern could apply.
Pure Reactive Use Cases
- If all or most of your processing can be done asynchronously. The reactive architecture pattern is a great fit for processing that needs to occur in parallel. If minimal to zero of your processing can be done asynchronously, then the reactive pattern may be excessive.
- If decentralizing the flow into each service is manageable. Correlation IDs can be used to re-create a centralized view for monitoring and auditing.
- If speed to market is a priority. Combining microservices with a reactive approach helps maximize decoupling and minimize dependencies, enabling products to get to market faster.
Pure Orchestration Use Cases
- If all or most of your steps have to be done in a sequential fashion, with minimal to zero opportunities for parallel processing.
- If there is a desire to keep the overall flow control centralized. This one can be tricky but I can see a scenario where centralizing the flow could make sense. Specifically, if the ability to see the end-to-end flow in one place is a high priority at both design-time and run-time. One condition where this can be true is if you have hundreds of services each with multiple different flows based on what is being processed. In this case, it may be easier to manage the flow in a central place rather than distributing it.
- Decoupling is not a priority.
Hybrid Use Cases
When to use reactive between services and orchestration within a service
- If all or most of your processing can be done asynchronously.
- If decentralizing the flow into each service is manageable.
- If speed to market is a priority.
- If there are sequential steps that only apply within a service, not across services.
When to use reactive between services and a coordinator to drive the flow
- If there are synchronous blocks of asynchronous processing.
- If the overall flow could change based on the data being processed, and that flow could involve several hundred microservices.
- If there is a need to see the overall all end-to-end flow at design-time and run-time.
- If there is a need to decouple as much as possible to eliminate dependencies.
Overall, all three categories of patterns — Orchestration, Reactive, and Hybrid — can apply in different scenarios and use cases. As such, none is necessarily better than the others or applicable for all needs. If you ask, “Should I use orchestration or a reactive approach in my application? And is it possible to use both?” my recommendation will be to evaluate using a reactive architecture first, then work backwards from there if it’s not a fit for your use case. This will help guarantee that you’re picking the best architecture for your needs, with a properly evaluated set of pros and cons specific to your project.