Event Sourcing is Functional, not Object-Oriented.
- Event Sourcing is a combination of an EventStore that uses CQRS (Command-Query Responsibility Segregation) and DDD (Domain Driven Design)
- The input is a command which gets validated by a CommandHandler
- Then it outputs one or many events that are stored transactionally (bundled in a single commit).
- Then they are published on a message broker such as MassTransit so that subscribed event listeners (or Observers) can trigger their Projection Builders.
- There are no deletes. Only appending. If you want to change the state you keep adding events to look like if it had returned to a previous state.
- Save events in the EventStore and the data in the RDS database
- Queries only have getById
- Don't lose info (storing data has become very cheap!)
- Has to be recorded from Time 0.
- Based on the alpha-beta algorithm.
- You can use Event Sourcing to run smoke-tests.
- Can prevent super-user (root) attacks
- Event Sourcing is like WORM drive (Write Once, Read Many).
- Reports are easier to put together.
- Simplifies database horizontal partitioning (sharding)
- No impedance mismatch
- More boiler plate
- Slower to code
- Overkill for data that you don't care.
The Event Store is a database designed to support Event Sourcing.
Save commits with a version number to check for concurrency issues in distributed databases. Each commit bundles a group of events as a payload.
Domain Driven Design
DDD does Not have CRUD (Create Read Update Delete). Only Create and Read.
Composed of Commands and Queries.
Commands should mutate. Queries should just read. Commands don't return anything. Queries don't mutate.
Commands should be imperative in their semantics. eg. "Purchase"
By being imperative the imply that the command can be rejected if an error occurs.
The command payload is just the necessary info needed to process that command, and not the entire DTO (Data Transfer Object).
Every command must get a UUID in the payload to know which item to interact with.
They are stored in a normalized way, ideally (3NF)
They are stored in a denormalized way to minimize JOINs needed and speedup queries.
Event is something that happened, therefor it can't be rejected.
- Events should be in past tense
The history of the events is called Projection.
The Projection is the left-fold or the result of the last state mutated by the last event handler. This result can be stored in a row in a database table. The result can be a json blob of the state at that point of time.
A Projection Builder runs the events and returns the left-fold.
a snapshot of the projection so we don't have to replay the millions of events stored.
So we don't need to query the projection.
To avoid replaying the whole Projection, you can create snapshots.
- Avoid snapshots if the Projection is short enough.
Snapshots is an optimization that can be done in case the queries are taking too long. You can do snapshots every X amount of events. Or if you get to a high volume of projection instances then you might just make a snapshot for the last state.
What is an actor?
Using bus to send events can lead into the trap of leading to ping the server for more information than what the message bus sends you. To try to solve that you write a Controller to ask for more info to the server.
As the app features pile up, you tend to end up with a bunch of controllers.
The result from chaining methods. Which each method comes from an event.
var leftfold = compute(compute(compute()));
A contract is a convention to set commands that mutate state and queries that are immutable, hence eliminating side-effects.