Commands and events, semantics in an EBA
In one of my previous posts I described the basic pieces of an EBA, and well, unsurprisingly the main piece is the event itself.
Events come and go into the system. They get consumed, they get produced and most likely they get transformed. The event has a meaning, and contains data.
What do events look like? Are there different kinds of events?
This is systems architecture, so the answer contains thin red lines, shades of grey and lots of “depends”. In this post I will try to explain why there are different kind of events, and how they might look like.
Event semantics are important here. And by semantics I mean the “purpose” of the event in the system.
If we have a look at bibliography, blog posts, tutorials (I curated a list of them in the Resources section, at the end of the blog post) we’ll see that we have different opinions, but I think that there is a general consensus on that we can split the event semantics in 3 different types of objects, or messages:
- Events
- Commands
- Data (or Snapshots)
Types of messages
If you are a purist you definitely won't agree that there are different kind of events. In the end, an event is by definition something that happened. So, if we aim to do a 100% pure Event Based Architecture we will only have events that record something that already happened. This is why I'm talking about messages from now on.
This is quite difficult to do, so it’s very usual to organise the messages by their meaning (semantics):
- Event. An action that happened.
- Command. Something that needs to happen.
- Data (or Snapshots). The result of something that happened.
Let’s have a proper look at each one of them.
Events
An event is the reaction to something that already happened. Usually their names reflect the action:
UserCreated
ItemAddedIntoCart
EmailSent
- …
As we can see there’s little left to imagination about what these events mean.
If we designed things correctly in our system, following the Single Responsibility Principle (a Domain Use Case is only implemented in one part of the system), a specific event can only be created in one of the components of our architecture. This means, that this specific component is the single producer of this event.
Ex: The microsystem that handles Users is probably the only component in our architecture that can create users. So, the UserCreated
event is produced in there, and only there.
Given that an event is created by a single microservice and that we are using the Single Responsibility Principle to design our systems this means that events are going to be like this:
Events have one single producer and have one or more consumers.
Commands
It is sometimes very hard to design a full system using only events. Events are reactions to things that happened, but sometimes we want this thing to happen.
In these situations it’s good to send a message with a request for action. We will call this kind of message a command
instead of event, because semantically it has much more sense.
A command is an object that carries information to a specific system that will need to use that information to do an event (exactly what you would do in an RPC or a REST API)
Let’s have a look at some examples of typical commands:
SendEmail
CreatePayment
- …
These commands might seem a bit weird because we’ve been talking that an Event Based Architecture reacts to events, and now we have these “kind of events” that are the other way round, they trigger actions instead of reacting to them.
Maybe your system will not need them. Maybe you just send an email when a very specific action happens. In this situation the EmailService might just be listening to those events, but as the system grows and some backend processes, batch jobs, …. get into play the complexity also increases and it might reach a point where is much better to just have some specific components that work this other way.
Using this pattern offers some great technological advantages. If we need to send several thousand of emails you just need to send the events into the message broker. The commands
will be safe in there and the processing system (in this example, the EmailSystem) will consume them at its own pace. This is much harder to do in a REST environment for example where you need to batch the calls or do them through a period of time in order to not collapse the system.
So, again, if we follow the Single Responsibility Principle we are going to have something like this for commands
:
Commands have one or more producers and they have a single consumer.
A word of warning though, if you have a good look at the previous diagram you’ll see that when any of the producers sends a message to the message broker it does not receive any kind of feedback from the Email Service.
Commands are usually “fire-and-forget”, meaning that you trigger an action, but then you don’t know anything about the action status. We’ll be exploring this in depth in another blog post (feedback systems) but for know, a solution would be something similar to:
Where the EmailService receives commands
and publishes events
as a result. Then a component that really needs to know the outcome of a command
could wait for the corresponding event
. This is conceptually quite easy to understand, but has its tricks. We’ll be talking about that soon.
Data (or Snapshot)
Events
and Commands
types are very easy to understand. One is an action that happened, and the other one is an action that we want to happen.
So, why do we have yet another type of message? the answer is “again, let’s be practical”. Sometimes we want to have that an action happened and this is the outcome of the action.
Is this a “special kind of event”? yes, definitely. But I consider this important enough to be its own type.
We’ll be seeing the structure of the events in the next section, and this is going to become much more clear, but now let’s explain the semantics of this type.
In our system we have already seen the UserService
and the UserCreated
event.
The UserService
will be most likely a CRUD system that handles the creation, updates and deletion of Users. As a result it will have these events:
UserCreated
when a user has been created.UserUpdated
when a user has been updated.UserDeleted
when a user has been deleted.
A consumer would receive these events and gain knowledge about the action that happened, but most likely that consumer will also need to know the resulting state of the event. So, for UserCreated and UserUpdated the consumer is most likely interested in the User that got created, or the User that got updated.
So, these kind of event just sends the latest snapshot of a domain entity (in the previous example, the user)
To sum up
Type | Semantics | Producers | Consumers | What are they good for ? |
---|---|---|---|---|
Event | An action that happened. | Usually one single producer. | Usually more than one consumer. | Notify to the rest of the system that an important domain action happened. |
Command | An action needs to happen. | There might be several producers sending these messages. | One single consumer. It’s the system that will execute the action. | We offer a single point to execute actions. Like a REST endpoint really. |
Data / Snapshot | The result of an action. | Usually one single producer. | Usually more than one consumer. | Sending the latest state of a domain entity that has been created or modified as a result of an action. |
Structure of the messages
By now we should have a clear understanding of the three different kind of “events” that we might design into our system. The next natural question is “how do they look like”?
I like to organise them in:
- Thin events
- Fat events
- Delta events
- Snapshots
- Commands
They have lots of similarities and they are not mutually exclusive. Let’s have a proper look at them.
Thin events
A thin event is a lightweight format that contains just the basic information that you need in order to inform the rest of the system that something happened.
What do we need exactly:
- Action that happened: Was it a creation of a resource, was it an update of a resource, …
- ID of the resource that was affected.
It could be something like this:
{
"resource_id": "xxxxxxx",
"action": "creation"
}
So, any consumer receiving this will know that resource “xxxxxxx” got created. Enough? it depends of course on your system.
Some systems might just need this, but some other systems might need to have more knowledge about the resource that got created. If you’re using these kind of events, then the consumer would need to go to the system that created this resource with the received ID and fetch it.
The action is very important, in this example I’m just using a CRUD system, but take into account that this could be a business event:
PaymentRefused
MessageSent
OrderReceived
- …
These events are lightweight, but I haven’t seen them much as, more often than not, the consumers expect more information.
Fat events
A fat events is basically a message that contains all the information that consumers might need. This information might be a lot.
If we get the previous example we could have something like:
{
"resource_id": "xxxxxxxx",
"action": "creation",
"data": {
"username": "joe",
"user_id": "xxxxxxxx",
"age": 29,
...
},
"version": 1
}
The main difference is that the event itself contains all the resource data (for example). It might have additional information.
The greatest advantage is that the consumer does not need to go back to the producer in order to get more information because the producer put all the known information into the event.
You’ll see that there is a version
field. While this is optional I think it’s very important. One of these days I’m going to write a post about the eventual consistency of these architectures, but for now trust me. Add a version field.
Of course, the disadvantage of these kind of events is that they are far bigger. While this might be fine in some environments it might be challenging in situations where there are lots of events, or where the events are very big.
Delta events
A delta event is a special event that sends what changed to that specific resource. Looks something like this:
{
"resource_id": "xxxxxxxxx",
"action": "update",
"data": {
"age": 30
},
"version": 2
}
Something updated resource “xxxxxxxxx”, changing the age from 29 to 30. So, the event only contains the data that changed.
This same event if we were using a more “fat event” approach would be:
{
"resource_id": "xxxxxxxx",
"action": "update",
"data": {
"username": "joe",
"user_id": "xxxxxxxx",
"age": 30,
...
},
"version": 2
}
As you can easily see, the delta event is much shorter as it’s not duplicating data that is not interesting, as it did not change.
Let’s take something into account though. If your system is using delta events it means that your consumers will need to keep the state of the resources, probably using a materialised view (local copy) of the data.
Probably this rings a bell? these kind of events are the base of the Event Sourcing and the CQRS patterns.
Snapshot events
Snapshot events are a specific kind of “fat events”, as they contain the full resource. Usually they miss the “action”, so they only contain the latest view of the resource:
{
"resource_id": "xxxxxxxx",
"data": {
"username": "joe",
"user_id": "xxxxxxxx",
"age": 29,
...
}
}
Commands
Commands are somewhat different to events as they don’t carry any state or snapshots or results of actions. They are like regular calls to a function with the required parameters, so they really don’t have a predefined structure.
Metadata of an event
If you start working with message based architectures you’ll soon realise that for debugging and observability you need more data.
- When was this message sent? when was it received?
- Have I already processed this message? or is it a repetition?
- …
So, my advise is that you need to wrap some metadata in every one of the previous structures.
My recommendation is, at least:
- Add the timestamp of when the event was created. This way you’ll be able to see when this event was generated. Was it some seconds ago? or is this something from last week ?
- Add a unique ID (UUID) in each event. I’m not going to get deep into the technical stuff in here, but some message brokers can send an event more than one time. If this happens the consumer needs to make sure that it only processes the event once. A UUID can be very handy. And also, it’s very useful in debugging sessions.
To sum up
These are the most popular types of events and structures I’ve seen. Of course, mix them and use them as you see fit for your architecture.
Type of event | Used For |
---|---|
Thin | Events |
Fat | Events |
Delta | Events |
Commands | Commands |
Snapshots | Snapshots |
Resources
Some very interesting blog posts and videos that will come very handy if you want to gain more understanding about event semantics, and of course if you want more info just drop me an email!