ciscoheat
ciscoheat

Reputation: 3947

What is a proper level of events for event sourcing?

Sometimes I'm having trouble deciding what an event should represent. For example, here's a simplified Ledger for a bank account:

Ledger {
    date : Date;
    amount : Int;
    cleared : String;
}

A user clears the ledger by entering a reference text. Or the user can remove the clearing by setting the text to an empty string.

My question is, when tracking changes with event sourcing, should I create events for what the user intends to do, like:

Event clearLedger(clearText : String)
Event removeClearing()

Or should I make a more generic event for what is going on behind the scenes, that works in both cases:

Event updateLedger(clearText : String)

This could be taken all the way to a very basic CRUD-like level, eventually ending up at the transaction-log level of a database, so are there any guidelines here?

Upvotes: 1

Views: 563

Answers (3)

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57259

Horses for courses.

My question is, when tracking changes with event sourcing, should I create events for what the user intends to do

Probably. Within the entity boundary itself, it doesn't matter much; but looking at that history from the outside, being able to recognize the context of a change from the event can be very useful. Think pub/sub; once you've got an entity writing events, you are likely to want to start subscribing to those events.

For instance, consider a change of address in a customer profile. That might be a correction of a typo in earlier data entry (the business tracks error rates looking for correctable systemic problems to improve customer experience), or a relocation by the customer (in which case, we want to send a welcome kit to their new address; or launch an audit to review their new address against their existing policies).

You lose that flexibility if everything lives under the umbrella of a single AddressChanged event. Best case, you find yourself trying to guess the context of the change from the data alone.

On the other hand, if that flexibility doesn't deliver any value, then you aren't going to need it.

That said, event sourcing CRUD is weird -- if you don't value the changes as first class citizens, why event source the entity at all? Writing out the aggregate state is much simpler.

What is the difference between writing out the events and writing out the aggregate state?

Not a lot; it's a bit more different reading them.

Less cryptically: writing out the aggregate state is analogous to writing what the aggregate looks like right now. Typically, this is done by a persistence component, which serializes the current state into a DTO. For example, we might represent the current state of the Ledger with a JSON document

{ "date"    : "2016-07-06"
, "amount"  : 40
, "cleared" : null
}

To recreate the ledger later, we simply fetch from the permanent store the json document, and let the object mapper go to work.

Writing out the history, which is to say the events, is going to look more like

[ { "event_type" : "LedgerCreated"
  , "data" 
  : { "date" : "2016-07-06"
    , "amount" : 40
    }
  }
, { "event_type" : "LedgerCleared"
  , "data"
  : { "reason" : "Because I said so" 
    }
  }
, { "event_type" : "ClearingRemoved"
  , "data"
  : {}
  }
]

To recreate the ledger later, we'll need to get the json document out of the permanent store, then use the object mapper to create an ordered sequence of events, create a Ledger in its initial "seed" state, then re-apply those events to our new Ledger entity, effectively discovering what the Ledger looks like now by replaying every change in its history.

Upvotes: 2

Alexey Zimarev
Alexey Zimarev

Reputation: 19630

Events represent some business operations, they have value in itself to be looked at and possibly analysed. For example, you might want to see how much times ledger clearance get removed. If this is what you want and this is what your domain experts are talking about - do that. If you want to do CRUD, as @VoiceOnUnreason wrote, do not event source CRUD. Essentially, you might not be only talking about domain events. For example, what kind of domain events will be produced by your aggregate methods clearLedger and removeClearance. It would be strange if two different business operations produce the same time of events.

Your candidate UpdateLedger would produce a generic LedgerUpdated. Then a couple of weeks later you add another operation into your UpdateLedger and it will become a large bloated methods with lots of parameters that are being checked for nulls/empties and lots of if statements. This is probably the first example every basic DDD talk/book/presentation starts with as an example of bad design, the reason to do DDD in a first place...

There are two easy rules about this stuff:

  • If you use Create, Update and Delete in your commands, methods and events - this smells CRUD. Question yourself if what you are doing is DDD
  • Having if statements in a command handler aka application service, which checks parameters and decide how to change aggregate state or which aggregate method to call, is a smell of not enough granularity, cross-cutting concerns and a violation of single responsibility principle

Upvotes: 1

tomliversidge
tomliversidge

Reputation: 2369

I would you whatever language the business experts use. If they use UpdateLedger then use this. If they use LedgerCleared use that.

Upvotes: 1

Related Questions