Event-based Microservices: Application Flow
In this post, we will look at what application flow is and how it can be defined. We will then discuss where this logic could live by comparing choreography and orchestration.
What do we mean by application flow?
Application flow is the flow of execution through your system. It describes how some input events progress through the system, going through a number of microservices in the correct order.
Firstly we need a way to determine from an event where it is in the flow, essentially why it has been produced. Each event has a type, but this might not contain enough information. For example, if an event fails and we want to retry this event by firing it back through the system, but we only want to retry it 3 times. How can we determine from the type alone how many times it has been retried, as this type will be the same every retry?
In order to describe where an event is in the flow, a string can be added to an event’s data. We will call this string identifying an event’s position in the flow a ‘flow node’. Now accurately knowing where an event has come from allows us to determine where an event should go next. We can think of the flow through the system as a graph of these flow nodes.
Thinking of the system flow as a graph will help us think about the ways in which we can define it.
Where should the flow logic live?
Given the above, we can see that there is a reasonable amount of logic around handling application flow. There are two ways in which we can control the flow in the system, choreography and orchestration:
- Choreography spreads this flow logic throughout the system, giving control to each microservice
- Orchestration puts this logic in one place—centralising control of the flow logic
Below we will look at both solutions in more detail.
This is likely the first one that came to mind if we think of this from a microservices point of view. We have microservices and they output their events to any microservice which subscribes to those events. Together, all the microservices naturally end up defining the flow throughout the system.
Using this solution (along with the event bus) gives us adequate decoupling between the microservices. As the publish/subscribe nature of an event bus provides a layer between microservices where they don’t directly know about each other. This results in a decentralised solution that fits nicely with microservices.
The only issue here is that if you have fairly complex flow logic that is used over a number of microservices, the microservices could end up duplicating some of this functionality between them. The logic is also spread throughout the system, which could make reading and understanding this logic difficult.
A centralised solution like orchestration doesn’t have the same issues as choreography. This is due to orchestration containing all flow logic in one place. Every time an output event is created, a centralized piece of logic is used to determine where it should go to next.
Orchestration allows easy readability of the flow of the whole system, as it is not spread throughout the system. And large changes to the flow would still only require one section of the code to be changed.
Using a shared library could work, it would be used by all microservices to determine which output events it should send. So when the output of a microservice is generated, it would pass through this library to determine the specific events which should be output. This library would be run on every microservice.
This does solve the problem of similar logic being spread throughout the system but means each microservice relies on a new library which:
- Causes rebuilds to every microservice when this library changes (any time the flow changes)
- Makes it difficult to be language-agnostic, as each language used would need to somehow use this library
How would this look if we contained all the logic in one microservice? Outputs from other microservices would always go to this orchestration microservice, which would then determine where to go next. This would help fix these negatives from using a shared library.
A downside to using an orchestration microservice is that each step of the flow would require an extra event to go through this microservice; potentially doubling the number of events. As this architecture can scale easily this slight performance hit may not be a worry, but it is something to keep in mind. This also adds a single point of failure because if the orchestration microservice goes down the flow of events throughout the system stops.
Pros and cons of each approach
Below is a table showing the advantages and disadvantages of each approach discussed:
With this being said, a hybrid approach of orchestration and choreography can be used as there is nothing forcing us to pick just one of the two. With this hybrid approach, you could have localised orchestration, where only subsections of the flow which are complex could be orchestrated. Other things in the system which don’t fit nicely into the application flow could be choreographed, for example, if we want to consume every event in the system for logging purposes. This gives us flexibility to choose the right solution for different scenarios.
Think of how the flow through your system should be defined; microservices can be choreographed together to naturally define the flow or they could be orchestrated by some microservice or library.
If you found this interesting you can read more on event-based microservices here!