One of the first decisions that needs to be made when building microservices is how to manage service-to-service communication. The usual answer from many developers is with API calls, but building asynchronous things on top of a synchronous protocol raises further questions. In a recent post on the Nordic APIs Blog, Francisco Méndez Vilas discusses asynchronous API calls as a more appropriate answer to the communications question.
Not to be confused with non-blocking APIs, asynchronous APIs don’t adhere to the client-server model. Everything is server-to-server and any service can initiate communication at any time. This approach aligns with webhooks that were designed to solve the problem of asynchronicity, but they create a dependency between both services that makes it difficult for services to manage failure when the called service goes down.
WebSockets are another potential solution, providing a permanent, bi-directional channel for communications between systems, but you’ll need to define how the message exchange will be done. Additionally, connecting directly to the service creates a dependency, meaning your infrastructure is tightly coupled.
In a choreographed microservices architecture, many of these issues are solved as every service is independent, talking to each other by connecting to the Message Broker and publish/subscribe to particular channels or topics. Even in its simplest form, a message broker routes messages between services via message queues that solve the issues of service failures. Brokers also offer Exchanges that act like small message brokers inside the actual message broker, allowing the workload to be distributed across multiple instances of a single service.
Messages are any piece of information transmitted between services, and while using a broker removes the dependency at the infrastructure level, there may still be dependencies at the program level if your messages stick to the request/response model. The key to choreographed microservices is to see messages as events, rather than orders such as GET or POST in HTTP.
Instead of telling the system what to do, rather tell it what happened. The example in the article is to, when a user signs up to use your app, send a message saying “a user has just signed up”, and include some user info. In this way, the email service will know to send a welcome email to the new user, and the metrics service will know to register the signup into your analytics tool.
This approach effectively lets you decouple your logic, but more complicated instances could benefit from using a different exchange with different topic structures to easily tell the difference between actions and events. These Topics act like channels or routes for a message or exchange. The AMQP and MQTT protocols offer powerful topics structures, but using a naming convention will help to keep things in order. When defining your naming convention, the author recommends that it has two things:
- Versioning by including v1.user.signup, or with a v2 as appropriate, so you will be able to migrate services without any issues
- Use present tense for actions and past tense for events, such as v1.user.signup when a user signs up (action), and v1.user.signedup once the process is finished (event) so you can easily tell the difference between the two.
The rise of microservices has seen engineers continue to rely on the HTTP protocol despite the challenges of maintaining and scaling the whole architecture. Service-to-service communication may end up proving easier to maintain and to develop new features, as is being experienced by Hitch, who are attempting to go fully asynchronous behind the scenes.