One of the leading architecture patterns used with microservices is event-driven architecture.
Event-driven architecture has many benefits over typical request-driven (API-driven) and message-driven architecture which we are going to take a look at today.
When you have a system composed of lots of different microservices, you need a way for them all to communicate with each other.
One way to do that is with messages.
We can have one service add a message to a queue and then have another service that listens to that queue and acts on it.
This is what we call a message-driven system and it differs slightly from an event-driven system but there are similarities.
The main distinction is that, in an event-driven system, a service sends an event after an action has happened.
The service raising the event doesn’t care what happens after the event has been raised. It doesn’t matter if another service is listening to those events or not. If the state has changed in your application an event is raised.
The event might be someone logging in to your application or performing a certain action. The key here is that your application isn’t completely reliant on something else picking up that event, it continues to work without it.
The events themselves are slightly different to messages as well.
A message is usually a command to do something. It is designed for a particular service with instructions on what needs to be done. This could be sending an email for example.
With an event, there are no instructions but instead information on what action has been performed and by who.
Once the message has been processed it gets deleted from the queue, ready for the next message to be picked up.
Events on the other hand are immutable. They can never be changed or deleted. They are a permanent record of what happened in your application.
So we have one service publishing events now we need a way for other services to respond to them.
We do this using an event broker. Your other services subscribe to the event broker and the broker is responsible for pushing the events to the subscribed services.
This is often referred to as the publisher/subscriber or pub/sub model.
When a service subscribes to the broker they specify what types of events they are interested in. It is then the broker’s responsibility to keep track of which events the service has received and which ones still need to be read which is usually done with a checkpoint.
So in an event-driven architecture, there are 3 main components.
Event-driven architecture is best used when a process can be run separately and doesn’t have a direct consequence on the application raising the events.
Examples of where Event-Driven Architecture is useful:
Event-driven architecture has several clear benefits over a simple request-based model.
As the publisher doesn’t need to know anything about the subscribers consuming their events the services are completely decoupled from each other.
Not only that but other services can be created and then read from the same events.
Compare this to a service calling another over API. Service 1 is now highly coupled with Service 2. It needs to know how to call it and the API contract to use. The system is also less reliable. If Service 2 goes down then Service 1 no longer works either.
With an event-based system, Service 2 can go offline and then pick up the events from where it left off.
Event-driven architecture allows you to use dependency inversion. The publishing service isn’t dependent on the low-level components and the low-level component isn’t dependent on the high-level component.
Both components are dependent on an abstraction which is the events being published. As each is dependent on abstraction they can be reused for other purposes or you can replace them with a different implementation without the other service being affected.
The main benefit of event-driven architecture is it allows your system to scale.
If the number of events published by a service increases then you can easily spin up more subscribers to process them.
You can add more functionality to your system such as auditing or data processing without slowing down the publishing service.
Event-driven architecture does have several disadvantages that you need to think about if you are considering using it.
The main issue is data consistency. When you are using an event-based system there is always going to be a delay between when an event is published and when your subscribers pick it up.
This is one of the trade-offs that you have to put up with if you want scalability.
You can greatly reduce the delay by making sure the subscribers are scaled up enough to cope with the number of events. If they fall behind then there is going to be a greater delay.
We call this eventual consistency as your data will eventually be consistent with what is being published.
If you are not careful eventual consistency can hurt users using your application.
For example, say you are running an e-commerce site and you use an event-based system to manage stock numbers. As the system is eventually consistent this could allow 2 people to order the last item in stock.
If the past order history is also based on events they might go to their orders page and find it isn’t there.
If you need to use an event-based system for scalability then you might need to consider adding a cache layer using Redis for example that stores the information for quick retrieval to allow for any delays.
Event brokers keep a checkpoint so they know what events have been received and which ones haven’t. However, for performance reasons, the event brokers don’t update the checkout for every single event.
Therefore if a service needs to be restarted then it will pick from the last checkpoint and as a result, could end up receiving duplicate messages.
You, therefore, need to make sure that your subscribers are idempotent. There is usually a unique ID on each of the events that you can use to make sure that you don’t process the same event twice.
Lastly, Event-driven architecture like most systems designed for scalability is more complex than just a simple request-based system.
You now have multiple components you need to build and there are additional infrastructure requirements. On top of that the system is going to be harder to debug when issues do arise.
If you are not having scaling problems at the moment and it isn’t likely that that is going to change, then in most cases the added complexity of an event-driven architecture isn’t worth it.