Skip to content

JasperFx 2.51.0

Choose a tag to compare

@jeremydmiller jeremydmiller released this 17 Aug 21:47
· 204 commits to main since this release

Three changes to the store-agnostic contracts, all additive. No store breaks on this bump.

IDocumentSessionOperations.PendingStreams (#673, #675)

A consumer holding the shared document session contract can now read the StreamActions a session has queued but not yet committed:

IReadOnlyList<StreamAction> PendingStreams { get; }

All three stores already surfaced the same JasperFx.Events.StreamAction collection under three different names — Marten's PendingChanges.Streams(), Polecat's PendingChanges.Streams, Fisher's Events.PendingStreams — so this closes a naming gap, not a capability gap. It sits on the committable tier for the same reason Events does: a stream action can only be pending in a session that can append, and IDocumentWriteOperations (the tier a projection's RaiseSideEffects receives) cannot.

The member carries a throwing default rather than an empty one. An empty list is indistinguishable from a session with nothing pending, so a silent default would let a consumer's derived work be discarded with a clean build and green tests.

New opt-in PendingStreamActionsCompliance holds stores to the behavior.

IAggregateWriteCache — a shared second-level snapshot cache for FetchForWriting (#674, #676)

The aggregate snapshot cache built and measured for Marten is now a shared contract in JasperFx.Events.Fetching, so Marten, Polecat and Fisher get one implementation of it rather than three.

  • IAggregateWriteCache, AggregateCacheKey, NulloAggregateWriteCache
  • RecentlyUsedAggregateWriteCache — the default, bounded and node-local
  • AggregateWriteCacheOptions — opt-in per aggregate type, off by default
  • EventRegistry.CacheAggregatesForWriting<T>() — inherited by every store's event options

The cached snapshot is a baseline only: the stream version and every event after the cached version are still read from the database on every call, and the optimistic concurrency assertion on append is untouched. A stale entry costs a larger delta query — never a wrong aggregate, never a suppressed concurrency failure. What it removes is the snapshot load, which on the measured workload was 4.5–4.9 ms of a 13.2 ms round.

The default implementation is backed by JasperFx.Core's existing RecentlyUsedCache, so this adds no new package dependency — a deliberate reversal of the prototype's Microsoft.Extensions.Caching.Memory reference, which would have pushed that onto every store.

New opt-in AggregateWriteCacheCompliance asserts that turning caching on is unobservable except in latency, including when the cached baseline is stale, ahead of the stream, or evicted.

Compliance suite fixes (#672, #677)

DocumentComplianceConfig gains a nullable StreamIdentity, so a document compliance suite can declare the stream identity style it needs instead of leaving each fixture to guess. DocumentSessionEventsCompliance appends by stream key and had no way to say so, which failed three of its five facts on every store defaulting to Guid identity — a correct store failing an undocumented precondition, which is a suite bug by definition. Additive: null means "leave the store on its own default", so no existing fixture changes.

BinaryEventAttribute is no longer sealed. A store that shipped its own before this one was promoted cannot delete it and could not derive from a sealed one, leaving it checking two attribute types indefinitely. Unsealing lets it subclass instead and collapse back to a single lookup, since attribute lookup matches by assignability. Stores without a pre-existing attribute should keep using the promoted one directly.

Downstream

Adoption is tracked per store: Marten JasperFx/marten#5248, #5249, #5250, #5251 · Polecat JasperFx/polecat#477, #478, #479 · Fisher JasperFx/fisher#96, #97, #98.

Full Changelog: V2.49.0...V2.51.0