Asynchronous Communication– How much information do we pack in events to reduce coupling?
The advantage of event-driven communication is that it promotes loose coupling, where the event source is unaware of the downstream services. The source emits an event about something which has happened (or some data which has changed) and expects downstream services to figure out how to deal with it.
One important aspect here is whether we are passing enough information to the downstream service so that it can work autonomously. This aspect gives rise to 2 kinds of events — Thin Events & Fat Events.
Thin events tell the recipient something has happened/changed and have bare minimum information (typically Identifiers) which allow the recipient to query more details from the event source.
This pattern has some characteristics which may or may not be desirable depending upon the context
- A higher degree of coupling & more chatty. The downstream services need to query the event source to get more information.
- The event source itself is getting bombarded with queries.
But despite its shortcomings, thin events are used in the following cases
- Strict/Different access control for sensitive information — Rather than broadcasting sensitive information to everyone, the event source may apply access policies on who can see what.
- The data which has changed belongs to a very hierarchical data model. Thus, it's optimal for the event source to respond to queries, rather than deliver all the details in the event itself.
Fat events have enough attributes conveying the change or the updated state so that the downstream services need not query the event source for more details & can work autonomously. The attributes are specifically chosen just like any public contract because downstream services start to grow over this schema.
The advantages are pretty apparent, reduced coupling & simpler design.
However, the fat events are broadcasting all the information to everyone, and this may not be desirable in all conditions. Let's take an example to explain this — Say a new customer is created in an insurance company. The marketing department needs to have some basic information about this customer to sell policies in future, while the underwriting department would need much more information and perhaps some sensitive information to evaluate the insurance applicant & the Accounting department would need information about the applicant’s account.
In such a case, Customer service emits 2 separate events on separate channels, CustmerCreatedEvent has basic information and is broadcasted to anyone interested. While ApplicantCreatedEvent has all the information about the applicant and is published on a discrete channel.
This does add complexity as compared to the simpler version, but the system is still decoupled and there is stronger access control.
How to prevent events from containing too much information?
Just like any public contract, one of the concerns with fat events is that whatever is exposed can be used by downstream services. And with time as downstream services start to grow over the exposed fields, it’ll create a schema coupling. Making it challenging to change or delete any of the exposed fields later.
One of the things is to start with bare minimum information in the event, exposing just what is needed for the time being.
Second is being mindful about the service boundaries, and scrutinizing the need for more information in the event with a lens of service boundaries. That is to say if the data is inside the correct boundaries, then such a need would not arise at first place.
Building on the insurance example, lets say the customer which is already created in the system wants to get into an insurance contact. So the insurance contract/policy service captures deal details and creates an event for insurance policy created. And lets say the underwriting department is one of the listeners of this event because it needs it to have terms for the policy itself and also the parties to calculate risk.
Now lets take a look at simplified data that would be part of the event -
In the above diagram, instead of packing all the data in the event, the event contains data owned by policy service itself and references from customer service. Policy service is not bloating its events by adding information from other services.
As Underwritting department is also getting information whenever a new customer is created. It can use the information from customer created event to complete the picture of the policy contract.
I hope you found the write-up useful and in case there are any bits that you would like me to elaborate, please let me know in the feedback.