What Is The Correct Granularity For Our Events?

One of the most important and yet hardest decisions we will have to make with event sourcing is how granular our events should be. There isn’t a single answer for this and it’s a decision we’ll have to make on a case by case basis as new events need to be created.

A chart showing the too course/too fine spectrum with a section that's just right

Too Course

When we create events that are too course they tend to have a generic name and too much data in them. Because we’ve named the event generically we need to look into the data to make sense of what happened. This causes any subscribers for these events to process every event of this type but they need to check to see if they should perform their work on it. This causes extra load on our servers and indicates some of our domain logic may have spilled over into our listener which makes them harder to maintain.

As an example, our parcel tracking application has a feature where we keep track of the state of the Parcel as it transits our delivery network. To keep things simple, we’ll just say it can be “Created” and “Delivered”. We’ve created an event called ParcelStateChangedEvent that will track these transitions. It will generate events like the following.

ParcelStateChangedEvent
  state: "Created"
ParcelStateChangedEvent
  state: "Delivered"

Now we want to add a listener to look for when a Parcel is “Created” so we can send an employee out to pick up the parcel. This listener will have to be set up to ignore all the states except for “Created”. Because we chose to have this event be course and have one event cover lots of potential paths it has to process extra items. In this case, it’s only adding an if block to the listener to reject anything that doesn’t have a state value of “Created”. If in the future we change the state text to “Parcel Created”? We’ll have to search our entire code base looking for “Created” to update it.

The solution is to make each of these physical events it’s own event class.

ParcelCreatedEvent
ParcelDeliveredEvent

Now our listener can be set up to receive just the ParcelCreatedEvent and none of the other events will be sent to it. Searching for ParcelCreatedEvent is also much easier.

Too Fine

When we create events that are too finely-grained we create very small and specific events that are well named but contain too little information. These events tend to not be useful by themselves and require lots of other events to build up enough information to act on them. We can tell if our events are too fine-grained if the subscribers need to listen to multiple events and there are a lot of duplicate payloads for our events.

As an example, after a Parcel has been created we can change its destination address. We could structure this by breaking each of the address components (house number, street name, city, state, etc.) into it’s own event (ParcelHouseNumberChangedEvent, ParcelStreetNameChangedEvent, ParcelCityChangedEvent, ParcelStateChangedEvent, etc.). This will work but requires us to create a lot of extra events for small changes that do not express much. Generally, our subscribers don’t care if these different events happen independently they only care if any of them change.

When we change an address we’ll create something like the following.

ParcelHouseNumberChangedEvent
  value: "724"
ParcelStreetNameChangedEvent
  value: "Evergreen Terrace"
ParcelCityChangedEvent
  value: "Springfield"
ParcelHouseNumberChangedEvent
  value: "742"

If we look closely there are two changes here. In one, the address was set to “724 Evergreen Terrace, Springfield” and then someone corrected the address to “742 Evergreen Terrace, Springfield” but the second change issued an event for only the house number change which isn’t helpful on its own.

We can easily make this too fine event more course by creating a single event (ParcelAddressChangedEvent) that tracks changes to all these individual fields and will be easier to consume by our subscribers. We’ll lose the “clean” audit log saying what specific thing changed but it might be for the better.

ParcelAddressChangedEvent
  number: "724"
  street: "Evergreen Terrace"
  city: "Springfield"
ParcelAddressChangedEvent
  number: "742"
  street: "Evergreen Terrace"
  city: "Springfield"

Just Right

Finding that “just right” sized event can be difficult but it’s worth the extra work. We may need to spend a lot of time thinking, refactoring, thinking some more, and then refactoring some more until we get it the way we like it. Before we allow an external subscriber to access our events we always create an internal one to see how it feels to work with the events.