Skip to main content

Access Strategies

The access strategy allows you to opt into custom loading or reloading algorithms. For almost all categories in almost all most systems, Unoptimized is a good choice; you can always switch later.

Unoptimized

The first, and likely most used access strategy is the unoptimized one. It'll not apply any optimised reading logic. When loading a stream for the first time all of its events will be loaded in batches of batchSize (default: 500). This turns out to be a sensible default for most aggregates, especially when used in conjunction with the built-in caching layer.

LatestKnownEvent(type)

There is a special case of stream whose events are all a "full-replace". An example could be a customer's contact preferences, or an entity view-data stream. This access strategy will ensure that you only load at most a single event when transacting or querying such a stream.

AdjacentSnapshots(type, toSnapshot, frequency)

Imagine a simple counter service. It does not have a defined start and end as most processes would and as such we don't know how large it'll grow over time. In order to ensure the snappiness of our application we might apply snapshotting to ensure that the state of the counter can be reconstructed in 2 round-trips instead of N, where N is the number of events divided by batch size.

In Equinox the snapshot is a member of the Event DU

type Event =
| { type: "Increment"; data: { amount: number } }
| { type: "Snapshot"; data: { current: number } }

const codec = Codec.json<Event>()

We'll handle the snapshot in our evolution function

type State = number
const initial = 0
const evolve = (state: State, event: Event) => {
switch (event.type) {
case "Increment":
return state + event.data.amount
case "Snapshot":
return event.data.current
}
}
const fold = reduce(evolve)

In addition to this we need to specify to Equinox which event type represents a snapshot and how to transform the current state into a snapshot

const snapshotEventType: Event["type"] = "Snapshot"
const toSnapshot = (state: State): Event => ({ type: snapshotEventType, data: { current: state } })

We can then create the access strategy

const access = AccessStrategy.AdjacentSnapshots(snapshotEventType, toSnapshot)

With this in place Equinox will maintain and store a snapshot event in an adjacent stream {category}:snapshot-{stream_id}. When loading the current state we will first load the latest event from the snapshot stream. The snapshot event will have the version of the stream it was generated from, this version will be used to fetch "events since version" from the source stream. In practise this guarantees that the state of a stream can be reconstructed in 2 round-trips (aside from some interactions with the cache that can cause it to need an extra round trip in exceedingly rare cases).