From e0bdc950a0cdd5e22ebd86eabc1e42acedbf9742 Mon Sep 17 00:00:00 2001 From: Laura Devriendt <94172218+laura-devriendt-lemon@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:05:04 +0200 Subject: [PATCH 1/2] [#4791] Extract snapshot sourcing composition into its own enhancer --- .../modules/tuning/pages/snapshotting.adoc | 28 +++ .../EventSourcingConfigurationDefaults.java | 21 +-- ...SnapshotSourcingConfigurationEnhancer.java | 90 +++++++++ .../SnapshotCapableEventStorageEngine.java | 30 +++ ...shotSourcingConfigurationEnhancerTest.java | 171 ++++++++++++++++++ ...SnapshotCapableEventStorageEngineTest.java | 62 +++++++ 6 files changed, 386 insertions(+), 16 deletions(-) create mode 100644 eventsourcing/src/main/java/org/axonframework/eventsourcing/configuration/SnapshotSourcingConfigurationEnhancer.java create mode 100644 eventsourcing/src/test/java/org/axonframework/eventsourcing/configuration/SnapshotSourcingConfigurationEnhancerTest.java diff --git a/docs/reference-guide/modules/tuning/pages/snapshotting.adoc b/docs/reference-guide/modules/tuning/pages/snapshotting.adoc index c9dd777c88..99801e160c 100644 --- a/docs/reference-guide/modules/tuning/pages/snapshotting.adoc +++ b/docs/reference-guide/modules/tuning/pages/snapshotting.adoc @@ -250,3 +250,31 @@ Only the events that occurred after that position are applied to bring the entit If a snapshot cannot be used, the full event stream is replayed and a new snapshot may be created after sourcing completes, depending on the configured `SnapshotPolicy`. This new snapshot replaces the previous one according to the current storage behavior. + +=== Snapshot sourcing composition + +An `EventStorageEngine` reads snapshots in one of two ways. +An engine that supports the snapshot sourcing strategy natively serves the snapshot and the events following it in a single call. +Any other engine is decorated with a `SnapshotCapableEventStorageEngine`, which loads the snapshot from the configured `SnapshotStore` first and then sources the events that follow it. + +That decoration is applied by the `SnapshotSourcingConfigurationEnhancer`, which the event sourcing defaults register for you. +The engine is left as is when no `SnapshotStore` is configured, and when the engine is the configured `SnapshotStore` itself. + +A storage topology that composes snapshot sourcing itself disables that enhancer and takes over the responsibility: + +[source,java] +---- +configurer.componentRegistry( + registry -> registry.disableEnhancer(SnapshotSourcingConfigurationEnhancer.class) +); +---- + +An engine routing sourcing to one engine per shard, tenant, or region is the case this exists for. +The target is only known once a message is inspected, so decorating the routing engine would read snapshots before then, and the engines it routes to would never receive the snapshot sourcing strategy. +Such a topology composes each of its engines with that engine's own snapshot store instead, using `SnapshotCapableEventStorageEngine.decorate(engine, snapshotStore)`. + +[NOTE] +==== +Disabling the enhancer without composing snapshot sourcing elsewhere leaves snapshots being written while never being read. +Sourcing then replays an entity's full event history, which is correct but forfeits the optimization. +==== diff --git a/eventsourcing/src/main/java/org/axonframework/eventsourcing/configuration/EventSourcingConfigurationDefaults.java b/eventsourcing/src/main/java/org/axonframework/eventsourcing/configuration/EventSourcingConfigurationDefaults.java index 5bac6ec8dd..01b0b7bd3a 100644 --- a/eventsourcing/src/main/java/org/axonframework/eventsourcing/configuration/EventSourcingConfigurationDefaults.java +++ b/eventsourcing/src/main/java/org/axonframework/eventsourcing/configuration/EventSourcingConfigurationDefaults.java @@ -23,11 +23,9 @@ import org.axonframework.eventsourcing.eventstore.EventStorageEngine; import org.axonframework.eventsourcing.eventstore.EventStore; import org.axonframework.eventsourcing.eventstore.InterceptingEventStore; -import org.axonframework.eventsourcing.eventstore.SnapshotCapableEventStorageEngine; import org.axonframework.eventsourcing.eventstore.StorageEngineBackedEventStore; import org.axonframework.eventsourcing.eventstore.TagResolver; import org.axonframework.eventsourcing.eventstore.inmemory.InMemoryEventStorageEngine; -import org.axonframework.eventsourcing.snapshot.store.SnapshotStore; import org.axonframework.messaging.core.MessageDispatchInterceptor; import org.axonframework.messaging.core.configuration.MessagingConfigurationDefaults; import org.axonframework.messaging.core.interception.DispatchInterceptorRegistry; @@ -52,13 +50,13 @@ * * Furthermore, this enhancer will decorate the: * + * It also registers the {@link SnapshotSourcingConfigurationEnhancer}, which decorates the + * {@link EventStorageEngine} to support the {@link org.axonframework.eventsourcing.eventstore.SourcingStrategy.Snapshot + * snapshot sourcing strategy}. Registering it here rather than through the {@link java.util.ServiceLoader} means + * applications registering {@code this} enhancer manually receive it as well. * * @author Steven van Beelen * @author John Hendrikx @@ -85,16 +83,7 @@ public void enhance(ComponentRegistry registry) { .registerIfNotPresent(EventStorageEngine.class, EventSourcingConfigurationDefaults::defaultEventStorageEngine) .registerIfNotPresent(EventStore.class, EventSourcingConfigurationDefaults::simpleEventStore); - registry.registerDecorator( - EventStorageEngine.class, - SnapshotCapableEventStorageEngine.DECORATION_ORDER, - (config, name, engine) -> { - SnapshotStore snapshotStore = config.getOptionalComponent(SnapshotStore.class).orElse(null); - return snapshotStore == null || snapshotStore == engine - ? engine - : new SnapshotCapableEventStorageEngine(engine, snapshotStore); - } - ); + registry.registerEnhancer(new SnapshotSourcingConfigurationEnhancer()); registry.registerDecorator( EventStore.class, InterceptingEventStore.DECORATION_ORDER, diff --git a/eventsourcing/src/main/java/org/axonframework/eventsourcing/configuration/SnapshotSourcingConfigurationEnhancer.java b/eventsourcing/src/main/java/org/axonframework/eventsourcing/configuration/SnapshotSourcingConfigurationEnhancer.java new file mode 100644 index 0000000000..58e376f696 --- /dev/null +++ b/eventsourcing/src/main/java/org/axonframework/eventsourcing/configuration/SnapshotSourcingConfigurationEnhancer.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2010-2026. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.axonframework.eventsourcing.configuration; + +import org.axonframework.common.annotation.RegistrationScope; +import org.axonframework.common.configuration.ComponentRegistry; +import org.axonframework.common.configuration.ConfigurationEnhancer; +import org.axonframework.eventsourcing.eventstore.EventStorageEngine; +import org.axonframework.eventsourcing.eventstore.SnapshotCapableEventStorageEngine; +import org.axonframework.eventsourcing.eventstore.SourcingStrategy; +import org.axonframework.eventsourcing.snapshot.store.SnapshotStore; + +/** + * A {@link ConfigurationEnhancer} making the {@link EventStorageEngine} support the + * {@link SourcingStrategy.Snapshot snapshot sourcing strategy}, by decorating it with the configured + * {@link SnapshotStore} through {@link SnapshotCapableEventStorageEngine#decorate(EventStorageEngine, SnapshotStore)}. + *

+ * The engine is left as is when no {@code SnapshotStore} is configured, and when it is the configured + * {@code SnapshotStore} itself. The latter resolves the snapshot within its own {@code source} call, in a single round + * trip, which decorating would undo. + *

+ * Registered by {@link EventSourcingConfigurationDefaults}, so applications registering those defaults manually receive + * it as well. It is deliberately not contributed through the {@link java.util.ServiceLoader}, unlike its sibling + * enhancers, to keep that single registration channel. Registered for the {@link RegistrationScope.Scope#CURRENT + * current} registry only, since the defaults registering it are copied into every module registry and would otherwise + * register it there a second time. + *

+ * A storage topology composing snapshot sourcing itself disables this enhancer, taking over that responsibility: + *

{@code
+ * configurer.componentRegistry(
+ *         registry -> registry.disableEnhancer(SnapshotSourcingConfigurationEnhancer.class)
+ * );
+ * }
+ * An engine routing {@code source} to one engine per shard, tenant, or region is the case this exists for: the shard is + * only known once a message is inspected, so decorating the routing engine would resolve snapshots before then, and the + * engines it routes to would never receive the snapshot sourcing strategy. Such a topology composes each of its engines + * with that engine's own snapshot store instead, through the same + * {@link SnapshotCapableEventStorageEngine#decorate(EventStorageEngine, SnapshotStore) decorate} operation. + *

+ * Disabling this enhancer without composing snapshot sourcing elsewhere leaves snapshots being written while never + * being read: sourcing then replays an entity's full event history. + * + * @author Laura Devriendt + * @since 5.3.0 + */ +@RegistrationScope(scope = RegistrationScope.Scope.CURRENT) +public class SnapshotSourcingConfigurationEnhancer implements ConfigurationEnhancer { + + /** + * The order of {@code this} enhancer compared to others, equal to 10 positions after + * {@link EventSourcingConfigurationDefaults} (thus, + * {@link EventSourcingConfigurationDefaults#ENHANCER_ORDER} + 10). + *

+ * What this order governs is the window in which {@code this} enhancer can still be disabled: a disable is only + * recorded while the enhancer has not been invoked yet, and is otherwise reported as having no effect. Running late + * therefore leaves every earlier-ordered enhancer free to take over snapshot composition. + */ + public static final int ENHANCER_ORDER = EventSourcingConfigurationDefaults.ENHANCER_ORDER + 10; + + @Override + public int order() { + return ENHANCER_ORDER; + } + + @Override + public void enhance(ComponentRegistry registry) { + registry.registerDecorator( + EventStorageEngine.class, + SnapshotCapableEventStorageEngine.DECORATION_ORDER, + (config, name, engine) -> config + .getOptionalComponent(SnapshotStore.class) + .map(snapshotStore -> SnapshotCapableEventStorageEngine.decorate(engine, snapshotStore)) + .orElse(engine) + ); + } +} diff --git a/eventsourcing/src/main/java/org/axonframework/eventsourcing/eventstore/SnapshotCapableEventStorageEngine.java b/eventsourcing/src/main/java/org/axonframework/eventsourcing/eventstore/SnapshotCapableEventStorageEngine.java index cb1be75387..e80942de56 100644 --- a/eventsourcing/src/main/java/org/axonframework/eventsourcing/eventstore/SnapshotCapableEventStorageEngine.java +++ b/eventsourcing/src/main/java/org/axonframework/eventsourcing/eventstore/SnapshotCapableEventStorageEngine.java @@ -77,6 +77,36 @@ public SnapshotCapableEventStorageEngine(EventStorageEngine delegate, SnapshotSt this.snapshotStore = Objects.requireNonNull(snapshotStore, "The snapshotStore parameter cannot be null."); } + /** + * Returns an {@link EventStorageEngine} that supports the {@link SourcingStrategy.Snapshot} sourcing strategy, + * given the {@code engine} to source events from and the {@code snapshotStore} holding its snapshots. + *

+ * The given {@code engine} is returned as is when it is the given {@code snapshotStore} itself. Such an engine + * resolves the snapshot within its own {@link #source(SourcingCondition, ProcessingContext) source} call, serving + * the snapshot and the events following it in a single round trip. Decorating it would resolve the snapshot + * separately and pass an {@link SourcingStrategy.Absolute absolute strategy} inward, costing that optimization. + *

+ * An {@code engine} that is already decorated is returned as is too, so composing twice is harmless. It keeps + * resolving snapshots from the store it was decorated with, and the given {@code snapshotStore} is ignored for it. + * Decorating again would put the given store in front of that one instead of adding anything. + *

+ * Any other {@code engine} is decorated, resolving the snapshot from the {@code snapshotStore} before sourcing the + * events that follow it. + * + * @param engine the engine to source events from + * @param snapshotStore the store holding the snapshots of the given {@code engine} + * @return an event storage engine supporting the snapshot sourcing strategy + * @throws NullPointerException if the given {@code engine} or {@code snapshotStore} is {@code null} + * @since 5.3.0 + */ + public static EventStorageEngine decorate(EventStorageEngine engine, SnapshotStore snapshotStore) { + Objects.requireNonNull(engine, "The engine parameter cannot be null."); + Objects.requireNonNull(snapshotStore, "The snapshotStore parameter cannot be null."); + return engine == snapshotStore || engine instanceof SnapshotCapableEventStorageEngine + ? engine + : new SnapshotCapableEventStorageEngine(engine, snapshotStore); + } + @Override public MessageStream source(SourcingCondition condition, @Nullable ProcessingContext context) { if (condition.strategy() instanceof SourcingStrategy.Snapshot s) { diff --git a/eventsourcing/src/test/java/org/axonframework/eventsourcing/configuration/SnapshotSourcingConfigurationEnhancerTest.java b/eventsourcing/src/test/java/org/axonframework/eventsourcing/configuration/SnapshotSourcingConfigurationEnhancerTest.java new file mode 100644 index 0000000000..7a75571ca7 --- /dev/null +++ b/eventsourcing/src/test/java/org/axonframework/eventsourcing/configuration/SnapshotSourcingConfigurationEnhancerTest.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2010-2026. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.axonframework.eventsourcing.configuration; + +import org.axonframework.common.configuration.ApplicationConfigurer; +import org.axonframework.common.configuration.BaseModule; +import org.axonframework.common.configuration.ComponentRegistry; +import org.axonframework.common.configuration.Configuration; +import org.axonframework.common.configuration.ConfigurationEnhancer; +import org.axonframework.eventsourcing.eventstore.EventStorageEngine; +import org.axonframework.eventsourcing.eventstore.SnapshotCapableEventStorageEngine; +import org.axonframework.eventsourcing.eventstore.inmemory.InMemoryEventStorageEngine; +import org.axonframework.eventsourcing.snapshot.inmemory.InMemorySnapshotStore; +import org.axonframework.eventsourcing.snapshot.store.SnapshotStore; +import org.axonframework.messaging.core.configuration.MessagingConfigurer; +import org.jspecify.annotations.NonNull; +import org.junit.jupiter.api.*; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test class validating the {@link SnapshotSourcingConfigurationEnhancer}. + * + * @author Laura Devriendt + */ +class SnapshotSourcingConfigurationEnhancerTest { + + private final SnapshotSourcingConfigurationEnhancer testSubject = new SnapshotSourcingConfigurationEnhancer(); + + @Test + void orderEqualsEnhancerOrderConstant() { + assertThat(testSubject.order()).isEqualTo(SnapshotSourcingConfigurationEnhancer.ENHANCER_ORDER); + } + + // The defaults register this enhancer while they are themselves being invoked, and an enhancer registered that way + // is invoked in its order relative to the enhancers that have not run yet. A lower order would place it before + // enhancers that already ran. + @Test + void orderExceedsTheEventSourcingDefaults() { + assertThat(SnapshotSourcingConfigurationEnhancer.ENHANCER_ORDER) + .isGreaterThan(EventSourcingConfigurationDefaults.ENHANCER_ORDER); + } + + @Test + void decoratesTheEventStorageEngineWhenASnapshotStoreIsPresent() { + ApplicationConfigurer configurer = EventSourcingConfigurer.create(); + configurer.componentRegistry(cr -> cr.registerComponent(SnapshotStore.class, c -> new InMemorySnapshotStore())); + + Configuration resultConfig = configurer.build(); + + assertThat(resultConfig.getComponent(EventStorageEngine.class)) + .isInstanceOf(SnapshotCapableEventStorageEngine.class); + } + + // Registering the event sourcing defaults by hand must yield the same decoration as EventSourcingConfigurer does, + // since those defaults are what register this enhancer. + @Test + void decoratesTheEventStorageEngineWhenTheEventSourcingDefaultsAreRegisteredManually() { + ApplicationConfigurer configurer = MessagingConfigurer.create(); + configurer.componentRegistry(cr -> cr.registerEnhancer(new EventSourcingConfigurationDefaults()) + .registerComponent(SnapshotStore.class, + c -> new InMemorySnapshotStore())); + + Configuration resultConfig = configurer.build(); + + assertThat(resultConfig.getComponent(EventStorageEngine.class)) + .isInstanceOf(SnapshotCapableEventStorageEngine.class); + } + + // The supported opt-out for a storage topology composing snapshot sourcing itself. + @Test + void leavesTheEventStorageEngineUndecoratedWhenDisabled() { + ApplicationConfigurer configurer = EventSourcingConfigurer.create(); + InMemoryEventStorageEngine composedEngine = new InMemoryEventStorageEngine(); + configurer.componentRegistry( + cr -> cr.registerComponent(SnapshotStore.class, c -> new InMemorySnapshotStore()) + .registerComponent(EventStorageEngine.class, c -> composedEngine) + .disableEnhancer(SnapshotSourcingConfigurationEnhancer.class) + ); + + Configuration resultConfig = configurer.build(); + + assertThat(resultConfig.getComponent(EventStorageEngine.class)).isSameAs(composedEngine); + } + + // A storage topology switches this off from its own enhancer, which is the cross-repo opt-out. That only works while + // this enhancer has not run yet, so the disabling enhancer has to be ordered before it. + @Test + void leavesTheEventStorageEngineUndecoratedWhenDisabledByAnEarlierEnhancer() { + ApplicationConfigurer configurer = EventSourcingConfigurer.create(); + InMemoryEventStorageEngine composedEngine = new InMemoryEventStorageEngine(); + configurer.componentRegistry( + cr -> cr.registerComponent(SnapshotStore.class, config -> new InMemorySnapshotStore()) + .registerComponent(EventStorageEngine.class, config -> composedEngine) + .registerEnhancer(new ComposesSnapshotSourcingItself()) + ); + + Configuration resultConfig = configurer.build(); + + assertThat(resultConfig.getComponent(EventStorageEngine.class)).isSameAs(composedEngine); + } + + // A module registry receives the parent's decorator definitions by copy, so the snapshot decorator is applied a + // second time to a module registering its own engine and snapshot store. The redundant decoration is not observable + // through snapshot loads, since the outer decoration rewrites the condition to an absolute one before delegating and + // the inner one then passes it through, so the nesting itself is what has to be asserted. + @Test + void decoratesAModulesOwnEventStorageEngineExactlyOnce() { + ApplicationConfigurer configurer = EventSourcingConfigurer.create(); + configurer.componentRegistry(parentRegistry -> parentRegistry.registerModule( + new EventSourcingModule("snapshot-module").componentRegistry( + moduleRegistry -> moduleRegistry + .registerComponent(SnapshotStore.class, config -> new InMemorySnapshotStore()) + .registerComponent(EventStorageEngine.class, + config -> new InMemoryEventStorageEngine()) + ) + )); + + Configuration moduleConfig = configurer.build().getModuleConfiguration("snapshot-module").orElseThrow(); + + EventStorageEngine engine = moduleConfig.getComponent(EventStorageEngine.class); + assertThat(engine).isInstanceOf(SnapshotCapableEventStorageEngine.class); + assertThat(engine).extracting("delegate").isNotInstanceOf(SnapshotCapableEventStorageEngine.class); + } + + @Test + void leavesTheEventStorageEngineUndecoratedWhenNoSnapshotStoreIsPresent() { + ApplicationConfigurer configurer = EventSourcingConfigurer.create(); + + Configuration resultConfig = configurer.build(); + + assertThat(resultConfig.getComponent(EventStorageEngine.class)) + .isNotInstanceOf(SnapshotCapableEventStorageEngine.class); + } + + private static class EventSourcingModule extends BaseModule { + + private EventSourcingModule(String name) { + super(name); + } + } + + // Stands in for a storage topology composing snapshot sourcing per shard, tenant, or region. Ordered before the + // enhancer it disables, since a disable is only recorded while that enhancer has not run yet. + private static class ComposesSnapshotSourcingItself implements ConfigurationEnhancer { + + @Override + public void enhance(@NonNull ComponentRegistry registry) { + registry.disableEnhancer(SnapshotSourcingConfigurationEnhancer.class); + } + + @Override + public int order() { + return SnapshotSourcingConfigurationEnhancer.ENHANCER_ORDER - 1; + } + } +} diff --git a/eventsourcing/src/test/java/org/axonframework/eventsourcing/eventstore/SnapshotCapableEventStorageEngineTest.java b/eventsourcing/src/test/java/org/axonframework/eventsourcing/eventstore/SnapshotCapableEventStorageEngineTest.java index 5a9a70d4f1..48571b8784 100644 --- a/eventsourcing/src/test/java/org/axonframework/eventsourcing/eventstore/SnapshotCapableEventStorageEngineTest.java +++ b/eventsourcing/src/test/java/org/axonframework/eventsourcing/eventstore/SnapshotCapableEventStorageEngineTest.java @@ -38,6 +38,7 @@ import java.util.concurrent.CompletableFuture; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.axonframework.messaging.eventhandling.EventTestUtils.createEvent; /** @@ -166,6 +167,67 @@ private static List sourceMessages(MessageStream SnapshotCapableEventStorageEngine.decorate(null, snapshotStore)) + .isInstanceOf(NullPointerException.class) + .hasMessage("The engine parameter cannot be null."); + } + + @Test + void rejectsANullSnapshotStore() { + assertThatThrownBy(() -> SnapshotCapableEventStorageEngine.decorate(delegate, null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("The snapshotStore parameter cannot be null."); + } + } + + // An engine that is its own snapshot store, as PostgresqlEventStorageEngine is. + private static class SnapshotResolvingEngine extends InMemoryEventStorageEngine implements SnapshotStore { + + @Override + public CompletableFuture store(QualifiedName qn, Object id, Snapshot s, ProcessingContext context) { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture load(QualifiedName qn, Object id, ProcessingContext context) { + return CompletableFuture.completedFuture(null); + } + } + private static SnapshotStore failingSnapshotStore() { return new SnapshotStore() { @Override From c3b2c6c827e508399979a7c30b053cf604a2ad2d Mon Sep 17 00:00:00 2001 From: Laura Devriendt <94172218+laura-devriendt-lemon@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:00:43 +0200 Subject: [PATCH 2/2] [#4791] Process review comments --- .../reference-guide/modules/tuning/pages/snapshotting.adoc | 7 ++++--- .../eventstore/SnapshotCapableEventStorageEngine.java | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/reference-guide/modules/tuning/pages/snapshotting.adoc b/docs/reference-guide/modules/tuning/pages/snapshotting.adoc index 99801e160c..93563cbe04 100644 --- a/docs/reference-guide/modules/tuning/pages/snapshotting.adoc +++ b/docs/reference-guide/modules/tuning/pages/snapshotting.adoc @@ -253,9 +253,10 @@ This new snapshot replaces the previous one according to the current storage beh === Snapshot sourcing composition -An `EventStorageEngine` reads snapshots in one of two ways. -An engine that supports the snapshot sourcing strategy natively serves the snapshot and the events following it in a single call. -Any other engine is decorated with a `SnapshotCapableEventStorageEngine`, which loads the snapshot from the configured `SnapshotStore` first and then sources the events that follow it. +An `EventStorageEngine` reads snapshots in one of two ways: + +* An engine that supports the snapshot sourcing strategy natively serves the snapshot and the events following it in a single call. +* Any other engine is decorated with a `SnapshotCapableEventStorageEngine`, which loads the snapshot from the configured `SnapshotStore` first and then sources the events that follow it. That decoration is applied by the `SnapshotSourcingConfigurationEnhancer`, which the event sourcing defaults register for you. The engine is left as is when no `SnapshotStore` is configured, and when the engine is the configured `SnapshotStore` itself. diff --git a/eventsourcing/src/main/java/org/axonframework/eventsourcing/eventstore/SnapshotCapableEventStorageEngine.java b/eventsourcing/src/main/java/org/axonframework/eventsourcing/eventstore/SnapshotCapableEventStorageEngine.java index e80942de56..e67475db35 100644 --- a/eventsourcing/src/main/java/org/axonframework/eventsourcing/eventstore/SnapshotCapableEventStorageEngine.java +++ b/eventsourcing/src/main/java/org/axonframework/eventsourcing/eventstore/SnapshotCapableEventStorageEngine.java @@ -84,7 +84,7 @@ public SnapshotCapableEventStorageEngine(EventStorageEngine delegate, SnapshotSt * The given {@code engine} is returned as is when it is the given {@code snapshotStore} itself. Such an engine * resolves the snapshot within its own {@link #source(SourcingCondition, ProcessingContext) source} call, serving * the snapshot and the events following it in a single round trip. Decorating it would resolve the snapshot - * separately and pass an {@link SourcingStrategy.Absolute absolute strategy} inward, costing that optimization. + * separately and pass an {@link SourcingStrategy.Absolute absolute strategy} inward, disabling that optimization. *

* An {@code engine} that is already decorated is returned as is too, so composing twice is harmless. It keeps * resolving snapshots from the store it was decorated with, and the given {@code snapshotStore} is ignored for it.