A Quick and Gentle Introduction

 

Handlers

The Entry Point into a Service

A handler is the entry point of a message into the business logic of a service. It receives instructions from other services, apps, and clients. You might think of them as controllers in MVC terms, but that's a very loose comparison.

This handler does deposits to a bank account.

A handler receives a command, does its work, and when it's done with that work, it reports the status and outcome of that work by publishing an event.

A handler might also respond (or react) to events by other services, or it might respond to events published by its own service (when a service calls itself).

For more, see the handlers user guide.

 
 

Commands

Telling Services What To Do

Commands are messages. They are the primary inputs to a service. They're just data structures.

For more, see the messages user guide.

 
 

Events

How Services Tell Each Other What They've Done
and how entities get their data

An event is also a message, and also just a data structure.

You might think of events as _responses_, but they're not like responses in an HTTP request/response sense. Events can be received by any number of recipients, not just the sender of the original command. Also, a handler might respond or _react_ to a command by writing many different events.

Often (in simple scenarios), events look a lot like the commands that they respond to.
 

Commands are usually named in the imperative, present tense, e.g.: Deposit. Events are usually named in the past tense, e.g.: Deposited. Events record what has already happened in the past, where as commands are directives and instructions for work that a service carries out.

Events are written to streams. All of the events for a given account are written to that account's stream. If the account ID is 123, the account's stream name is account-123, and all events for the account with ID 123 are written to that stream.

When the deposited event is issued in response to handling the deposit stream, it is written to the account's stream.

For more, see the messages user guide.

 
 

Entities

The Service's Model Objects

Handlers make use of entities. You might think of entities as model objects, but there's no ORM here, and these are not ORM models.

Entities are data structures, as well. And they may have business logic operations that affect an entity's attributes.

For more, see the entities user guide.

 
 

Projections

The Power of Turning a Stream of Events Into an Entity

When the store retrieves the entity, it runs the projection. But since the entity is event sourced, there's no fixed entity row to retrieve. Instead, the events that pertain to an entity are retrieved, and then processed in order, in order to construct a view of that entity per the things that have happened to it.

The store only retrieves the events that have been recorded since the last retrieval and passes them along to the projection.

The projection has the rules and logic for how an event affects the state of the entity.

Once the new events are applied to the entity, the store caches the current version of the entity in memory, and returns the entity to the caller (usually, a handler).

The store may also optionally cache the entity on disk as a snapshot. By caching the entity on disk, or snapshotting the entity, lengthy streams don't have to be read in their entirety. Instead, the snapshot is retrieved, and only events that have not been applied to that version of the entity are then read and applied.

For more, see the projection user guide.

 
 

Stores

Data Retrieval and Caching for a Service's Entities

Entities are retrieved from stores.

In the example handler above, the account entity is retrieved along with the version:

account, current_version = store.fetch(account_id, include: :version)

But where is the entity? Where is it saved?

It's not a row in a table.

An entity (or model if you prefer) is just stored as a series of things that has happened to it. It doesn't need to be a database record that's constantly worked over, saving and retrieving, continually working it over with updated, and maybe even making it inconsistent with reality.

A ORM model is also the sum of its events. But with ORM we discard the reasons for the data being the way it is and keep the well-worn model with no real way to understand what series of events led to its current state (or it's state at any point in time, really).

When a store retrieves an entity, it's actually retrieving new events, and then applying them to the entity.

When a deposited event is applied to the account entity, the account's balance increases by the amount of the deposit. This would be done by invoking the entity's `deposit` method with the amount conveyed by the deposited event. A withdrawn event would decrease the account's balance, which is done by invoking the entity's `withdraw` method with the amount conveyed by the withdrawn event.

Only events that have been published since the last time that an entity was retrieved are read from storage. In practice, only a few or a handful of events are ever read in a single retrieval of an entity.

For more, see the store user guide.