Skip to content

Instantly share code, notes, and snippets.

@arturopie
Last active January 8, 2019 04:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arturopie/0519cd936123b2b9298aaf5f5af971df to your computer and use it in GitHub Desktop.
Save arturopie/0519cd936123b2b9298aaf5f5af971df to your computer and use it in GitHub Desktop.
Domain Event Rules

Rules for building Domain Events

Rule #1: Naming is hard - really hard

A Domain Event is:

Something that has already happened, and therefore should be named in past tense It should be named in business language (Ubiquitous Language) Represents a state change Something that will never change (the record is written once and never edited afterwards) 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, and it can be misleading when it doesn’t capture the exact business change.

For Example: ProductItemSold vs TicketSold

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 is not the case. The user might have registered (UserRegisteredFromEmail, UserJoinedFromFacebook), the user might have been imported (UserImportedByAdmin), 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.

Prefer intention revealing names: UserChangedPassword over UserUpdated.

And one more thing: don’t talk CRUD to your domain expert/customer. When she/he starts to talk CRUD, you are in serious trouble.

Rule #3: your event is not your entity

Don't just add all the attributes of the entity into your event. Why? Because it creates a coupling between our domain event & our database schema. That 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 nor 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

Explicit definition of your domain event’s attributes will not only let you avoid unintentional attributes in the domain event schema but force’s you to think what really should be included in the event’s data.

Compare:


 1 RailsEventStore::Client.new.read_all_streams_forward(:head, 1)
 2 => ProductItemEvents::ProductItemSold
 3      @event_id="74eb88c0-8b97-4f27-9234-ed390f72287c",
 4      @metadata={:timestamp=>2014-11-12 22:20:24 UTC},
 5      @data={:order_id=>23456, :product_item_id=>123456,
 6             :attributes=>{
 7                "id"=>123456, "order_id"=>23456, "product_type_id"=>98765,
 8                "price"=>50, "barcode"=>"1234567890",
 9                "scanned_at"=>nil, "serialized_type"=>nil,
10                "order_line_id"=>3456789, "code_id"=>nil,
11                "updated_at"=>2014-11-12 22:20:24 UTC, "created_at"=>2014-11-12 2\
12 2:20:24 UTC}}

vs

1 RailsEventStore::Client.new.read_all_streams_forward(:head, 1)
1 => TicketSold
2      @event_id="74eb88c0-8b97-4f27-9234-ed390f72287c",
3      @metadata={:timestamp=>2014-11-12 22:20:24 UTC},
4      @data={:barcode=>"1234567890",
5             :order_id=>23456, :order_line_id=>3456789,
6             :ticket_type_id=>98765,
7             :price=>{Price value object here}}

In the second example, all important data is explicit. Clearly defined contracts on 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, an important attribute was revealed here: 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 barcode 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 talk about it. Natural keys 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 will use the barcode as identification. When you validate the ticket near 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’re doing it right ;) ). The barcode is the identification on the ticket. The database record’s id is not. Don’t let your database leak through your domain.

Rule #6: time is a modeling factor

“Modeling events forces temporal focus” - Greg Young at DDD Europe 2016

How do things correlate over time, what happens when this happens before this becomes a real domain problem. Very often our understanding of the domain is very naive. If you don’t include time as a modelling factor your model might not reflect what is happening in the real world.

Rule #7: when in doubt

TALK TO YOUR DOMAIN EXPERT / BUSINESS

Rules for building Domain Events

Rule #1: Naming is hard - really hard

A Domain Event is:

Something that has already happened, and therefore should be named in past tense It should be named in business language (Ubiquitous Language) Represents a state change Something that will never change (the record is written once and never edited afterwards) 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, and it can be misleading when it doesn’t capture the exact business change.

For Example: ProductItemSold vs TicketSold

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 is not the case. The user might have registered (UserRegisteredFromEmail, UserJoinedFromFacebook), the user might have been imported (UserImportedByAdmin), 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.

Prefer intention revealing names: UserChangedPassword over UserUpdated.

And one more thing: don’t talk CRUD to your domain expert/customer. When she/he starts to talk CRUD, you are in serious trouble.

Rule #3: your event is not your entity

Don't just add all the attributes of the entity into your event. Why? Because it creates a coupling between our domain event & our database schema. That 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 nor 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

Explicit definition of your domain event’s attributes will not only let you avoid unintentional attributes in the domain event schema but force’s you to think what really should be included in the event’s data.

Compare:


 1 RailsEventStore::Client.new.read_all_streams_forward(:head, 1)
 2 => ProductItemEvents::ProductItemSold
 3      @event_id="74eb88c0-8b97-4f27-9234-ed390f72287c",
 4      @metadata={:timestamp=>2014-11-12 22:20:24 UTC},
 5      @data={:order_id=>23456, :product_item_id=>123456,
 6             :attributes=>{
 7                "id"=>123456, "order_id"=>23456, "product_type_id"=>98765,
 8                "price"=>50, "barcode"=>"1234567890",
 9                "scanned_at"=>nil, "serialized_type"=>nil,
10                "order_line_id"=>3456789, "code_id"=>nil,
11                "updated_at"=>2014-11-12 22:20:24 UTC, "created_at"=>2014-11-12 2\
12 2:20:24 UTC}}

vs

1 RailsEventStore::Client.new.read_all_streams_forward(:head, 1)
1 => TicketSold
2      @event_id="74eb88c0-8b97-4f27-9234-ed390f72287c",
3      @metadata={:timestamp=>2014-11-12 22:20:24 UTC},
4      @data={:barcode=>"1234567890",
5             :order_id=>23456, :order_line_id=>3456789,
6             :ticket_type_id=>98765,
7             :price=>{Price value object here}}

In the second example, all important data is explicit. Clearly defined contracts on 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, an important attribute was revealed here: 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 barcode 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 talk about it. Natural keys 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 will use the barcode as identification. When you validate the ticket near 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’re doing it right ;) ). The barcode is the identification on the ticket. The database record’s id is not. Don’t let your database leak through your domain.

Rule #6: time is a modeling factor

“Modeling events forces temporal focus” - Greg Young at DDD Europe 2016

How do things correlate over time, what happens when this happens before this becomes a real domain problem. Very often our understanding of the domain is very naive. If you don’t include time as a modelling factor your model might not reflect what is happening in the real world.

Rule #7: when in doubt

TALK TO YOUR DOMAIN EXPERT / BUSINESS

Other tips:

One action/command/request can trigger multiple events

Excerpt From: Robert Pankowecki & Arkency Team. “Domain-Driven Rails.”

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment