Event-based Microservices: Overview
Simple, Scalable, and Robust
What are microservices?
A microservice is something that takes input and gives output. It can almost be thought of as a function. The only difference here is that this function can be run anywhere you want and can be replicated as many times as you want. It can be run on multiple machines, being horizontally scaled to handle more calls and so more data.
Why use them?
The reason a microservice architecture is valued is not solely because of this ability to handle vast amounts of data. It is also this concept that each service is strongly decoupled from each other. They are just black boxes from each other’s eyes.
The decoupled nature comes with a few benefits. Firstly it helps promote a simpler engineering environment. They can be independently edited, as long as the interface (the input and output) remains intact. If changes are made to the outputs a developer, additionally, just needs to understand how changes made affect areas downstream in the application flow. It also results in a more explicit flow throughout the system reducing the likelihood of spaghetti junctions in code.
It allows for focused testing, as each microservice can be tested in isolation. Ensuring that a microservice acts as expected given some inputs. You can then also contract test against the microservices it interacts with, to ensure that the inputs it expects are the inputs it will receive from another service. Beyond this you can test the system as a whole, but with more confidence in the building blocks which make up the system.
A microservice architecture is also un-opinionated. Because they are so decoupled, each service doesn’t even need to use the same language or libraries as each other. If a specific circumstance means another language or library would be more productive, you can use it. You are minimally tied into past technical decisions. One thing to note is that some code may be shared between services in shared libraries, which may affect language decisions.
In summary, the benefits of microservices are:
- Scalable performance
- Simple development
- Robust testing
- Flexible & Language agnostic
There are some complexities to be mindful of when using microservices, for example:
- Complex DevOps setup: There are a lot of services being deployed and run
- Distribution complexities: Service discovery & network disconnects between different services need to be handled
How do they communicate?
We have an understanding of what a microservice is, but how do they work together? If microservices can be run on different machines, how do they communicate?
A simple example of a communication framework is microservices communicating directly via REST endpoints. This allows microservices to communicate over a network, although it has a couple of drawbacks, one of which is slight coupling between services. The coupling comes from microservices needing to know about the microservice they are outputting to. Other drawbacks include; difficulty handling network disconnects and performing service discovery.
We will look at the alternative approach of using event-based communication, which keeps the benefit of loose coupling. In this approach, a microservice receives an input event, processes it, and returns any number of output events. An event bus is then used to transport these output events between microservices.
There is no direct line of communication between microservices to send events. Instead, the event bus takes in output events and provides them to any microservice which takes those events as inputs. Each service knows about the event bus and can subscribe to a selection of events, and produce new events back to the event bus.
Can they persist data?
The event bus comes with another benefit of persisting events, which ensures that even if a microservice is down or disconnected, it will still eventually receive its events. This frees us from having to worry about persisting events. But what if data needs to be persisted outside of an event’s lifetime?
Like normal systems, microservices can also have access to a database to allow saving or loading of data that you want to persist. The main thing to remember when using databases in a microservice architecture is that a database should serve just one microservice. As a microservice touching another microservice’s data breaks proper encapsulation. In the case where data is needed in another microservice, the data should be accessed by the owning microservice, and then sent via an event.
These are some guidelines on how to view microservices:
- Decoupled: A microservice should not know about other microservices, any shared code should be provided via a shared library.
- Encapsulated: Do not directly access another microservice’s database, think of this in the same way as why a class’s member variables should be private.
- Single Responsibility: Each microservice should do conceptually one job, and one job only. There is a skill in deciding what a service should own and when to break out a new service.
- SSOT: The event bus is the single source of truth. If data is contained in an event it should be the most up to date information, this event is the source of truth.
Microservices are like functions, which can run on a cluster of machines; and in event-based systems they communicate by sending messages through a message bus.
If you found this interesting you can read more on event-based microservices here!