One of the debates that’s been smoldering since the microservices movement began is how microservices should communicate with one another.
In the James Lewis and Martin Fowler bliki post that popularized the approach, they said this about communication protocols:
The two protocols used most commonly are HTTP request-response with resource APIs and lightweight messaging.
Since that post, a schism has seemed to form between microservice practitioners who promote the use of HTTP APIs and those who promote the idea of event-driven architecture, adapted as “reactive microservices.” The first sect cites the robustness of the RESTful API ecosystem as a strength that outweighs any concerns around latency and blocking. The second sect has strong arguments around the power of event sourcing in a distributed architecture and points to the increasing popularity of Apache Kafka as a vote in their favor.
As with most technical arguments like this, I believe the right approach is to accept the reality that you will need to apply both synchronous and asynchronous patterns in your service interactions, and the right thing to focus on is learning when and where to apply which. In my experience, it helps to step out of the protocol weeds when modeling systems of microservices. The fact is that you can implement asynchronous interactions over HTTP, just as you can implement synchronous interactions over a message bus. Not that you would, but that just underlines the point that system engineering can best be done when the implementation details are abstracted away.
There are some exciting approaches to this type of systems architecture out there. Domain Driven Design (DDD) – initially conceived at the height of object-oriented programming – has had a tremendous revival in popularity in the microservices movement. It provides an implementation-agnostic approach to modeling complex software systems, and its “context mapping” is particularly useful in this regard. Alberto Brandolini’s “Event Storming” grew out of the DDD community as a collaborative approach to identifying service boundaries and interactions, and – as the name applies – has a particular focus on events. I advocate these DDD practices, but I don’t think they focus enough on the practical aspects of how services and domains interact.
Software legend Alan Kay once lamented coming up with the concept of object-oriented programming, saying:
I’m sorry that I long ago coined the term ‘objects’ for this topic because it gets many people to focus on the lesser idea. The big idea is ‘messaging’…
The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.
Given the significance of inter-service communication, I think more emphasis needs to be placed on the arrows in whiteboard diagrams than on the boxes. To help, here’s a simple way to classify the interactions you can have between services. In my experience, there are really only three types of interactions:
Here’s a simple analogy going through a coffee order…
Query: “How much is it for a half-caf latte with a twist of lime?”
Command: “I would like to order a half-caf latte with a twist of lime, please” [pays]
Event: “Matt, your half-caf latte with a twist of lime is ready!”*
The protocol is abstracted in this example too. The query and command could have happened in person, or on a mobile app. The event notification could have been shouted across the cafe or buzzed on the phone. No matter how the steps are performed, they represent the consistent flow of interactions you would follow in the task of obtaining the coffee.
I’ve embedded these interaction types in the microservice design canvas I created two years ago, and since then it has helped microservice implementers bring clarity to their interface definitions. Interaction design first, interface definition second. As for the protocols, once the interactions and interfaces are set, have at it! I believe that with the continual optimization of HTTP – see HTTP2 – and the increasing robustness of the asynchronous protocol ecosystem – see the AsyncAPI specification – the gaps are narrowing on all fronts. It will become obvious that the right approach is not to choose one or the other but to find the one that fits a particular interaction best.
* I would never drink this…
To learn more about design principles for a microservices architecture, check out our whitepaper, microservices best practices.