We have been living in a distributed world for quite some time, and when considering how should you implement the communication between any two parties involved, the discussion seems to gravitate around two options: API or messaging.
Instead of focusing on the lost battle of which one is better, how about we focus instead on looking at their characteristics, what heuristics can we use to help our decision, and ultimately the consequences of choosing one?
Let’s begin by looking at APIs, which seem to be the predominant choice — or at least the first one considered.
Application Programming Interface
APIs define the contract that allows two applications to communicate with each other. This contract is in the form of protocols and styles that the server is committed to providing and that the client should adhere to.
A predominant style found today is REST, having replaced SOAP in this service-oriented architecture that is commonplace nowadays. While it has limitations it is often considered a natural fit for our HTTP culture.
A worthy mention, albeit if being more specific is the Remote Procedure Call (RPC). It is a protocol that enables a client to call a function — a procedure — on a remote server as if it was locally available. The client code is abstracted from the networking details as the protocol takes care of the remote invocation.
Figure 1. RPC flow between the client and server.
The concept of RPC is older than the APIs, with some recent implementations such as gRPC being built on top of HTTP as well.
Independent of which one you choose, they have in common that the client needs to wait for the reply before finishing the execution that originated the call.
Figure 2. The call to a remote service will block the execution that depends on the result.
While modern languages support the asynchronous aspect of calling APIs, delaying the blocking nature, it is still there.
Figure 3. Even with multiple async calls you have to block at one point to use the result(s).
With its simplicity you may wonder why I need any other way of operating. The flip side of its simplicity manifests itself in the fact that there is a chain of availability that needs to be satisfied for every request.
That creates an ever bigger challenge to manage as the number of services increases. All of them must be available and able to handle scaling at the same pace as the client that receives the original request.
With messaging, we shift the concept from client/server pair to a producer/consumer. While this may sound like “a distinction without a difference”, we have two main points to highlight:
• It provides temporal decoupling
Messaging is asynchronous by nature, where the producer of the message being sent (or published), does not wait for it to be received, acknowledged and much less processed by the consumer. To understand more about this and other forms of coupling you can check here.
Figure 4. Send and Receiver are temporarily decoupled.
• It supports a one-to-many semantic
If in our solution the message being sent is an event, we have a potential one-to-many capability where for this single message many different consumers may be interested in it.
Together these two enable your application to handle temporary disruptions in your dependencies, handle spikes of demand in a more graceful way or extend functionality without modifying the sender.
Figure 5. The same message is sent to many different receivers without the sender’s awareness.
Another benefit, If your application has to handle the passage of time, is that you can use messaging to better capture your domain requirements.
Contrary to what many think, messaging does not equate to a fire-and-forget mode as you can use an asynchronous request/reply pattern .
With all those benefits why don’t we only use messaging? Well, as powerful as it is, it comes with new mechanics that if your team does not master can bring you headaches.
Thinking in terms of asynchronous execution is not a natural way for many. Add that you can receive messages out of order or multiple copies , then you have a set of problems that you did not have with the API approach.
Choosing the right one for you
If you are faced with the development of a new service, or have the opportunity to revisit an existing one I recommend you to look at the following criteria, trying to assess each one according to your reality.
- Team knowledge & Tooling
Messaging is a powerful option, but as we saw it has some challenges as it differs from the linear model many may be used to. I do not know about you, but my first experiences were all with the sync model, be it an API or RPC.
If your team has no prior experience with messaging, picking it comes with an additional set of challenges, on top of whatever the functional requirements dictate.
If that is the case, I would recommend at least securing extra time for experimentation and learning in your project estimates. If you can’t guarantee that, understand that there is a higher risk due to the unknowns, and the API path would be a better (or safer) choice, at last for now.
Many of the issues with messaging only manifest themselves at a higher scale, so during development or regular testing, you may think all is way only to find out issues once you go live.
- Do you need a synchronous response
This is an “easy” one. If your application requires the answer from a given request before being able to proceed and you have a time-sensitive case — for example, a user waiting on the UI — API is your choice.
While it is possible to start an asynchronous process and a polling or WebSockets solution to provide feedback, those come with additional complexity that you should assess if really warrants the cost of doing so.
Beware of thinking the synchronous way will always be the case. It may be simply the result of current practices.
- Do you have long-running executions
If the service you need to reach can have long execution times, the client will be in a waiting mode, holding resources, such as memory and TCP/IP sockets. On a small scale, this may be negligible, but as the demand rises you will be begging for those resources.
Additionally, if you use a cloud provider that charges based on the end-to-end execution time, you are going to be paying for all this waiting.
If that is your case, messaging will give you back those resources, so it would be the choice I would prefer, especially if you already have the demand for scalability and/or tight resource constraints.
- Do you have many (non-critical) dependencies
Depending on the granularity of your ecosystem, you may find yourself with many dependencies being reached as part of the execution of a given use case.
While that on its own may be a sign of issues, having many dependencies means you are bound to have to handle the potential failures of any of them.
Upon closer inspection, you may find that you do not actually need the response of some of those dependencies to move forward.
If that is your case, then all those non-critical dependencies may be shifted, making your process more resilient — and potentially faster — for your clients.
Integrating using APIs or Messaging is the common decision you will be faced when developing new services.
As we saw, there is no simple answer to this question as in most cases both can solve your connectivity needs in a similar fashion.
I presented some heuristics to help you with the decision. But remember that they are hardly the only factors and some of them are inherently subjective, such as the team expertise or the risk of introducing a novel technology/toolchain.
While I tend to use more and more messaging, the question I recommend asking is “Do I need the benefits that messaging can provide?”. If the answer is no, then API would be my selection for the mental model simplicity.
If your ecosystem is complex enough, chances are you will have both APIs and messaging working together. Take the time to dig deeper into both so you are ready to leverage all that they can offer.
- Understanding Coupling with Event-Driven Architecture
- Handling Complexity: Using Sagas to Provide Transactional Support for Distributed Systems
- Handling Eventual Consistency with Distributed Systems