The 'Taxi-Ride' Interaction example
Let's take a closer look at what a REST API is. It's basically an interaction pattern; the way systems can interact with each other.
REST API Interaction:
To understand these two forms of interactions let's consider a equivalent real life use case of a user ordering a taxi ride from an agency.
Now, user asking the question: "What time does my taxi-ride arrive?" In the REST API wording, the user asking is the "consumer" and the agency or person responding is the "provider" (aka "producer")
This real-time interaction shown above matches exactly how a REST API works. And it translates to the following:
Now let’s change the question: “Is my ride ready?”
As the answer is not the expected one, the consumer will continue until they finally receive the expected one. From a human perspective, this situation is quite repetitive and annoying.
Above set of repeated queries from consumer to the producer mimics the following API.
REST API limitations
So, what is the difference between these two examples? It’s time!
The value of information decreases over time. But the decrease in rate is not the same for all pieces of information.
Let's again look at the 'Taxi-ride' example to understand the 'proportionality of the value of information with time'
An estimated arrival time for the cab can be relevant is only before the arrival of the cab.
Most importantly whent the user is actively waiting for the cab in order to reach somewhere in time, nothing else matters than this “your ride is already there” notification. And once the trip starts, this notification no longer has any value.
There are plenty of other real-time scenarios of this kind, few of them are:
With a very high value, for a very short time.
A human could ask “Is my ride ready?”
It’s easy for a machine to provide the state of a resource such as “ready/not ready.” But predictions (“arriving in 10 minutes”) are rare.
To be relevant, it has to be accurate. What if it is not ready at the estimated time? What if it is ready before? This is a very complex problem. And they’re far simpler ways to handle this.
If we could ask “Tell me when it’s ready,” the problem would be solved.
REST API interaction pattern implies the consumer always initiates interaction with the provider. That’s how it works. So, asking to “know when it’s ready” is not possible with the REST API. Guess what?
This is exactly the value provided by event-driven APIs.
Event-Driven API Interaction:
Event-driven API interaction patterns differ from REST API. We will see below, how. There are multiple forms, two of the popular ones are:
Webhook (depicted with the"taxi-ride" scenario)
Let's go back to the "taxi-ride" example we discussed above. And use the "tell me when my ride is ready" interaction pattern.
We can see the difference clearly here. Now the event is initiated by the provider (producer), which is the cab agency in this case. The consumer has to define an endpoint(i.e. URL) that the producer can call in order to send the notification to the consumer. The consumer is notified as soon as the piece of information is ready.
This interaction type is referred to as Webhook and is preferred style for asynchronous API.
This kind of interaction forms the basis of Even-Driven Architecture.
API Streaming (depicted with the"taxi-ride" scenario)
Let’s change the provider capability a little.
Rather than answering “ready/not ready,” now the answer is the current status of the cab-ride.
It’s natural for a machine to tell a resource state.
This would allow another kind of interaction: API Streaming.
And the API version of above is:
The consumer receives each change in state in real time.
Often the Webhook is intended from application-to-application, whereas Streaming is more targeted towards real time interaction with humans at the user end consuming the information directly in realtime.
Now looking at this from microservices architecture patterns standpoint
There are different ways to design microservices, this blog focuses primarily on the microservice architectures patterns, request-driven and event-driven.
What is a microservice?
It is an application which is loosely coupled, highly testable, independently deployed, defining clear business domain boundary and maintained easily by a relatively small team.
Request Driven Microservices
Example e-commerce application
There is a clear control of the flow, looking at the code of the orchestrator, we can determine the sequence of the actions.
Single point of failure If there is a failure in the Orchestrator service, it will be a single point of failure. When this service is down, the entire flow won’t be executed.
Replaying data for recovery not easy There is no easy way to recover the actions by reprocessing failed calls to dependent services.
Services Coupled Tightly (relatively) If one of the dependent services is down, there is a high chance to exclude calls to the other services. Rest API of the dependent services cannot be easily modified. If it is changed, consumers of the API also need to be modified. Therefore, microservices are not loosely coupled.
Let's convert our previous request-driven application to an event-driven e-commerce application.
Advantages of Event-Driven Architecture
Loosely Coupled Services A producer of a message does not need to know which service is interested in receiving it. This makes it much easier to add additional capabilities later on without affecting existing functionality.
Asynchronous When you emit an event, it is asynchronous, meaning that the microservice can immediately continue its work without waiting for the consumer of the event to finish. This means that event spikes don’t slow down user interfaces or other critical functions.
Scalability With microservices focused on doing one thing well and no tight coupling to other services, you can individually scale the services that have the largest workload in order to ensure that each microservice is up to date with its work log.
Recovery Events are point-in-time facts that are easy to store and naturally decoupled from any other data.
Maintainability Events can simply be discarded and re-populated with the new schema by replaying the event log. No more complex data migrations!
No Central Orchestrator There is no clear central place (orchestrator) defining the whole flow.
Rollbacks are complex Managing distributed transaction could be complex. There are multiple services that consume an event, as a result, if an exception occurred in one of the services, what should happen to the entire flow or implementing a rollback process is challenging.
Both patterns have benefits, tradeoffs and their suitability also depend on the use case. There is also a choice of using a hybrid architecture based on application requirements. However, if there is an opportunity to implement event-driven microservice, that will surely provide a good foundation to build loosely coupled microservices.