org.axonframework.eventsourcing.EventSourcingRepository Maven / Gradle / Ivy
Show all versions of axon-eventsourcing Show documentation
/*
* Copyright (c) 2010-2023. 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;
import org.axonframework.common.caching.Cache;
import org.axonframework.common.lock.LockFactory;
import org.axonframework.eventhandling.DomainEventMessage;
import org.axonframework.eventsourcing.conflictresolution.ConflictResolution;
import org.axonframework.eventsourcing.conflictresolution.DefaultConflictResolver;
import org.axonframework.eventsourcing.eventstore.DomainEventStream;
import org.axonframework.eventsourcing.eventstore.EventStore;
import org.axonframework.messaging.annotation.HandlerDefinition;
import org.axonframework.messaging.annotation.ParameterResolverFactory;
import org.axonframework.messaging.unitofwork.CurrentUnitOfWork;
import org.axonframework.modelling.command.Aggregate;
import org.axonframework.modelling.command.AggregateNotFoundException;
import org.axonframework.modelling.command.LockAwareAggregate;
import org.axonframework.modelling.command.LockingRepository;
import org.axonframework.modelling.command.Repository;
import org.axonframework.modelling.command.RepositoryProvider;
import org.axonframework.modelling.command.RepositorySpanFactory;
import org.axonframework.modelling.command.inspection.AggregateModel;
import org.axonframework.tracing.SpanFactory;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
import static org.axonframework.common.BuilderUtils.assertNonNull;
/**
* Abstract repository implementation that allows easy implementation of an Event Sourcing mechanism. It will
* automatically publish new events to the given {@link org.axonframework.eventhandling.EventBus} and delegate event
* storage to the provided {@link org.axonframework.eventsourcing.eventstore.EventStore}.
*
* @param The type of aggregate this repository stores
* @author Allard Buijze
* @see org.axonframework.eventsourcing.eventstore.EventStore
* @since 0.1
*/
public class EventSourcingRepository extends LockingRepository> {
private final EventStore eventStore;
private final SnapshotTriggerDefinition snapshotTriggerDefinition;
private final AggregateFactory aggregateFactory;
private final RepositoryProvider repositoryProvider;
private final Predicate super DomainEventMessage>> eventStreamFilter;
/**
* Instantiate a {@link EventSourcingRepository} based on the fields contained in the {@link Builder}.
*
* A goal of the provided Builder is to create an {@link AggregateModel} specifying generic {@code T} as the
* aggregate type to be stored. All aggregates in this repository must be {@code instanceOf} this aggregate type. To
* instantiate this AggregateModel, either an {@link AggregateModel} can be provided directly or an
* {@code aggregateType} of type {@link Class} can be used. The latter will internally resolve to an AggregateModel.
* Thus, either the AggregateModel or the {@code aggregateType} should be provided. An
* {@link org.axonframework.common.AxonConfigurationException} is thrown if these criteria are not met. The same
* criteria holds for the {@link AggregateFactory}. Either the AggregateFactory can be set directly or it will be
* instantiated internally based on the {@code aggregateType}. Hence, one of both is a hard requirement, and will
* also result in an AxonConfigurationException if both are missing.
*
* Additionally, the builder will assert that the {@link LockFactory}, {@link EventStore} and
* {@link SnapshotTriggerDefinition} are not {@code null}, resulting in an AxonConfigurationException if for any of
* these this is the case. The {@link RepositorySpanFactory} is defaulted to a
* {@link org.axonframework.modelling.command.DefaultRepositorySpanFactory} backed by a
* {@link org.axonframework.tracing.NoOpSpanFactory}.
*
* @param builder the {@link Builder} used to instantiate a {@link EventSourcingRepository} instance
*/
protected EventSourcingRepository(Builder builder) {
super(builder);
this.eventStore = builder.eventStore;
this.aggregateFactory = builder.buildAggregateFactory();
this.snapshotTriggerDefinition = builder.snapshotTriggerDefinition;
this.repositoryProvider = builder.repositoryProvider;
this.eventStreamFilter = builder.eventStreamFilter;
}
/**
* Instantiate a Builder to be able to create a {@link EventSourcingRepository} for aggregate type {@code T}. Can
* also be used to instantiate a {@link CachingEventSourcingRepository} for aggregate type {@code T}. This Builder
* will check whether a {@link Cache} is provided. If this holds, the {@link Builder#build()} function returns a
* CachingEventSourcingRepository instead of an EventSourcingRepository.
*
* The {@link LockFactory} is defaulted to an {@link org.axonframework.common.lock.PessimisticLockFactory} and the
* {@link SnapshotTriggerDefinition} to a {@link NoSnapshotTriggerDefinition} implementation. A goal of this Builder
* goal is to create an {@link AggregateModel} specifying generic {@code T} as the aggregate type to be stored. All
* aggregates in this repository must be {@code instanceOf} this aggregate type. To instantiate this AggregateModel,
* either an {@link AggregateModel} can be provided directly or an {@code aggregateType} of type {@link Class} can
* be used. The latter will internally resolve to an AggregateModel. Thus, either the AggregateModel or the
* {@code aggregateType} should be provided. The same criteria holds for the {@link AggregateFactory}. Either the
* AggregateFactory can be set directly or it will be instantiated internally based on the {@code aggregateType}.
* Hence, one of both is a hard requirement.
*
* Additionally, the {@link EventStore} is a hard requirement and as such should be provided.
*
* @return a Builder to be able to create a {@link EventSourcingRepository}
*/
public static Builder builder(Class aggregateType) {
return new Builder<>(aggregateType);
}
/**
* Perform the actual loading of an aggregate. The necessary locks have been obtained.
*
* @param aggregateIdentifier the identifier of the aggregate to load
* @param expectedVersion The expected version of the loaded aggregate
* @return the fully initialized aggregate
*
* @throws AggregateDeletedException in case an aggregate existed in the past, but has been deleted
* @throws AggregateNotFoundException when an aggregate with the given identifier does not exist
*/
@Override
protected EventSourcedAggregate doLoadWithLock(String aggregateIdentifier, Long expectedVersion) {
SnapshotTrigger trigger = snapshotTriggerDefinition.prepareTrigger(aggregateFactory.getAggregateType());
DomainEventStream eventStream = readEvents(aggregateIdentifier);
if (!eventStream.hasNext()) {
throw new AggregateNotFoundException(aggregateIdentifier, "The aggregate was not found in the event store");
}
AggregateModel model = aggregateModel();
EventSourcedAggregate aggregate = spanFactory
.createInitializeStateSpan(model.type(), aggregateIdentifier)
.runSupplier(() -> doLoadAggregate(aggregateIdentifier, trigger, eventStream, model));
if (aggregate.isDeleted()) {
throw new AggregateDeletedException(aggregateIdentifier);
}
return aggregate;
}
private EventSourcedAggregate doLoadAggregate(String aggregateIdentifier,
SnapshotTrigger trigger,
DomainEventStream eventStream,
AggregateModel model) {
EventSourcedAggregate loadingAggregate = EventSourcedAggregate
.initialize(aggregateFactory.createAggregateRoot(
aggregateIdentifier,
eventStream.peek()),
model,
eventStore,
repositoryProvider,
trigger);
loadingAggregate.initializeState(eventStream);
return loadingAggregate;
}
/**
* Reads the events for the given aggregateIdentifier from the eventStore. this method may be overridden to
* add pre or postprocessing to the loading of an event stream
*
* @param aggregateIdentifier the identifier of the aggregate to load
* @return the domain event stream for the given aggregateIdentifier, with {@link #eventStreamFilter} applied if
* one was configured
*/
protected DomainEventStream readEvents(String aggregateIdentifier) {
DomainEventStream fullStream = eventStore.readEvents(aggregateIdentifier);
return eventStreamFilter != null ? fullStream.filter(eventStreamFilter) : fullStream;
}
@Override
protected void validateOnLoad(Aggregate aggregate, Long expectedVersion) {
if (expectedVersion != null && expectedVersion < aggregate.version()) {
DefaultConflictResolver conflictResolver =
new DefaultConflictResolver(eventStore, aggregate.identifierAsString(), expectedVersion,
aggregate.version());
ConflictResolution.initialize(conflictResolver);
CurrentUnitOfWork.get().onPrepareCommit(uow -> conflictResolver.ensureConflictsResolved());
} else {
super.validateOnLoad(aggregate, expectedVersion);
}
}
@Override
protected void reportIllegalState(LockAwareAggregate> aggregate) {
// event sourcing repositories are able to reconstruct the current state
}
@Override
protected EventSourcedAggregate doCreateNewForLock(Callable factoryMethod) throws Exception {
return EventSourcedAggregate.initialize(factoryMethod, aggregateModel(), eventStore, repositoryProvider,
snapshotTriggerDefinition.prepareTrigger(getAggregateType()));
}
@Override
protected void doSaveWithLock(EventSourcedAggregate aggregate) {
}
@Override
protected void doDeleteWithLock(EventSourcedAggregate aggregate) {
}
/**
* Returns the factory used by this repository.
*
* @return the factory used by this repository
*/
public AggregateFactory getAggregateFactory() {
return aggregateFactory;
}
/**
* Builder class to instantiate a {@link EventSourcingRepository}. Can also be used to instantiate a
* {@link CachingEventSourcingRepository}. This Builder will check whether a {@link Cache} is provided. If this
* holds, the {@link Builder#build()} function returns a CachingEventSourcingRepository instead of an
* EventSourcingRepository.
*
* The {@link LockFactory} is defaulted to an {@link org.axonframework.common.lock.PessimisticLockFactory},
* {@link RepositorySpanFactory} is defaulted to a
* {@link org.axonframework.modelling.command.DefaultRepositorySpanFactory} backed by a
* {@link org.axonframework.tracing.NoOpSpanFactory} and the {@link SnapshotTriggerDefinition} to a
* {@link NoSnapshotTriggerDefinition} implementation. A goal of this Builder goal is to create an
* {@link AggregateModel} specifying generic {@code T} as the aggregate type to be stored. All aggregates in this
* repository must be {@code instanceOf} this aggregate type. To instantiate this AggregateModel, either an
* {@link AggregateModel} can be provided directly or an {@code aggregateType} of type {@link Class} can be used.
* The latter will internally resolve to an AggregateModel. Thus, either the AggregateModel or the
* {@code aggregateType} should be provided. The same criteria holds for the {@link AggregateFactory}. Either the
* AggregateFactory can be set directly or it will be instantiated internally based on the {@code aggregateType}.
* Hence, one of both is a hard requirement.
*
* Additionally, the {@link EventStore} is a hard requirement and as such should be provided.
*
* @param a generic specifying the Aggregate type contained in this {@link Repository} implementation
*/
public static class Builder extends LockingRepository.Builder {
protected EventStore eventStore;
protected SnapshotTriggerDefinition snapshotTriggerDefinition = NoSnapshotTriggerDefinition.INSTANCE;
private AggregateFactory aggregateFactory;
protected RepositoryProvider repositoryProvider;
protected Cache cache;
protected Predicate super DomainEventMessage>> eventStreamFilter;
/**
* Creates a builder for a Repository for given {@code aggregateType}.
*
* @param aggregateType the {@code aggregateType} specifying the type of aggregate this {@link Repository} will
* store
*/
protected Builder(Class aggregateType) {
super(aggregateType);
}
@Override
public Builder parameterResolverFactory(@Nonnull ParameterResolverFactory parameterResolverFactory) {
super.parameterResolverFactory(parameterResolverFactory);
return this;
}
@Override
public Builder handlerDefinition(@Nonnull HandlerDefinition handlerDefinition) {
super.handlerDefinition(handlerDefinition);
return this;
}
@Override
public Builder aggregateModel(@Nonnull AggregateModel aggregateModel) {
super.aggregateModel(aggregateModel);
return this;
}
/**
* {@inheritDoc}
* If this Builder is used to instantiate a {@link CachingEventSourcingRepository}, do note that an optimistic
* locking strategy is not compatible with a caching approach.
*/
@Override
public Builder lockFactory(LockFactory lockFactory) {
super.lockFactory(lockFactory);
return this;
}
@Override
public Builder subtypes(@Nonnull Set> subtypes) {
super.subtypes(subtypes);
return this;
}
@Override
public Builder subtype(@Nonnull Class extends T> subtype) {
super.subtype(subtype);
return this;
}
@Override
@Deprecated
public Builder spanFactory(SpanFactory spanFactory) {
super.spanFactory(spanFactory);
return this;
}
@Override
public Builder spanFactory(RepositorySpanFactory spanFactory) {
super.spanFactory(spanFactory);
return this;
}
/**
* Sets the {@link EventStore} that holds the event stream this repository needs to event source an Aggregate.
*
* @param eventStore an {@link EventStore} that holds the event stream this repository needs to event source
* an Aggregate
* @return the current Builder instance, for fluent interfacing
*/
public Builder eventStore(EventStore eventStore) {
assertNonNull(eventStore, "EventStore may not be null");
this.eventStore = eventStore;
return this;
}
/**
* Sets the {@link SnapshotTriggerDefinition} specifying when to trigger a snapshot for an Aggregate contained
* in this repository.
*
* @param snapshotTriggerDefinition a {@link SnapshotTriggerDefinition} specifying when to trigger a snapshot
* for an Aggregate contained in this repository
* @return the current Builder instance, for fluent interfacing
*/
public Builder snapshotTriggerDefinition(SnapshotTriggerDefinition snapshotTriggerDefinition) {
assertNonNull(snapshotTriggerDefinition, "SnapshotTriggerDefinition may not be null");
this.snapshotTriggerDefinition = snapshotTriggerDefinition;
return this;
}
/**
* Sets the {@link AggregateFactory} used to create new Aggregate instances.
*
* @param aggregateFactory the {@link AggregateFactory} used to create new Aggregate instances
* @return the current Builder instance, for fluent interfacing
*/
public Builder aggregateFactory(AggregateFactory aggregateFactory) {
assertNonNull(aggregateFactory, "AggregateFactory may not be null");
this.aggregateFactory = aggregateFactory;
return this;
}
/**
* Sets the {@link RepositoryProvider} which services repositories for specific aggregate types.
*
* @param repositoryProvider a {@link RepositoryProvider} servicing repositories for specific aggregate types
* @return the current Builder instance, for fluent interfacing
*/
public Builder repositoryProvider(RepositoryProvider repositoryProvider) {
this.repositoryProvider = repositoryProvider;
return this;
}
/**
* Sets the {@link Cache} which services repositories for specific aggregate types.
*
* @param cache a {@link Cache} servicing repositories for specific aggregate types
* @return the current Builder instance, for fluent interfacing
*/
public Builder cache(Cache cache) {
this.cache = cache;
return this;
}
/**
* Sets the {@link Predicate} used to filter events when reading from the EventStore. By default, all events
* with the Aggregate identifier passed to {@link EventSourcingRepository#readEvents(String)} are returned.
* Calls to {@link #filterByAggregateType()} will overwrite this configuration and vice versa.
*
* @param filter a {@link Predicate} that may return false to discard events.
*/
public Builder eventStreamFilter(Predicate super DomainEventMessage>> filter) {
this.eventStreamFilter = filter;
return this;
}
/**
* Configures a filter that rejects events with a different Aggregate type than the one specified by this
* Repository's {@link AggregateModel}. This may be used to enable multiple Aggregate types to share overlapping
* Aggregate identifiers. Calls to {@link #eventStreamFilter(Predicate)} will overwrite this configuration and
* vice versa.
*
* If the caller supplies an explicit {@link AggregateModel} to this Builder, that must be done before
* calling this method.
*/
public Builder filterByAggregateType() {
final String aggregateType = buildAggregateModel().type();
return eventStreamFilter(event -> aggregateType.equals(event.getType()));
}
/**
* Initializes a {@link EventSourcingRepository} or {@link CachingEventSourcingRepository} as specified through
* this Builder. Will return a CachingEventSourcingRepository if {@link #cache(Cache)} has been set. Otherwise
* builds a regular EventSourcingRepository
*
* @param a generic extending {@link EventSourcingRepository}, so allowing both an EventSourcingRepository
* and {@link CachingEventSourcingRepository} return type
* @return a {@link EventSourcingRepository} or {@link CachingEventSourcingRepository} (if {@link #cache(Cache)}
* has been set) as specified through this Builder
*/
@SuppressWarnings("unchecked")
public > R build() {
return cache != null
? (R) new CachingEventSourcingRepository<>(this)
: (R) new EventSourcingRepository<>(this);
}
/**
* Instantiate the {@link AggregateFactory} of generic type {@code T} for the Aggregate this
* {@link EventSourcingRepository} will instantiate based on an event stream.
*
* @return a {@link AggregateFactory} of generic type {@code T} for the Aggregate this
* {@link EventSourcingRepository} will instantiate based on an event stream
*/
private AggregateFactory buildAggregateFactory() {
if (aggregateFactory == null) {
return new GenericAggregateFactory<>(buildAggregateModel());
} else {
return aggregateFactory;
}
}
@Override
protected void validate() {
super.validate();
assertNonNull(eventStore, "The EventStore is a hard requirement and should be provided");
if (aggregateFactory == null) {
assertNonNull(
aggregateType,
"No AggregateFactory is set, whilst either it or the aggregateType is a hard requirement"
);
return;
}
assertNonNull(
aggregateFactory,
"No aggregateType is set, whilst either it or the AggregateFactory is a hard requirement"
);
}
}
}