Events: Fat or Thin

When it comes to the events, the big debate is about the contents of its body. Some devs argue that events should carry the complete load with it, I am calling them Fat Events in this blog. And then we have others who believe that events should be lightweight and containing minimum details, hence I call them Thin Events. In Thin Events, subscriber of the event is expected to request the required data after receiving the event.

Like every other dilemma in choosing suitable design patterns, this is a hard question to answer and it depends on various things. Lets compare them:

  • Thin Events add coupling which is one of concerns we wanted to address by using events. It forces the event subscriber to call the publisher APIs to get more details. This means a subscriber cannot do its job if the publisher is down. In case of a bulk execution, it can adversely impact the APIs it will be calling. In contrast, Fat Events remove that dependency and lead to a better decoupling of the systems.
  • The data in any event could be outdated by the time it is processed. This impacts both sides in quite opposite ways. Thin Events shine when the real-time information is crucial at the time of processing while Fat Events work better where sequential processing is required.
  • Deciding the contents for an event is the part where Thin Events win, simply because it will only contain the bare minimum details for the subscriber to call back if required. But for Fat Events, we will have to think about the event body. We want to carry enough for all the subscribers but it comes at an expense: the publisher model would be coupled to the contract. It also adds an unnecessary dependency to consider in case you want to remove some data from the domain.

Thin Events do not cut it for me

From my experience so far, I think Thin Events does not offer anything that Fat Events cannot. As with Fat Events, the subscriber can also choose to call back the API if needed. In fact, I tried to think of an example where calling back the publisher is the only way to get the real time information but in those cases it felt like the bounded contexts are not cut right.

So are Fat Events the answer?

No, I don’t think so. Carrying the complete bounded context with every event is not a good idea. As noticed above, it tightly couples your domain model with the event contents.

Delta Events

I am not sure if ‘Delta Events’ is a known term for this, but it is the best name I could think to describe the event contents. The basic concept is to make events carry ‘just enough details’ to describe the change in addition to the identity (Id) of the entities changed. Delta events work even better with the Event Sourcing because they are like a log of what has happened, which is the basic foundation of the Event Sourcing.

So in Delta Events, the contents can consist of:

  • Public Id of the primary entity, that event is broadcast for.
  • Fields that have changed in the event.

e.g. AccountDebited

{
    AccountHolderUserId: <Account holder Id>
    FromAccountNumber: "<Account Number that is debited from>"
    ToAccountNumber: "<Account Number that money is credited to>"
    Description: "Description of transaction"
    AmountDebited:  "<Amount that is debited from the account>"
    Balance: "<Balance remaining after this transaction>"
    TransactionId: "<To correlate with the parent transaction>"
}

The event above, carries the complete details to explain what has happened along with the public Id of the entities involved.

We can make it a Fat Event and carry the additional content such as the Account holder name for the systems which may need that information e.g. notification, reporting etc. But unfortunately that will couple publisher domain unnecessarily to that data it does not need.

How does the subscriber get the missing pieces?

It depends on the situation, for a new subscriber you may want to listen to the other events in the system and build a database of the subscriber’s domain.

Lets assume we have a Notification domain that will send an SMS to the account holder whose account will be debited, if the amount to debit is above $100.00. SMS body would be like:

Hi <First Name> <Last Name>,

Your account number <account number> is debited. Transaction details:

Amount: $<amount>

Remaining Balance: $<remaining balance>

Description: $:<Transaction description>

For the sake of an example, lets assume our system is cut into the following sub domains:

  • User Profile: Maintains account holder details such as Name, Address, Mobile Number and Email etc.
  • Notification: Maintain user preferences about receiving notifications and sending notifications.
  • Accounts: Maintain ledger of debits and credits of accounts

To send the SMS, we have almost all the details in the event body except the account holder name and mobile number of the recipient. In our imaginary system, it is the user profile domain that has those details instead of the accounting domain, that is broadcasting the event. There are two things that can happen here:

Notification domain can call the User Profile to get more details before sending the SMS. So now we always get the up to date contact details of the user but at the cost of run time dependency. If User Profile system is down for any reason, it would break the notification system as well.

OR

We can make the notification system listen to the events from the User Profile system as well to maintain a local database of its recipients (translated from account holders) along with their notification preferences. e.g.

  • UserProfileAdded
  • UserProfileUpdated

I prefer the later option to keep the system decoupled and avoid run time dependency. The workflow will be like this:

The subscriber will build the store as they go, but still there can be scenarios where they don’t have this data:

  1. New Subscriber
  2. An event lost for some reason – not sent, not delivered, delivered but failed etc.

For the first scenario you can start with some data migration to give it a seed data, but for the second case data migration may not work. I have dealt with this situation by introducing a snapshot builder.

Snapshot Builder:

If subscriber does not know about the entity it has received in the event, it would call the relevant systems and build a snapshot. This can be quite handy in scenarios where occasionally the subscriber needs to sync its data (translated from other domains) with the original owner of the data.

I hope you find this post useful, if not the complete approach, it may give you some options to consider when thinking about the event contents.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s