StackOverflow has a good article discussing the pros and cons of fat vs thin events. I thought I'd repeat it here.
The question raised was:
"When raising an event, which pattern is the most suited:
- Name the event "CustomerUpdate" and include all information (updated or not) about the customer
- Name the event "CustomerUpdate" and include only information that have really been updated
- Name the event "CustomerUpdate" and include minimum information (Identifier) and/or a URI to let the consumer retrieves information about this Customer.
I ask the question because some of our events could be heavy and frequent."
And the response was:
Name the event "CustomerUpdate"
First let's start with your event name. The purpose of an event is to describe something which has already happened. This is different from a command, which is to issue an instruction for something yet to happen.
Your event name "CustomerUpdate" sounds ambiguous in this respect, as it could be describing something in the past or something in the future.
CustomerUpdated would be better, but even then, Updated is another ambiguous term and is nonspecific in a business context. Why was the customer updated in this instance? Was it because they changed their payment details? Moved home? Were they upgraded from silver to gold status? Events can be made as specific as needed.
This may seem at first to be overthinking, but event naming becomes especially relevant as you remove data and context from the event payload, moving more toward skinny events (the "option 3" from your question, which I discuss below).
That is not to suggest that it is always appropriate to define events at this level of granularity, only that it is an avenue which is open to you early on in the project which may pay dividends later on (or may swamp you with thousands of event types).
Going back to your actual question, let's take each of your options in turn:
Name the event "CustomerUpdate" and include all information (updated or not) about the customer
Let's call this "pattern" the Fat message.
Fat messages represent the state of the described entity at a given point in time with all the event context present in the payload. They are interesting because the message itself represents the contract between service and consumer. They can be used for communicating changes of state between business domains, where it may be preferred that all event context be present during message processing by the consumer.
Advantages:
- Self-consistent - can be consumed entirely without knowledge of other systems.
- Simple to consume (upsert).
Disadvantages:
- Brittle - the contract between service and consumer is coupled to the message itself.
- Easy to overwrite current data with old data if messages arrive in the wrong order.
- Large.
Name the event "CustomerUpdate" and include only information that have really been updated
Let's call this pattern the Delta message.
Deltas are similar to fat messages in many ways, though they are generally more complex to generate and consume. Because they are only a partial description of the event entity, deltas also come with a built-in "assumption" that the consumer knows something about the event being described. For this reason, they may be less suitable for sending outside a business domain, where the event entity may not be well known.
Advantages:
- Smaller than Fat messages
Disadvantages:
- Brittle - similar to the Fat message, the contract is embedded in the message.
- Easy to overwrite current data with old data.
- Complex to generate and consume
Name the event "CustomerUpdate" and include minimum information (Identifier) and/or a URI to let the consumer retrieves information about this Customer.
Let's call this the Skinny message.
Skinny messages are different from the other message patterns you have defined, in that the service/consumer contract is no longer explicit in the message, but implied in that at some later time the consumer will retrieve the event context. This decouples the contract and the message exchange, which is a good thing.
This may or may not lend itself well to cross-business domain communication of events, depending on how your enterprise is set up. Because the event payload is so small (usually just an ID), there is no context other than the name of the event on which the consumer can base processing decisions; therefore it becomes more important to make sure the event is named appropriately, especially if there are multiple ways a consumer could respond to a CustomerUpdated message.
Additionally it may not be good practice to include an actual resource address in the event data - because events are things which have already happened, event messages are generally immutable and therefore any information in the event should be true forever in case the events need to be replayed. In this instance a resource address could easily become obsolete and events would not be re-playable.
Advantages:
- Decouples service contract from message.
- Information about the event contained in the event name.
- Naturally idempotent (with time-stamp).
- Generally tiny.
- Simple to generate and consume.
Disadvantages:
- Consumer must make additional call to retrieve event context - requires explicit knowledge of other systems.
- Event context may have become obsolete at the point where the consumer retrieves it, making this approach generally unsuitable for some real-time applications.
When raising an event, which pattern is the most suited?
I hope you have by now realised that the answer to this is it depends. I will stop here - yours is a great question because you could probably write a book while answering it, but I hope you found this answer helpful.