Projections

Overview of projections

A Projection is a special type of Event Handler, that only deals with updating or deleting Read Models based on Events that it handles. The read model instances are managed by the Runtime in a read model store, where they are fetched from whenever needed. This is useful, for when you want to create views from events, but don’t want to manually manage the read model database.

Read models defines the data views that you are interested in presenting, while a projection specifies how to compute this view from the event store. There is a one-to-one relationship between a projection and their corresponding read model. A projection can produce multiple instances of that read model and it will assign each of them a unique key. This key is based on the projections key selectors.

Example of a projection:

flowchart LR
    subgraph Business moments
        direction LR
        CR["Customer Registered<br/>Id: 123<br/>Name: John Doe"]
        DAO["Debit Account Opened<br/>Id: 456<br/>Balance: 0"]
        DP["Debit Performed<br/>Account: 456<br/>Amount: $20"]
        WP["Withdrawal Performed<br/>Account: 56<br/>Amount: $10"]
    end
    subgraph Operations
        CR --> O1["Customer = Id<br/>Name = Name"]
        DAO --> O2["Id = Id<br/>Type = Debit"]
        DP --> O3["Id = Id<br/>Amount += Amount"]
        WP --> O4["Id = Id<br/>Amount -= Amount"]
    end
    subgraph Read Model
        O1 --> RM
        O2 --> RM
        O3 --> RM
        O4 --> RM["Account Details
        Id: 456
        Type: Debit
        Customer: 123
        Name: John Doe
        Balance: $10"]
    end

Read model

A read model represents a view into the data in your system, and are used when you want to show data or build a view. It’s essentially a Data transfer object (DTO) specialized for reading. They are computed from the events, and are as such read-only object without any behaviour seen from the user interface. Some also refer to read models as materialized views.

As read models are computed objects, you can make as many as you want based on whatever events you would like. We encourage you to make every read model single purpose and specialized for a particular use. By splitting up or combining data so that a read model matches exactly what an end-user sees on a single page, you’ll be able to iterate on these views without having to worry how it will affect other pages.

On the other hand, if you end up having to fetch more than one read model to get the necessary data for a single page, you should consider combining those read models.

The read models are purely computed values, which you are free to throw them away or recreate lost ones at any point in time without loosing any data.

The Runtime stores the read models into a read model store, which is defined in the resources.json. Each read model gets its own unique key, which is defined by the projections key selector.

Projection

A projections purpose is to populate the data structure (read model) with information from the event store. Projections behave mostly like an event handler, but they don’t produce a Stream from the events that it handles. This means that changing a projection (like adding or removing handle methods from it) will always make it replay and recalculate the read models from the start of the Event Log. This makes it easier to iterate and develop these read models.

This is a simplified structure of a projection:

Projection {
    ProjectionId Guid
    Scope Guid
    ReadModel type
    EventTypes EventType[]
}

For the whole structure of a projections as defined in protobuf, please check Contracts.

Key selector

Each read model instance has a key, which uniquely identifies it within a projection. A projection handles multiple instances of its read models by fetching the read model with the correct key. It will then apply the changes of the on methods to that read model instance.

The projection fetches the correct read model instance by specifying the key selector for each on method. There are 3 different key selector:

  • Event source based key selector, which defines the read model instances key as the events EventSourceId.
  • Event property based key selector, which defines the key as the handled events property.
  • Partition based key selector, which defines the key as the events streams PartitionId.