Domain Events

What are Domain Events

Domain Events are such a simple, yet great concept, that it's a shame they are not used more. At its core Domain Event is just a term used for naming changes to the system. When you give name to some change, you can discuss it, track it and do various analysis on it.

If you don't use Domain Events, but instead change your system state by doing create/update/delete commands on you current state, you lose the ability to reconstruct the path to this state (excluding the option to track this from database transaction log).

Domain Event is basically just a fancy name for a message your system knows how to process and that will change the state of your system. But it's certainly better name than message.

How Domain Events works

When you have defined a Domain Event, for example

event TransferMoney 
{
  string FromAccountNumber;
  string ToAccountNumber;
  money Amount;
}

then you can interact with the system from the outside with a TransferMoney message. The system can register various handlers for this message type and process it accordingly.

That's about it. Event processing logic is similar to the way you would handle it anyway (changing state of various objects at that time).

In DSL Platform you would handle an event by writing a class which implements IDomainEventHandler[TEvent] interface and provides a single void Handle(TEvent de) method. Platform will take care of invoking your event, doing cleanup if an exception is thrown, ensuring database transaction across calls and providing any dependencies your class requires.

Event sourcing and undo events

Popular Event sourcing frameworks have slightly different architecture. Instead of transaction per event, most of frameworks have separate transaction for each event handler. This leads to systems with undo events which are not part of the domain, but instead part of the technical requirements.

One table for all events is another "interesting" design choice. While easier to implement, it's much harder to extract value from such a design. Also, often implementations details like Version, Unprocessed events and others are leaking into user code.

Event sourcing can simplify system design. Unnecessary code like undo events in case of failure is an anti-pattern and should be avoided, unless that undo event is a real Domain Event. Unless we are designing a distributed system it's better to use database transactions than try to implement undo actions by hand.

Sometimes, undo events can't rollback executed action. Let's say that one of your event sends an email (or even better, puts it on a queue). You can't unsend an email after it's gone. At best you can send another email.

Domain Events in DSL Platform

There are actually two type of Domain Events in DSL Platform. One that references an aggregate (when single aggregate state change will happen) and another that doesn't have an explicit reference, which should be used when an action will result in modifications of multiple aggregates or some other action. Saga is another term used for events which can affect multiple aggregates, but we think Saga is just introducing unnecessary terminology, so we'll stick with Domain Event.

event SendSpam
{
  string[] emails;
  string subject;
  string body;
}

aggregate User(name)
{
  string name;
  string email;
}

event User.ChangeEmail
{
  string newEmail;

  async; //User state is not persisted at same time as this event
}

Every event has its own table which looks exactly as defined in DSL. Aggregate events share same numbering method, which means system can replay them if they are async (meaning that they will be logged in the system, but state changes on other aggregate will not be applied in the database at that moment).

This makes it very easy and performant to query them, use them as first class citizens (like aggregates, snowflakes, etc...) and reason about them equally in database layer as in client layer.

Domain Events and user interface

When user interface is non-trivial, ie. it has different buttons which invoke different action, it maps really well to Domain Events.

Even the most complicated user interface can map 1:1 to an object type from DSL Platform. If your interface has a single save command, that's probably a sign of a simple CRUD interface. Otherwise you should use Domain Events to send messages to the server.

Interface mapping 1:1

  • aggregate root => CRUD
  • snowflake => Domain Events
  • report => Domain Events

For CRUD user interfaces (usually simple helper tables), Domain Events are probably an overkill. If you need log on them, you can just use a history concept and will have a log of all changes.