-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Problem
In EventStore.fetchEntitySnapshot() (event-store.ts, line 46+), when all pending events for an entity return ReducerAction.Skip, the code currently sets newEntitySnapshot to the latest loaded snapshot but still calls storeSnapshot, even though the entity's state hasn't changed:
// event-store.ts, simplified logic
let newEntitySnapshot = latestSnapshotEnvelope
for (const pendingEvent of pendingEvents) {
const reducerResult = await this.entityReducer(pendingEvent, newEntitySnapshot)
// If reducer returns ReducerAction.Skip, keep the current snapshot unchanged
if (reducerResult !== ReducerAction.Skip) {
newEntitySnapshot = reducerResult
}
}
// (later...)
await this.storeSnapshot(newEntitySnapshot)As a result, duplicate snapshots are stored with the same data, which can repeatedly happen for entities that keep receiving unprocessable or "skipped" events (e.g., events for deleted/missing entities), leading to:
- Snapshot bloat: unnecessary storage of identical snapshots, causing unbounded growth
- Unnecessary writes: repeated "snap" operations with no effect, wasting resources
This contradicts the intended semantics of ReducerAction.Skip, as documented in entity.md:
When a reducer returns
ReducerAction.Skip, the framework will:
- Keep the previous entity snapshot unchanged
- Continue processing subsequent events in the event stream
- Not store a new snapshot for this event
Code Example (Current Behavior)
Example showing how "skip" is currently handled in tests (actual and expected):
// event-store.test.ts
context('when reducer returns ReducerAction.Skip', () => {
it('keeps the current snapshot unchanged', async () => {
// ...setup skipped for brevity...
// entityReducer returns ReducerAction.Skip
const entity = await eventStore.fetchEntitySnapshot(entityName, entityID)
expect(eventStore.entityReducer).to.have.been.calledOnce
// This currently still gets called!
expect(eventStore.storeSnapshot).to.have.been.calledOnceWith(someSnapshotEnvelope)
expect(entity).to.be.deep.equal(someSnapshotEnvelope)
})
})Recommendation
Track whether at least one reducer actually produced a new snapshot during the pending events iteration, and only call storeSnapshot if an update was made. This avoids duplicate snapshots and unwanted storage growth.
- Option: Introduce a "didReduce" or "changed" flag
- Or: Compare
newEntitySnapshottolatestSnapshotEnvelopebefore writing