The thing I always get stuck on with these techniques is, how do you handle transactions which perform validations/enforce invariants on data when you’re just writing writes to a log and computing materialized views down the line? How can you do essentially, an “add item to shopping cart” if for example, users can only have max 10 items and so you need to validate that there aren’t already 10 items in the cart?
Here's how that looks like in a DDD world:
* An aggregate is responsible for encapsulating business rules and emitting events
* An aggregate is responsible for maintaining the validity of its own state (ensuring invariants are valid)
* When a command/request is received, the aggregate first rehydrates its current state by replaying all previous events
* The aggregate then validates the command against its business rules using the current state
* Only if validation passes does the aggregate emit the new event
* If validation fails, the command is rejected (e.g., throws CartMaxLimitReached error)
Example flow: Command "AddItemToCart" arrives
>> System loads CartAggregate by replaying all its events
>> CartAggregate checks its invariants (current items count >> If valid: emits "ItemAddedToCart" event. If invalid: throws CartMaxLimitReached error