Skip to content

Conversation

@CorentinDoue
Copy link
Collaborator

@CorentinDoue CorentinDoue commented Oct 5, 2025

Description 🦫

Add snapshot capabilities :

export const pokemonsEventStoreWithSnapshot = new EventStore({
  eventStoreId: 'POKEMONS',
  eventTypes: [pokemonAppearedEvent, pokemonCaughtEvent, pokemonLeveledUpEvent],
  reducer: pokemonsReducer,
  eventStorageAdapter: eventStorageAdapterMock,
  snapshotConfig: {
    currentReducerVersion: 'v1.0.0',
    shouldSaveSnapshot: createShouldSaveForRecurentSnapshots(5),
    cleanUpAfterSnapshotSave: cleanUpLastSnapshot,
  },
  snapshotStorageAdapter,
});

consult packages/core/src/eventStore/eventStore.unit.test.ts and packages/core/src/eventStore/eventStore.fixtures.test.ts to know more.

Fixes #181

Replaces #174

TODO 🚧

  • Challenge getAggregate signature cf Question part
  • Propagate changes to ConnectedEventStore
  • Add DynamoDbSnapshotStorageAdapter
  • Add documentation

Questions ⁉️

I'm not sure about getAggregate signature with snapshot. Currently getAggregate returns { aggregate, events, lastEvent };. But with snapshot returning events and lastEvent is either cost expensive or misleading.

Here are some propositions:

  • include snapshot capabilities to current getAggregate, determine if snapshot must be use if snapshotConfig is defined. Return partials events (only those fetched) and lastEvent only if one event have been fetched additionally to a snapshot
  • (✅ currently implemented) include snapshot capabilities to current getAggregate, add an explicit useSnapshot option, determine if snapshot must be use if useSnapshot is true. Return partials events (only those fetched) and lastEvent only if one event have been fetched additionally to a snapshot
  • include snapshot capabilities to getAggregate but with signature BREAKING CHANGE. Determine if snapshot must be use if snapshotConfig is defined. Return only aggregate. Add a new explicit getEventsAndAggregate corresponding to current getAggregate without snapshot capabilities
  • include snapshot capabilities to a new explicit getAggregateWithSnapshot. Determine if snapshot must be use if snapshotConfig is defined. Return only aggregate. Let getAggregate as it is.

Type of change 📝

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested? 🧑‍🔬

🚧

  • Test unit
  • Test linked on real projet

Test Configuration: 🔧

  • Firmware version:
  • Hardware:
  • Toolchain:
  • SDK:

Checklist: ✅

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Base automatically changed from chore/upgrades to main October 12, 2025 15:04
@ThomasAribart
Copy link
Contributor

@CorentinDoue What about:

interface EventStoreContext { 
  eventStoreId: string
}

interface SnapshotsQueryOptions {
  aggregateId?: string
  minVersion?: number
  maxVersion? number
  limit?: number
  reverse?: boolean
  reducerVersion?: string
  pageToken?: string
}

interface SnapshotKey {
  aggregateId: string
  version: number
  reducerVersion: string
}

interface SnapshotStorageAdapter {
  listSnapshots( // <= Or maybe split between two methods: `listAllSnapshots` + `listAggregateSnapshots` ?
    context: EventStoreContext,
    options: SnapshotsQueryOptions = {}
  ) => Promise<{  snapshotsKeys: SnapshotKey[],  nextPageToken?: string; }>
  getSnapshot(snapshotKey: SnapshotKey, context: EventStoreContext) => Promise<{ snapshot: Aggregate }> // + possibly some useful metadata ?
  putSnapshot(reducerVersion: string, snapshot: Aggregate, context: EventStoreContext) => Promise<void>
  deleteSnapshot(snapshotKey: SnapshotKey, context: EventStoreContext) => Promise<void>
}

And then for event stores:

export const pokemonsEventStoreWithSnapshot = new EventStore({
  ...
  reducerVersion: "v1.0.0", // <= I still think it's useful to version reducers outside of snapshots
  snapshotStrategy: {
    strategy: "PERIODIC", // or "NONE" or "CUSTOM"
    periodInVersions: 100,
    pruningStrategy: { // optional pruning strategy
      strategy: "ON_NEW_SNAPSHOT", // or "NONE" or "TTL" for instance
      keepLastVersions: 3
    }
  },
  snapshotStorageAdapter,
});

@oxc
Copy link

oxc commented Oct 12, 2025

Regarding getAggregate: I would hope that I could enable snapshots and benefit from them throughout my codebase without having to change all invocations. I personally would definitely prefer a breaking change that removes events from the return type, over having to add useSnapshot: true to all calls.

@ThomasAribart
Copy link
Contributor

@oxc Yes the idea is that adding a snapshotStorageAdapter + a snapshotStrategy inside your EventStore is enough to enable snapshot without having to worry about it. The EventStore will then search for snapshot but still work if none is found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Snapshot Discussion

4 participants