… a facetious look at the nature of events.

Anything that happens, happens.
Anything that, in happening, causes something else to happen, causes something else to happen.
Anything that, in happening, causes itself to happen again, happens again.
It doesn’t necessarily do it in chronological order, though.

– Douglas Adams, Mostly Harmless

Principles of expressing events

  • Events are immutable facts about the past.
  • The system that undergoes the state change is responsible for publishing the event.
  • The system publishing the event is responsible for defining the structure of the event.

I’ll deal with each of these principles individually - and slightly out of order. Please forgive the poetic licence.

Principle: The system that undergoes the state change is responsible for publishing the event.

Whilst modesty is admirable in a human, in software it is confusing. The component that has undertaken the action or undergone the state change should be the one to publish an event saying so. Our software should shout about its own doings, and not expect the town crier to follow it around.

Our code should follow the Assert. Act. Announce. pattern. Specifically, it should assert its preconditions, perform precisely its appointed task (and nothing more) and then announce that that task has been performed.

If we have some code that looks like this:

var cat = CatFactory.SpawnOne();
var mat = MatFactory.WeaveOne();
cat.SitOn(mat);

then our Cat implementation could look something like this:

public class Cat
{
    // ...

    public void SitOn(Mat mat)
    {
        // Assert
        if (_remainingLives <= 0) throw new DeadCatException("Dead cats can't sit on things.");

        // Act
        _currentlySittingOn = mat;
        mat.BeSatUponBy(this);

        // Announce
        DomainEvents.Raise(new CatSatOnMatEvent(this, mat))
    }
}

Crucially, the Cat is the one that has undergone the state change so it is the one that emits the event. (Side note: the Mat might also change state by virtue of being sat upon, in which case it should emit its own event.)

Principle: The system publishing the event is responsible for defining the structure of the event.

… and for honouring that contract.

The system that underwent the state change is the one that knows what happened. It is therefore the best placed to define the structure of the event representing that state change. Crucially, the publishing system should not attempt to cater to downstream subscribers who might desire additional information.

Consider the following event:

public class CatSatOnMatEvent
{
    public Instant Timestamp {get; set; }
    public Guid CatId { get; set; }
    public Guid MatId { get; set; }
}

This structure is quite terse but describes exactly what happened and only what happened. This is a Good Thing.

What happens, though, when a downstream subscriber wants to know the cat’s name? We end up with something like this:

public class CatSatOnMatEvent
{
    public Instant Timestamp {get; set; }
    public Guid CatId { get; set; }
    public string CatName { get; set; } // not a good idea
    public Guid MatId { get; set; }
}

What could be so harmful about that? Let’s go one further and suggest that another downstream subscriber wants to know the colour of the mat upon which the cat sat:

public class CatSatOnMatEvent
{
    public Instant Timestamp {get; set; }
    public Guid CatId { get; set; }
    public string CatName { get; set; } // not a good idea
    public Guid MatId { get; set; }
    public Color MatColor { get; set; } // also not a good idea
}

All of a sudden, we discover that our publisher will be responsible for marshalling all sorts of additional information just in case a subscriber wants it. The first subscriber doesn’t care about the colour of the mat and the second doesn’t care about the name of the cat, yet the publisher ends up being obliged to collate both every time it publishes an event just in case a subscriber wants it. This gives the publisher potentially as many reasons to change as there are subscribers - exactly the opposite of what the Single Responsibility Principle advocates.

What the publishing system should be doing is including only the information 1) about which it is authoritative; and 2) necessary to convey the essence of what happened. The system that cares about the name of the cat can then call a service to retrieve the name of the cat; likewise for the system that cares about the colour of the mat.

The publishing system is responsible for honouring that event contract for as long as agreed with its downstream subscribers. For an in-process event a refactoring could be done in a single commit, but for a business event published to a Kafka stream it might need to honour the contract for weeks, months or even years. It is the responsibility of the publisher to honour this contract for as long as its SLA specifies. (Site note: contract tests and/or schema registries will come in useful here.)

Principle: Events are immutable facts about the past.

An event should describe a thing that happened. It must have actually happened at a specific time. Events are expressed in the simple past tense.

The cat sat on the mat.

This is a good event. It’s expressed in the simple past tense. It unequivocally happened. Anything that may have happened after this event does not change the correctness of this event.

The cat is on the mat.

This sentence is in the present tense.

We could validly fire this event infinite times per second provided that there were, indeed, an appropriate cat sitting upon an appropriate mat. It would not be helpful. It would be much more helpful if we were to announce (once) at the moment that the cat were to first sit on the mat that it had done so.

The cat was sitting on the mat.

This is in the past progressive tense. It’s in the past, true, but it describes something that was happening and continuing to happen. Provided that the statement was once true (i.e. that the cat, once upon a time, sat upon the mat), the statement will always continue to be true. While a true statement, it is not helpful.

The cat sits on the mat.

Does it? When? Is it there now? Does it just generally sit on the mat? Do all cats sit on all mats?

The cat will sit on the mat.

Are we sure? How sure? Are we sure that it’s going to sit on the mat at precisely the timestamp on the event? It is a cat, after all. What if it gets hungry? Or bored?

This event is neither immutable (it might or might not happen) nor about the past.

The cat would have sat on the mat.

… but it didn’t because cats are fickle. This is not a thing that happened, therefore it’s not a good candidate for an event.

The cat should sit on the mat in ten minutes.

This is a command, not an event. It’s a valid command but it’s not a fact about the past.

Some other considerations

Commands should not be dressed up as passive-aggressive events.

We want our event publishers to observe the Assert. Act. Announce. pattern. Specifically, the publisher of an event should actively not care about whether any downstream consumers take action as a result of the event.

When an upstream system requires a downstream system to take a specific action as a result of something that happened, it should send an explicit command (either synchronous or asynchronous) to that system rather than announcing an event and then waiting expectantly.

Dinner is served, cat!

This is a passive-agressive event. The publisher of the event is waiting expectantly for their cat to come and eat its dinner, without having expressed that expectation.

Cat was requested to eat dinner.

This is also a passive-aggressive event. It expects a specific action to be taken and it is likely that the publisher of the event is waiting for that action to be completed.

Cat! Come and eat your dinner!

This is a perfectly acceptable command; it’s just not an event. It is an imperative instruction for an entity (cat) to undertake an action (come and eat dinner). It is entirely reasonable to send such a command; just not to present it as an event.

Think about your events’ granularity.

It is useful to structure events according to business events that actually happened and that people would talk about in real terms. It is likely that downstream systems will take different actions based on different events and don’t want to process “catch-all” events that they then have to further filter.

For a more serious example, consider entity-named events (e.g. CustomerEvent) that describe something vaguely related to a customer and, worse, generic CustomerUpdated or event just CustomerUpdate “events” that neither describe what happened or even assert that something did, in fact, happen.

A downstream system will most likely take very different actions as a result of a CustomerCancelledAccountEvent (perhaps sending an apology letter) than a CustomerChangedEmailAddressEvent (sending an alert email to the old address), and a different action again as a result of a CustomerConvictedOfFraudEvent (perhaps suspending shipments to that customer). If all of those happenings are wrapped up in a single CustomerUpdated or CustomerEvent then 1) too much responsibility is pushed onto each subscriber; and 2) the event payload will bloat in order to accommodate all of the different information required for all the downstream subscribers.

Statements about the future are not events.

We see many examples of these kinds of anti-patterns in transaction-processing systems.

For example, future-dated transactions are inserted into a table in the same way as past-dated transactions (strike one: not about the past). Almost invariably they are then updated with an IsProcessed flag or similar when they are actually processed (strike two: not immutable).