Alpha Version: You are viewing the ALPHA documentation. This is an experimental version and may contain breaking changes.
Skip to main content

StateViewSlice

For a short summary of StateViewSlice, see Reventless Components Overview.

Framework Implementation

This component follows the Reventless Component Structure Pattern, using separate files for interface definitions (StateViewSlice.res), builder logic (StateViewSlice_Builder.res), and callback/handler logic (StateViewSlice_Callback.res).

Overview

d2 diagram

The StateViewSlice is a DCB (Dynamic Consistency Boundary) component that projects events from a shared DcbEventLog into a QueryDb-backed read model. It implements the projection pattern where events are transformed into state updates.

Purpose and Responsibilities

  • Responsibility: Listen to events from DcbEventLog and project them into a QueryDb for efficient reading
  • In: Events from DcbEventLog (subscribed via EventCollector)
  • Out: State updates to QueryDb
  • Key Feature: Complements StateChangeSlice by providing read-optimized views of the event-sourced state

Relationship with DCB

StateViewSlice works alongside StateChangeSlice in the DCB architecture:

d2 diagram

Component Spec

A StateViewSlice is split into two files:

  • <Name>.res — the spec (@@reventless.spec): the local consumedEvent and state @schema types.
  • <Name>_Projection.res — the projection (@@reventless.projection): a single let project = event => [...] function (ONE argument).

The spec file. @@reventless.spec injects name, module Id, moduleUrl, let config = config(), and let subIdConfig = None:

Item/StateViewSliceStream/Items.res
@@reventless.spec

@schema
type consumedEvent =
| Created({id: string, name: string})
| Updated({id: string, name: string})
| Deleted({id: string})

@schema
type state = {name: string}

The projection file. @@reventless.projection injects open Reventless.Projection (so Set, Update, UpdateWithDefault, Delete are in scope unqualified) and brings the spec module into scope. project takes ONE argument — the event:

Item/StateViewSliceStream/Items_Projection.res
@@reventless.projection

let project = event =>
switch event {
| Created({id, name}) => [Set(id, {name: name})]
| Updated({id, name}) => [Update(id, state => {...state, name})]
| Deleted({id}) => [Delete(id)]
}

There is no DcbEventLogSpec reference — the slice declares its own local consumedEvent union.

Spec Fields Explained

In the spec file (@@reventless.spec injects name, module Id, moduleUrl):

FieldTypeDescription
consumedEvent@schema typeThe local subset of event variants this slice projects
state@schema typeState type for the read model (schema auto-generated)

In the _Projection.res file:

FieldTypeDescription
projectconsumedEvent => array<Projection.action<state>>Function to transform an event into state actions

Runtime Behavior

Event Processing Flow

d2 diagram

Projection Actions

In the _Projection.res file, @@reventless.projection injects open Reventless.Projection, so the action constructors (Set, Update, UpdateWithDefault, Delete) are in scope unqualified:

Item/StateViewSliceStream/Items_Projection.res
@@reventless.projection

let project = event =>
switch event {
| ItemCreated({itemId, name}) => [Set(itemId, {name, createdAt: Date.now()})]
| ItemRenamed({itemId, newName}) => [Update(itemId, state => {...state, name: newName})]
| ItemDeleted({itemId}) => [Delete(itemId)]
| StockAdjusted({itemId, delta}) =>
// UpdateWithDefault handles case where item doesn't exist yet
[UpdateWithDefault(itemId, {count: 0}, state => {...state, count: state.count + delta})]
}

Key Design Annotations

StateViewSlice supports the same PPX annotations on @schema type state as ReadModel. Annotations on state fields automatically generate let makeId, let subIdConfig, and let config. These annotations go on the spec file's @schema type state:

OrderLineItems/StateViewSliceStream/OrderLineItems.res
@@reventless.spec

@schema
type state = {
@id orderId: string, // generates: let makeId
@subId lineItemId: string, // generates: let subIdConfig — enables sort key queries
@index categoryId: string, // generates: let config with a secondary index
@resolves({table: "Products", field: "product"}) productId: string,
quantity: int,
}

@schema
type consumedEvent =
| LineItemAdded({orderId: string, lineItemId: string, productId: string, categoryId: string, quantity: int})
| LineItemRemoved({orderId: string, lineItemId: string})

The project function lives in the sibling _Projection.res file, where @@reventless.projection injects open Reventless.Projection (so Set, Update, UpdateWithDefault, and Delete are in scope unqualified):

OrderLineItems/StateViewSliceStream/OrderLineItems_Projection.res
@@reventless.projection

let project = event =>
switch event {
| LineItemAdded({orderId, lineItemId, productId, categoryId, quantity}) =>
[Set(lineItemId, {orderId, lineItemId, productId, categoryId, quantity})]
| LineItemRemoved({lineItemId}) => [Delete(lineItemId)]
}

For the full annotation reference, see PPX annotations.

Comparison with ReadModel

AspectReadModelStateViewSlice
Event SourceMultiple EventTopicsSingle DcbEventLog
MappingsComplex mapping systemSingle projection function
SpecReventless.ReadModel.SpecCustom Spec with project function
Key annotations@id, @subId, @index, @resolves on state fieldsSame — identical PPX annotation support
Use CaseGeneral-purpose read modelsDCB-specific view projections

Comparison with StateChangeSlice

AspectStateChangeSliceStateViewSlice
PurposeHandle commands and decide on eventsProject events into read model
InputCommands from CommandTopicEvents from DcbEventLog EventTopic
OutputAppends events to DcbEventLogUpdates QueryDb state
PatternDecision/command patternProjection pattern

Pulumi Outputs

type outputs = {
resources: array<Reventless.Adapter.resource>,
queryDb: QueryDb.outputs,
}

The StateViewSlice creates its own QueryDb:

  • DynamoDB table for state storage
  • AppSync resolvers for querying
  • Related IAM roles and policies