#674 / JasperFx/jasperfx#676 moves IAggregateWriteCache into JasperFx.Events, so Marten, Polecat and Fisher share one second-level aggregate-snapshot cache. Marten's implementation is the existing local branch, rebased onto the promoted contract.
The branch
feature/aggregate-cache-fetchforwriting, built 2026-07-28, measured 2026-07-30, rebased onto V9.20.2. Two commits — 834e5a286 (Async lifecycle) and 5620d4039 (Inline lifecycle) — plus AGGREGATE_WRITE_CACHE_SPIKE.md. Local only, never pushed. 5/5 new Inline tests, 228/228 FetchForWriting regression, 1484 pass / 0 fail across EventSourcingTests at the time it was parked.
What changes on the rebase
Delete the Marten-local copies and use the promoted types:
| was, on the branch |
now |
Marten.Events.Fetching.IAggregateWriteCache |
JasperFx.Events.Fetching.IAggregateWriteCache |
Marten.Events.Fetching.AggregateCacheKey |
JasperFx.Events.Fetching.AggregateCacheKey |
MemoryCacheAggregateWriteCache |
RecentlyUsedAggregateWriteCache — see below |
NulloAggregateWriteCache |
promoted as-is |
FetchForWritingCacheOptions |
AggregateWriteCacheOptions |
EventGraph.AggregateWriteCaching / CacheAggregatesForWriting<T>() |
delete both — EventGraph derives from EventRegistry, which now declares them |
IEventStoreOptions.AggregateWriteCaching / CacheAggregatesForWriting<T>() |
keep only if Marten wants them on its own interface; the inherited members already work |
Everything genuinely Marten-shaped stays on the branch: FetchAsyncPlan.cs / .ForUpdate.cs, FetchInlinedPlan.cs / .ForUpdate.cs, AggregateWriteCacheWriteBack.cs (PendingAggregateCacheWrites + AggregateWriteCacheListener), and the tests.
⚠️ Drop the Microsoft.Extensions.Caching.Memory dependency. The spike's own follow-up list flagged it as a decision to make before graduation, and #676 decided it: the promoted default is backed by JasperFx.Core's existing RecentlyUsedCache, so Marten.csproj and Directory.Packages.props revert. Same three-member interface, same take-on-read semantics, no new package on core Marten. If a deployment specifically wants entries in a shared IMemoryCache, that is now a small adapter on the consumer's side rather than a dependency on everyone.
AggregateWriteCacheOptions.ResolveCache(Type) returns NulloAggregateWriteCache for a type nobody enrolled, so wherever the branch branches on "is caching enabled for T", that check can go — every take just misses.
Semantics are unchanged, and now written down
Grade 1 only. The promoted interface's XML docs carry the whole argument: baseline-only, version and delta always re-read, OCC untouched, take-on-read as a contract requirement rather than an implementation detail, and the version taken from the committed StreamAction rather than off the document.
⛔ The retired "trusted" variant is named as retired in the interface docs, with the measurement that retired it (0.19 ms of a 13.2 ms round). Please keep it retired on the rebase.
Compliance
public class aggregate_write_cache_compliance
: AggregateWriteCacheCompliance<MartenComplianceFixture, IDocumentOperations, IQuerySession>;
Needs one new registrar member — IComplianceStoreRegistrar.CacheAggregatesForWriting<TDoc>(IAggregateWriteCache cache), two lines against the shared EventRegistry:
opts.Events.AggregateWriteCaching.Cache = cache;
opts.Events.CacheAggregatesForWriting<TDoc>();
It carries a throwing default, so Marten keeps compiling until it enrolls.
The suite covers both lifecycles with the daemon deliberately never started, so the Async aggregate's snapshot always lags and every fetch has a real delta to fold. It asserts the OCC fact #674 asked for by name, plus the stale / ahead-of-stream / evicted baselines, and it does not pin when an entry is written — that genuinely differs by lifecycle, which the branch already knows.
Expect meaningful overlap with the branch's own caching_async_aggregates_for_writing and caching_inline_aggregates_for_writing. Keep the Marten-specific ones that assert things the shared suite deliberately does not — cache_hit_folds_only_the_delta (which counts queries rather than watching a hit counter) and a_failed_commit_leaves_no_entry_behind inspecting the cache directly — and let the shared suite own the portable facts.
Follow-ups still open from the spike doc
These were on AGGREGATE_WRITE_CACHE_SPIKE.md §6 and are not resolved by the promotion:
Needs JasperFx 2.51.0.
#674 / JasperFx/jasperfx#676 moves
IAggregateWriteCacheintoJasperFx.Events, so Marten, Polecat and Fisher share one second-level aggregate-snapshot cache. Marten's implementation is the existing local branch, rebased onto the promoted contract.The branch
feature/aggregate-cache-fetchforwriting, built 2026-07-28, measured 2026-07-30, rebased ontoV9.20.2. Two commits —834e5a286(Async lifecycle) and5620d4039(Inline lifecycle) — plusAGGREGATE_WRITE_CACHE_SPIKE.md. Local only, never pushed. 5/5 new Inline tests, 228/228FetchForWritingregression, 1484 pass / 0 fail acrossEventSourcingTestsat the time it was parked.What changes on the rebase
Delete the Marten-local copies and use the promoted types:
Marten.Events.Fetching.IAggregateWriteCacheJasperFx.Events.Fetching.IAggregateWriteCacheMarten.Events.Fetching.AggregateCacheKeyJasperFx.Events.Fetching.AggregateCacheKeyMemoryCacheAggregateWriteCacheRecentlyUsedAggregateWriteCache— see belowNulloAggregateWriteCacheFetchForWritingCacheOptionsAggregateWriteCacheOptionsEventGraph.AggregateWriteCaching/CacheAggregatesForWriting<T>()EventGraphderives fromEventRegistry, which now declares themIEventStoreOptions.AggregateWriteCaching/CacheAggregatesForWriting<T>()Everything genuinely Marten-shaped stays on the branch:
FetchAsyncPlan.cs/.ForUpdate.cs,FetchInlinedPlan.cs/.ForUpdate.cs,AggregateWriteCacheWriteBack.cs(PendingAggregateCacheWrites+AggregateWriteCacheListener), and the tests.Microsoft.Extensions.Caching.Memorydependency. The spike's own follow-up list flagged it as a decision to make before graduation, and #676 decided it: the promoted default is backed byJasperFx.Core's existingRecentlyUsedCache, soMarten.csprojandDirectory.Packages.propsrevert. Same three-member interface, same take-on-read semantics, no new package on core Marten. If a deployment specifically wants entries in a sharedIMemoryCache, that is now a small adapter on the consumer's side rather than a dependency on everyone.AggregateWriteCacheOptions.ResolveCache(Type)returnsNulloAggregateWriteCachefor a type nobody enrolled, so wherever the branch branches on "is caching enabled for T", that check can go — every take just misses.Semantics are unchanged, and now written down
Grade 1 only. The promoted interface's XML docs carry the whole argument: baseline-only, version and delta always re-read, OCC untouched, take-on-read as a contract requirement rather than an implementation detail, and the version taken from the committed
StreamActionrather than off the document.⛔ The retired "trusted" variant is named as retired in the interface docs, with the measurement that retired it (0.19 ms of a 13.2 ms round). Please keep it retired on the rebase.
Compliance
Needs one new registrar member —
IComplianceStoreRegistrar.CacheAggregatesForWriting<TDoc>(IAggregateWriteCache cache), two lines against the sharedEventRegistry:It carries a throwing default, so Marten keeps compiling until it enrolls.
The suite covers both lifecycles with the daemon deliberately never started, so the Async aggregate's snapshot always lags and every fetch has a real delta to fold. It asserts the OCC fact #674 asked for by name, plus the stale / ahead-of-stream / evicted baselines, and it does not pin when an entry is written — that genuinely differs by lifecycle, which the branch already knows.
Expect meaningful overlap with the branch's own
caching_async_aggregates_for_writingandcaching_inline_aggregates_for_writing. Keep the Marten-specific ones that assert things the shared suite deliberately does not —cache_hit_folds_only_the_delta(which counts queries rather than watching a hit counter) anda_failed_commit_leaves_no_entry_behindinspecting the cache directly — and let the shared suite own the portable facts.Follow-ups still open from the spike doc
These were on
AGGREGATE_WRITE_CACHE_SPIKE.md§6 and are not resolved by the promotion:UseIdentityMapForAggregates. The item-map bridge only fires when it is on, and that is currently silent — either require it for Inline caching or make the store path work without it.Decide the— decided in Typename not getting properly written to mt_streams when using private apply strategy #676, see above.Microsoft.Extensions.Caching.MemorydependencyNeeds JasperFx 2.51.0.