When starting out with CQRS, designing the ‘events’ is not always obvious or easy. The more experienced you are, the more likely your habits will lead you astray. This post will help you spot the problems early and stay on the right track.
1. Watch out for the ‘Updated’ word
Watch out for event names that include the word ‘Updated’ or ‘Edited’. These are usually associated with state based operation like those found in REST (Representational state transfer) or CRUD (Create, Read, Update and Delete). Through heavy use of ORM’s like Entity Framework and NHibernate, we have trained ourselves to think in CRUD terms. In the classic bank account scenario, when depositing money, what would make a good event name? A sensible answer may be ‘AccountUpdated’, why not, after all you have just updated the account, right? So here’s the issue. Can you read from the name of the event what just happened? AccountUpdated could refer to a deposit, a correction of the account holder name, a credit or the application of a charge etc. Event message names should reflect what just happened, as well as describe the actual change in it’s content. In fact, the words you use for event names are important and should reflect the ‘ubiquitous language’ of the domain.
2. Event Streams that don’t make Sense to a Domain Expert
Another indicator of bad event naming can be found when looking at the event stream. Would a domain expert be able to infer from the names alone what has just happened in the system? The following 2 event streams clearly illustrates the difference:
Stream 1 – Badly named events
Stream 2 – Well named events
This also makes it easier to debug your application. Out of place events stand out in an event stream when they are clearly named.
3. Event Messages are not View Models
Another common temptation is to put the view model fields into your event. The key purpose of an event message is to represent what has just happened. The message should contain the information needed to rebuild the state of the domain object. It may well end up looking like a view model but that should not be the driving force behind its design. Given the bank account example above, you can spot this kind of issue when looking at the fields. A deposit event may have the current balance but will have have the amount deposited. i.e. the change in state!
Events are also subscribed to by de-normalisers which can use the information to build out a highly optimised read model. By keeping the contents of the event focused on describing the state change, it gives you greater scope to produce better and potentially more diverse read models.
4. Event Names should be Past Tense
This seems obvious but remember that an ‘Event’ is always something that has happened. It should, therefore, reflect this in its name being in the past tense.
5. Missing Commonly Required Fields
Here is a list of common fields found in a typical event and what they are used for:
- AggregateId – This field is used to associate the particular event to a specific aggregate root.
- Date Time Stamp – Ordering of events is crucial. Replaying events in the wrong order can result is unpredictable outcomes.
- UserId – This field is commonly required in a line of business applications and can be used to build audit logs. It is a common field, but not always necessary and depends on the specific domain.
- Version – The version number allows the developer to handle concurrency conflicts and partial connection scenarios. For more information, take a look at Handling Concurrency Issues in a CQRS Event Sourced system.
- ProcessId – At its simplest, this field can be used to tie a series of events back to their originating command. However, it can also be used to ensure the idempotence* of the event.
* Idempotence refers to the ability of a system to produce the same outcome, even if an event or message is received more than once.
There are no hard and fast rules as to what to include, however, the list above will give you some guidance toward the right direction.
6. Mutable Events
Given the reliance of events to be the source of truth, it is vitally important to ensure they are immutable. That is to say, once an event object is created, it should not be possible to change any of its fields. This property of messaging, in general, has some profound implications on your over all system. Immutable objects are far easier to test with, can be reliably sent around a system or communications bus and play well in a multi threaded environment. I follow a very simple pattern when creating my events:
The 6 indicators described above are just rules of thumb. Use them as a guide, not a rule book. They should help you avoid some common early mistakes. The process of naming events gets easier as your knowledge of the specific domain grows and matures. It is often harder to find the right names early in a project and usually hints at missing concepts and lack of understanding. When you do get it right and your events form the basis of a common language, domain experts are then able to clearly reason about your system and feel able to communicate their ideas and requirements far more clearly.
I read this article a few times and all the points are valid, just a small correction I think AccountCreated event is a code smell as well I think it might be better to call it AccountOpened because unless you are monitoring a production line because this sort of smells like CRUD.
Good point. I do have a practical reason for this. I have often used a convention based on all “created” events. But you are right. Well spotted!
Very good article. I would like to ask you one more thing about the convention for Domain and Integration commands/events. How to differentiate them (by package, interface, or maybe suffix). Which approach is the best and why?
Not sure what you mean by ‘Integration commands/events’? Commands are always in the imperative and events are always in the past tense. This ensures it’s easy to differentiate.