Domain-Driven Design (DDD) With F# and EventStore - Projections
Projections implement the query side of CQRS. Specifically in EventStore, projections are a mechanism for transforming event streams into other event streams. This has a wide range of applications including CEP. For this project, projections were used to generate read models, also known as views or query models.
The OverviewReadModelProjection.js projection counts the total number of items in inventory. It does so by selecting events which change the count, namely ItemsCheckedIn and ItemsRemoved. Each occurrence of those events adjusts a state variable and emits a snapshot of the variable as an event. This event can then be retrieved as a read model to serve a query.
As described in the projections series on the EventStore blog, the call to emit emits an event to an event stream with identity “InventoryItemOverviewReadModel”. The fromCategory() function selects events in a specified category. Events are categorized by a built-in projection $by_category which determines the category from the stream ID. The object passed into the when() function contains functions for handling the desired events as well as a state initialization function. The first parameter of the event handling functions is a state variable maintained by EventStore. The state variable can be scoped at the projection level or per event stream, depending on how events are selected. In this case, the state variable is scoped at the projection level. The second variable is the event itself. It has the following structure:
The values contains the stream ID, event body, metadata, event type and other details. Note that this structure is dictated by the serialization format. As such, care must be take to ensure the structure is palatable both in streams, projections and code.
Inventory Item State Projection
The FlatReadModelProjection.js projection captures the state of individual inventory items including their name, count and active flag.
In this case, events are partitioned by stream with the foreachStream() function. As a result, the state variable will be a associated with each stream. The emitted events will be retrievable using the ID of the source aggregate stream and have category “InventoryItemFlatReadModel”, for example “InventoryItemFlatReadModel-880852396f0f48c6b73d017333cb99ba”.
The projections are retrieved as read models using the ReadStreamEventsBackward function to read the last event:
The read models are declared in F# as follows in the ReadModels module:
Note that the structure of the read models has to match the structure of the events emitted by the projection.
Projections in EventStore are a powerful mechanism with a wide array of applications. In this post, they were shown to support some basic read models. However, some scenarios can call for a document database or full-text search. In such cases events can be dispatched outside of the event store.
The source code for this post can be found on GitHub.