Skip to content

[#4791] Extract snapshot sourcing composition into its own enhancer - #4792

Merged
laura-devriendt-lemon merged 2 commits into
mainfrom
enhancement/4791/extract-snapshot-sourcing-enhancer
Jul 28, 2026
Merged

[#4791] Extract snapshot sourcing composition into its own enhancer#4792
laura-devriendt-lemon merged 2 commits into
mainfrom
enhancement/4791/extract-snapshot-sourcing-enhancer

Conversation

@laura-devriendt-lemon

@laura-devriendt-lemon laura-devriendt-lemon commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Fixes #4791

Supersedes #4788, which added a handlesSnapshotSourcing() capability method to EventStorageEngine. That approach left two signals meaning "do not decorate this engine" with opposite precedence against a configured SnapshotStore, and let an engine silently override an explicitly registered one. This replaces it: the identity comparison stays the only signal, and the decision moves to the configuration layer, where a topology can take it over.

What changes

The snapshot-composition decorator moves into its own enhancer. SnapshotSourcingConfigurationEnhancer registers that one decorator and nothing else. EventSourcingConfigurationDefaults registers it during its own enhance(ComponentRegistry), so applications registering those defaults manually receive it too. It is deliberately not contributed through the ServiceLoader, keeping a single registration channel and avoiding a duplicate-registration warning on every module build.

The composition rule moves to a static factory. SnapshotCapableEventStorageEngine.decorate(engine, snapshotStore) returns the engine as is when it is the snapshot store itself, and decorates it otherwise. One home for the rule, reusable by a topology composing per shard, tenant, or region.

Disabling the enhancer becomes the supported opt-out. Documented on the enhancer and on the snapshotting page, with the consequence of disabling it without composing elsewhere spelled out: snapshots keep being written while never being read.

Bug fixed along the way

decorate(...) also returns an already-decorated engine as is, which fixes a defect on main. DefaultComponentRegistry.copyWithDecoratorsAndEnhancers() copies the parent's decorator definitions into a module registry, and that registry re-runs the copied enhancers, registering the same decorator a second time. The identity check passes for the second application, because the already-decorated engine is not the registered store, so a module registering its own EventStorageEngine and SnapshotStore got an engine decorated twice.

That redundant decoration costs a layer and a stream hop per sourcing, and silently ignores the second decoration's snapshot store. It does not cause a second snapshot read: the outer decoration rewrites the condition to an absolute one before delegating, so the inner one passes it through. A snapshot is therefore still read exactly once, which is why decoratesAModulesOwnEventStorageEngineExactlyOnce asserts the nesting rather than a load count. I first wrote it as a load-count assertion and it passed with the guard removed, so it would have proven nothing. The structural version fails with the guard removed, and only it fails.

The new enhancer carries @RegistrationScope(scope = CURRENT), so the defaults being copied into module registries do not register it again. Without it, every module build logs a Duplicate Configuration Enhancer registration warning: 41 of them in one eventsourcing test run.

Behaviour

No behaviour change for existing applications, and no EventStorageEngine API added. All ten pre-existing EventSourcingConfigurationDefaultsTest cases pass unchanged, including decoratesEventStorageEngineWhenSnapshotStoreIsDifferentInstance_evenIfEngineImplementsSnapshotStore — a separately configured SnapshotStore still wins, since implementing SnapshotStore still does not opt an engine out.

The new enhancer is deliberately public rather than @Internal: its class literal is the opt-out contract, so renaming it in a minor would break callers at compile time.

Motivation

A routing event storage engine that fans out to one engine per context, each composed with that context's own snapshot store. Decorating the routing engine consumes the snapshot sourcing strategy before any context is known, so the composed engines never receive it. Tracked internally as axoniq-framework#263.

Testing

SnapshotSourcingConfigurationEnhancerTest covers order relative to the defaults, decoration with a snapshot store present, the manual-defaults-registration path, the disable opt-out both through the registry and from an earlier-ordered enhancer (the cross-repo path, and the order-sensitive one), single decoration in a module registry, and no decoration without a snapshot store. SnapshotCapableEventStorageEngineTest.Decorate covers all three factory branches plus the null contracts. Full eventsourcing module: 542 tests, no failures.

Comment thread docs/reference-guide/modules/tuning/pages/snapshotting.adoc Outdated

@hjohn hjohn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@smcvb smcvb added Type: Enhancement Use to signal an issue enhances an already existing feature of the project. Priority 2: Should High priority. Ideally, these issues are part of the release they’re assigned to. labels Jul 28, 2026
@smcvb smcvb added this to the Release 5.3.0 milestone Jul 28, 2026
@laura-devriendt-lemon
laura-devriendt-lemon force-pushed the enhancement/4791/extract-snapshot-sourcing-enhancer branch from b9d19c8 to e0bdc95 Compare July 28, 2026 08:57

@smcvb smcvb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me 👍

@laura-devriendt-lemon
laura-devriendt-lemon merged commit 559adcb into main Jul 28, 2026
8 checks passed
@laura-devriendt-lemon
laura-devriendt-lemon deleted the enhancement/4791/extract-snapshot-sourcing-enhancer branch July 28, 2026 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority 2: Should High priority. Ideally, these issues are part of the release they’re assigned to. Type: Enhancement Use to signal an issue enhances an already existing feature of the project.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow a storage topology to take over snapshot composition, and stop double-complementing in module registries

3 participants