The anatomy of Domain Event
… and check why 5600+ Rails engineers read also this
The anatomy of Domain Event
Almost 2 years and over 16 million domain events ago I’ve started a process of “switching the mindset”. I had no production experience with Event Sourcing (BTW it still is used only in some parts of the application, but that’s a topic for another post), I had only a limited experience with Domain Driven Design (mainly knowing the tactical patterns). During that time, a lot has changed.
Start from the middle
I’ve started introducing new concepts in our project’s code base from the middle. Not with Domain Driven Design, not with Event Sourcing or CQRS (Command Query Responsibility Segregation). It all has started by just publishing Domain Events.
Trials & errors
The more Domain Events we have “published” the better understanding of our domain I’ve got. Also, I’ve started better understand the core concepts of Domain Driven Design, the terms like bounded context, context map and other from strategic patterns of DDD started to have more sense and be more and more important. Of course, I’ve made a few mistakes, some of them still bite us because of decisions made almost 2 years ago ;)
Our first ever published domain event is:
RailsEventStore::Client.new.read_all_streams_forward(:head, 1)
=> ProductItemEvents::ProductItemSold
@event_id="74eb88c0-8b97-4f27-9234-ed390f72287c",
@metadata={:timestamp=>2014-11-12 22:20:24 UTC},
@data={:order_id=>23456, :product_item_id=>123456,
:attributes=>{
"id"=>123456, "order_id"=>23456, "product_type_id"=>98765,
"price"=>50, "barcode"=>"1234567890",
"scanned_at"=>nil, "serialized_type"=>nil,
"order_line_id"=>3456789, "code_id"=>nil,
"updated_at"=>2014-11-12 22:20:24 UTC, "created_at"=>2014-11-12 22:20:24 UTC}}
Here is a set of rules / things to consider when you will build your domain events. Each of them is based on a mistake I’ve made ;)
Rule #1: the naming is hard, really hard
In my old talk I’ve presented at dev’s user group meetup I’ve defined domain event as:
- Something that has had already happened, and therefore…
- Should be named in past tense…
- … and in business language (Ubiquitous Language)
- Represents state change,
- Something that will never change
The name of a domain event is extremely important. It is the “definition” of an event for others. It brings a lot of value when defined right, but it might be misleading when it won’t capture the exact business change.
In the example event above the name of the domain event is ProductItemSold
. And this name is not the best one. The application domain is not selling some products but selling tickets for events (actually that’s huge simplification but it does not matter here). We do not sell products. We sell tickets. This domain event should be named TicketSold
. Yeah, sure we could also sell some other products but then it should be a different domain event.
Rule #2: don’t be CRUDy
There are very few domains where something is really created. Every time I see a UserCreated
domain event I feel that this is not the case. The user might be registered, the user might be imported, I don’t know the case when we really create a user (he or she exists already ;P). Don’t stop when your domain expert tells you that something is created (updated or deleted). It is usually something more, something that has real business meaning.
And one more thing: don’t talk CRUD to your domain expert / customer. When he will start to talk CRUD, you are in serious trouble.
You know what’s the biggest tragedy in software engineering?
— Andrzej Krzywda (@andrzejkrzywda) October 3, 2015
The customers gave up and learnt to speak CRUD to developers.
Check the blog post of Udi Dahan where he explains this in more details.
Rule #3: your event is not your entity
You might have spotted the attributes
in the data argument of our first domain event. This is something I dislike most in that domain event. Why? Because it creates a coupling between our domain event & our database schema. Every kind of coupling is bad. Especially when you try to build a loosely coupled, event driven architecture for your application. The attributes of a domain event are their contract. It is not something you could and should easily change. There could be parts of the
system that rely on that contract. Changing it is always a trouble. Avoid that by applying Rule #4.
Rule #4: be explicit
The serialized_type
? It this a business language? Really? Or does the business care about the scanned_at
when a ticket has been just sold? I don’t. And all event handlers for this event do not care. That’s just pure garbage. It holds no meaningful information here. It just messing with your domain event contract making it less usable, more complicated.
Explicit definition of your domain event’s attributes will not only let you avoid those unintentional things in the domain event schema but will force you to think what really should be included in the event’s data.
=> TicketSold
@event_id="74eb88c0-8b97-4f27-9234-ed390f72287c",
@metadata={:timestamp=>2014-11-12 22:20:24 UTC},
@data={:barcode=>"1234567890",
:order_id=>23456, :order_line_id=>3456789,
:ticket_type_id=>98765,
:price=>{Price value object here}}
The modified version of my first domain event. Much cleaner. All important data is explicit. Clearly defined contract what to expect. Maybe some more refactoring could be applied here (TicketType value object & OrderSummary value object that will encapsulate the ids of other aggregates). Also, important attribute here was revealed. The ticket’s barcode.
Rule #5: natural is better
With the explicit definition of domain event schema, it is easier to notice that we do not need to rely on database’s id of a ticket (product_item_id
) because we already have a natural key to use - the barcode
. Why is the natural better?
Natural keys are part of the ubiquitous language, are the identifications of the objects you & your domain expert will understand and will use when you will talk about it. It also will be used in most cases on your application UI (if not you should rethink your user experience). When you want to print the ticket you use barcode as identification. When you validate the ticket on the venue entrance you scan the barcode. When some guest has troubles with his ticket your support team asks for the
barcode (…or order number, or guest name if you doing it right ;) ). The barcode is the identification of the ticket. The database record’s id is not. Don’t let you database leak through your domain.
Rule #6: time is a modelling factor
“Modelling events forces temporal focus”
Greg Young, DDD Europe 2016 talk
How things correlate over time, what happens when this happens before this becomes a real domain problem. Very often our understanding of domain is very naive. If you don’t include time as a modelling factor your model might not be reflecting what is happening in the real world.
Rule #7: when in doubt
TALK TO YOUR DOMAIN EXPERT / BUSINESS
Want to learn more?
Struggling with finding a direction of refactorings to keep your app maintainable? Legacy codebase driving you nuts and not revealing intention?
Join our Rails + Domain Driven Design Workshop. The next edition will be held on 12-13th January 2017 (Thursday & Friday), in Wrocław, Poland.