Four Event Driven Patterns
In this story I would like to explain my understanding of the Event Driven Architecture(EDA) and its patterns. I was assigned to introduce the CQRS pattern in my previous company. That is how I got to know about EDA. EDA comes with a lot of concepts like Events, Commands, Event sourcing, Aggreate, Evet Reaplay etc.. . I’ll briefly explain what EDA is and what common patterns are.
Event Driven Architecture(EDA)
EDA is producing, captureing , processing and persisting of Events. The concept of the EDA is not new. When we talk about EDA, we can identify two types of objects, those are Event and Command. We cannot do a proper implementation without knowing these two.
Command
Command is a request to a system to do something and sometimes expects a response for that. Command is very specific and it has a known destination. That means we know what is going to process and who is going to execute our command. Let’s take an Ordering System as an example. When we want to confirm an Order, we say Order system to “This is my order and these are my items please confirm it”. We know what is going to be confirmed and Who is going to do it. We can name this command as “ConfirmOrder”. When we translate this into a json it would be something like this.
{ "customerId" : "
77bdf5e5-2bcb-43a9-978c-6ae90d6a08db", "items" : [ { "name" : "Vintage Yamaha FG 180 Red Label Guitar", "quantity" : 1, "price" : 560 } ] }
Very important thing about Command is that it can be failed due to many reasons. If we take the ConfirmOrder command, this can be failed because of not having enough credits in the customer account or payment gateway is not working. Usually out come of a Command is an Event.
Command is a request to a system to do something and sometimes expects a response for that. Command is very specific and it has a known destination.
Event
Event is a change of the application state that has already happened. Event is immutable and we can’t undo events. Events don’t have a known destination. We can think of events as first class types because we can pass events around our services. Unlike commands, events are generated locally by the service. Once an event happened we can’t undo it, only thing we can do is emit an compensation event. Because there could be multiple listeners for that particular event, removing it from our side will not reflect on other services. Event doesn’t have an objective. That means we don’t expect something to happen after we publish it. Other services can accept, reject or choose to react on events. Normally we name events as past time verbs. Examples for event are “Order Confirmed”, “Item Added to the Order” or “Order Rejected”.
Event is a change of the application state that has already happened. Event is immutable and we can’t undo events. Events don’t have a known destination.
Notification pattern
The easiest implementation of EDA is Notification pattern. In notification pattern, one service just notifies that something has happened. Services that are interested in this event have to send a request to get additional data.
Events in this pattern are very light weight. They don’t carry much state, it contains only the basic data. If we take a “OrderConfirmed” event, it would be something like this,
{ "orderId" : "
0bfaab92-504a-4ec1-8c88-0b6df1bf8355" }
If Shipping service wants to get more data, they have to send an additional request for that. But the benefit of this pattern is we can reverse the dependancies.
Let’s say when we confirmed an order, we want to start the shipping process. If we implement this without using Events, we will have to call Shipping service API thus our application depend on Shipping service. Whenever Shipping service changes its API, we have to change our service accordingly. But with event notification we just publish OrderConfirmed event. Then shipping service has to listen to that event. If Shipping service needs additional data, then it will have to send a request to our Order Service. Now our service doesn’t depend on shipping services. Other benefit about this pattern is that we can easily integrate new services into the system without doing any changes.
But this doesn’t quite remove the dependencies between services. Because services can’t do much with the events published with this pattern. They will always have to call an API in the service that the event was published.
Event-Carried State Transfer(ECST)
With Event-Carried State Transfer we can eliminate drawbacks in the Notification pattern. The idea of Event-Carried State Transfer is to add enough state to the events. There is no limit for adding data to an event but we have to be careful not to add unnecessary data. It might confuse other services or it could add security risk.
If we take “OrderConfirmed” event as an example, in ECST pattern “OrderConfirmed” event contains not only order id but also other related data.
{ "orderId" : "
0bfaab92-504a-4ec1-8c88-0b6df1bf8355", "customerId" : "
77bdf5e5-2bcb-43a9-978c-6ae90d6a08db", "items" : [ { "name" : "Vintage Yamaha FG 180 Red Label Guitar", "quantity" : 1, "price" : 560 } ] }
With ECST we send what happened exactly. Event doesn’t have a destination therefore we don’t add state to events thinking about one particular service.
The other important thing we can do with this pattern is, we can completely remove the interaction between services by maintaining copy of the event state. That means “Order service” is going to keep a copy of all the Customer data that it needs. We get two major benefits with this, great decoupling and high Availability. Order service can work even without the “Customer service” because it has all the necessary Customer data.
Downside of this pattern is lack of consistency. We have duplicated data in the Customer service and Order service. Our system becomes Eventually Consistent . But I believe that all the distributed systems are eventually consistent, we cannot achieve strong consistency in distributed system.
We cannot achieve both availability and consistency at the same time in a distributed systems because they should be Partition tolerance so when we achieve Availability we loose Consistency(CAP theorem).
Event Sourcing(ES)
Event Sourcing is storing application state as sequence of events. Application state is not a single recode in the database. It is the sum of all the events that happened in the system. In ES we build application state by replaying events. Event Source is the Single Source of Truth (SSOT). ES is not a new concept, the best example is a bank account. Account current balance is not a column in a table, it is sum of all the past transactions.
With ES we get the ability to build the application state at any point in time. We do this by replaying events from the beginning to the time we want. When an account holder wants to know his account balance two years ago, we can do that when we have a ES based system.
ES gives us to validate application state accurately. Think if our account balance is just a column then there is no way we can know how we got that value. An Account holder can argue that his balance is wrong but in ES we can accurately say how he got his current balance.
Other thing we can do with ES is branching. Normally we append new events to the last event. But we can append new events to any point of event source. This allows us to support branching. We can have different version of the application state co-existing at the same time or we can apply different events and see how current state would be.
CQRS
CQRS means “Command Query Responsibility Segregation”. What it basically says is we can use different models to read than write. The idea of CQRS was introduce by Greg Young. CQRS is originated from Bertrand Meyer’s Command and Query Separation Principle.
It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both. In other words, Asking a question should not change the answer. More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.
This Command Query separation allows us to implement two different requirements. We implement CQRS by creating separate services for read and write. Both services are communicated via Events.
Command side is responsible for handling update requests. We must ensure the Consistency in this. Command side can be implemented using ES. In the most of systems, Command side processes very little amount of requests therefore requirement for scaling is not very heigh.
Read side we often try to optimise the read performance. We can use non-relational database or we can store data in a relational database in 1st Normal Form. Read model is updated by listening to the events published by the Command side. Requirement for scaling is heigh because of that we should implement read side in a way that we can scale easily.
EDA comes with few different patterns. Depending on our use cases, we can choose a pattern that fits. We can use EDA patterns with other architectures like Microservice. Combining EDA and Microservice, we can improve overall system performance. My next article will see how we can combine EDA and Microservice.
Comments
Post a Comment