All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dk.cloudcreate.essentials.components.eventsourced.aggregates.flex.FlexAggregateRepository Maven / Gradle / Ivy

There is a newer version: 0.40.19
Show newest version
/*
 * Copyright 2021-2024 the original author or authors.
 *
 * 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
 *
 *      https://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 dk.cloudcreate.essentials.components.eventsourced.aggregates.flex;

import dk.cloudcreate.essentials.components.eventsourced.aggregates.*;
import dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.*;
import dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.eventstream.*;
import dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.persistence.*;
import dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.transaction.EventStoreUnitOfWorkFactory;
import dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.types.EventOrder;
import dk.cloudcreate.essentials.components.foundation.transaction.*;
import dk.cloudcreate.essentials.shared.reflection.Reflector;
import dk.cloudcreate.essentials.types.LongRange;
import org.slf4j.*;

import java.util.*;

import static dk.cloudcreate.essentials.shared.FailFast.requireNonNull;

/**
 * Opinionated {@link FlexAggregate} Repository that's built to persist and load a specific {@link FlexAggregate} type in combination
 * with {@link EventStore}, {@link EventStoreUnitOfWorkFactory} and a {@link FlexAggregateRepository}.
*

* Here's how to create an {@link FlexAggregateRepository} instance that can persist an {@link FlexAggregate} * of type Order which has an aggregate id of type OrderId: *

{@code
 * FlexAggregateRepository repository =
 *      FlexAggregateRepository.from(
 *                eventStores,
 *                standardSingleTenantConfiguration(
 *                     AggregateType.of("Orders"),
 *                     new JacksonJSONEventSericreateObjectMapper(),
 *                     AggregateIdSerializer.serializerFor(OrderId.class),
 *                     IdentifierColumnType.UUID,
 *                     JSONColumnType.JSONB),
 *                 unitOfWorkFactory,
 *                 OrderId.class,
 *                 Order.class
 *                );
 * }
* Here's a typical usage pattern for when you want to persist an new {@link FlexAggregate} instance * (i.e. the {@link EventStore} doesn't contain an events related to the given Aggregate id): *
{@code
 * unitOfWorkFactory.usingUnitOfWork(unitOfWork -> {
 *      var eventsToPersist = Order.createNewOrder(orderId, CustomerId.random(), 123);
 *      repository.persist(eventsToPersist);
 *  });
 * }
* Here's the typical usage pattern for {@link FlexAggregateRepository} for already existing {@link FlexAggregate} * instance (i.e. an instance that has events in the {@link EventStore}): *
{@code
 * unitOfWorkFactory.usingUnitOfWork(unitOfWork -> {
 *      var order = repository.load(orderId);
 *      var eventsToPersist = order.accept();
 *      repository.persist(eventsToPersist);
 *  });
 * }
 * 
* * @param the aggregate id type * @param the aggregate type * @see FlexAggregate */ public interface FlexAggregateRepository> { /** * Create a {@link FlexAggregateRepository} - the {@link EventStore} will be configured with the supplied eventStreamConfiguration.
* * @param the aggregate ID type * @param the concrete aggregate type (MUST be a subtype of {@link FlexAggregate}) * @param eventStore The {@link EventStore} instance to use * @param aggregateEventStreamConfiguration the configuration for the event stream that will contain all the events related to the aggregate type * @param unitOfWorkFactory The factory that provides {@link UnitOfWork}'s * @param aggregateIdType the concrete aggregate ID type * @param aggregateImplementationType the concrete aggregate type (MUST be a subtype of {@link FlexAggregate}) * @return a repository instance that can be used load, add and query aggregates of type aggregateImplementationType */ static > FlexAggregateRepository from(ConfigurableEventStore eventStore, CONFIG aggregateEventStreamConfiguration, EventStoreUnitOfWorkFactory unitOfWorkFactory, Class aggregateIdType, Class aggregateImplementationType) { return new DefaultFlexAggregateRepository<>(eventStore, aggregateEventStreamConfiguration, unitOfWorkFactory, aggregateIdType, aggregateImplementationType); } /** * Create a {@link FlexAggregateRepository} - if missing, the {@link EventStore} will be configured with the * default {@link AggregateEventStreamConfiguration} based on the {@link AggregateEventStreamConfigurationFactory} that the {@link EventStore}'s {@link AggregateEventStreamPersistenceStrategy} * is configured with
* * @param the aggregate ID type * @param the concrete aggregate type (MUST be a subtype of {@link FlexAggregate}) * @param eventStore The {@link EventStore} instance to use * @param aggregateType the aggregate type being handled by this repository * @param unitOfWorkFactory The factory that provides {@link UnitOfWork}'s * @param aggregateIdType the concrete aggregate ID type * @param aggregateImplementationType the concrete aggregate type (MUST be a subtype of {@link FlexAggregate}) * @return a repository instance that can be used load, add and query aggregates of type aggregateImplementationType */ static > FlexAggregateRepository from(ConfigurableEventStore eventStore, AggregateType aggregateType, EventStoreUnitOfWorkFactory unitOfWorkFactory, Class aggregateIdType, Class aggregateImplementationType) { return new DefaultFlexAggregateRepository<>(eventStore, aggregateType, unitOfWorkFactory, aggregateIdType, aggregateImplementationType); } // ------------------------------------------------------------------------------------------------------------------------------------------------- /** * Try to load an {@link FlexAggregate} instance with the specified aggregateId from the underlying {@link EventStore}
* If the aggregate instance exists it will be associated with the {@link UnitOfWorkFactory#getRequiredUnitOfWork()} * and any changes to will be tracked and will be persisted to the underlying {@link EventStore} when the {@link UnitOfWork} * is committed. * * @param aggregateId the id of the aggregate we want to load * @param expectedLatestEventOrder the expected {@link PersistedEvent#eventOrder()} of the last event stored in relation to the given aggregate instance * @return an {@link Optional} with the matching {@link FlexAggregate} instance if it exists, otherwise it will return an {@link Optional#empty()} * @throws OptimisticAggregateLoadException in case the {@link PersistedEvent#eventOrder()} of the last event stored in relation to the given aggregate instance * is different from the expectedLatestEventOrder */ default Optional tryLoad(ID aggregateId, long expectedLatestEventOrder) { return tryLoad(aggregateId, Optional.of(expectedLatestEventOrder)); } /** * Try to load an {@link FlexAggregate} instance with the specified aggregateId from the underlying {@link EventStore}
* If the aggregate instance exists it will be associated with the {@link UnitOfWorkFactory#getRequiredUnitOfWork()} * and any changes to will be tracked and will be persisted to the underlying {@link EventStore} when the {@link UnitOfWork} * is committed. * * @param aggregateId the id of the aggregate we want to load * @param expectedLatestEventOrder Optional with the expected {@link PersistedEvent#eventOrder()} of the last event stored in relation to the given aggregate instance (if any) * @return an {@link Optional} with the matching {@link FlexAggregate} instance if it exists, otherwise it will return an {@link Optional#empty()} * @throws OptimisticAggregateLoadException in case the {@link PersistedEvent#eventOrder()} of the last event stored in relation to the given aggregate instance * is different from the expectedLatestEventOrder */ Optional tryLoad(ID aggregateId, Optional expectedLatestEventOrder); /** * Try to load an {@link FlexAggregate} instance with the specified aggregateId from the underlying {@link EventStore}
* If the aggregate instance exists it will be associated with the {@link UnitOfWorkFactory#getRequiredUnitOfWork()} * and any changes to will be tracked and will be persisted to the underlying {@link EventStore} when the {@link UnitOfWork} * is committed. * * @param aggregateId the id of the aggregate we want to load * @return an {@link Optional} with the matching {@link FlexAggregate} instance if it exists, otherwise it will return an {@link Optional#empty()} * @throws OptimisticAggregateLoadException in case the {@link PersistedEvent#eventOrder()} of the last event stored in relation to the given aggregate instance * is different from the expectedLatestEventOrder */ default Optional tryLoad(ID aggregateId) { return tryLoad(aggregateId, Optional.empty()); } /** * Load an {@link FlexAggregate} instance with the specified aggregateId from the underlying {@link EventStore}
* The loaded aggregate instance will be associated with the {@link UnitOfWorkFactory#getRequiredUnitOfWork()} * and any changes to will be tracked and will be persisted to the underlying {@link EventStore} when the {@link UnitOfWork} * is committed. * * @param aggregateId the id of the aggregate we want to load * @return an {@link Optional} with the matching {@link FlexAggregate} instance * @throws AggregateNotFoundException in case a matching {@link FlexAggregate} doesn't exist in the {@link EventStore} */ default AGGREGATE_TYPE load(ID aggregateId) { return tryLoad(aggregateId).orElseThrow(() -> new AggregateNotFoundException(aggregateId, aggregateRootImplementationType(), aggregateType())); } /** * The type of {@link FlexAggregate} implementation this repository handles */ Class aggregateRootImplementationType(); /** * The type of {@link AggregateType} this repository is using to persist Events */ AggregateType aggregateType(); /** * Load an {@link FlexAggregate} instance with the specified aggregateId from the underlying {@link EventStore}
* The loaded aggregate instance will be associated with the {@link UnitOfWorkFactory#getRequiredUnitOfWork()} * and any changes to will be tracked and will be persisted to the underlying {@link EventStore} when the {@link UnitOfWork} * is committed. * * @param aggregateId the id of the aggregate we want to load * @param expectedLatestEventOrder the expected {@link PersistedEvent#eventOrder()} of the last event stored in relation to the given aggregate instance * @return an {@link Optional} with the matching {@link FlexAggregate} instance * @throws AggregateNotFoundException in case a matching {@link FlexAggregate} doesn't exist in the {@link EventStore} */ default AGGREGATE_TYPE load(ID aggregateId, long expectedLatestEventOrder) { return tryLoad(aggregateId, expectedLatestEventOrder).orElseThrow(() -> new AggregateNotFoundException(aggregateId, aggregateRootImplementationType(), aggregateType())); } /** * Associate a newly created and not yet persisted {@link FlexAggregate} instance with the {@link UnitOfWorkFactory#getRequiredUnitOfWork()}
* Any changes to will be tracked and will be persisted to the underlying {@link EventStore} when the {@link UnitOfWork} * is committed. * * @param eventsToPersist the events to persist to the underlying {@link EventStore} (a result of a Command method * invocation on an {@link FlexAggregate} instance */ void persist(EventsToPersist eventsToPersist); // TODO: Add loadReadOnly (that doesn't require a UnitOfWork), getAllAggregateId's, loadByIds, loadAll, query, etc. /** * The type of aggregate ID this repository uses */ Class aggregateIdType(); // ------------------------------------------------------------------------------------------------------------------------------------------------- class DefaultFlexAggregateRepository> implements FlexAggregateRepository { private static final Logger log = LoggerFactory.getLogger(FlexAggregateRepository.class); private final EventStore eventStore; private final Class aggregateRootImplementationType; private final Class aggregateIdType; private final EventStoreUnitOfWorkFactory unitOfWorkFactory; private final FlexAggregateRepositoryUnitOfWorkLifecycleCallback unitOfWorkCallback; private final AggregateType aggregateType; /** * Create an {@link FlexAggregateRepository} - the {@link EventStore} will be configured with the supplied eventStreamConfiguration.
* * @param eventStore the event store that can load and persist events related to the aggregateType * @param aggregateEventStreamConfiguration the configuration for the event stream that will contain all the events related to the aggregate type * @param unitOfWorkFactory The factory that provides {@link UnitOfWork}'s * @param aggregateIdType the concrete aggregate ID type * @param aggregateRootImplementationType the concrete aggregate type (MUST be a subtype of {@link FlexAggregate}) */ private DefaultFlexAggregateRepository(ConfigurableEventStore eventStore, CONFIG aggregateEventStreamConfiguration, EventStoreUnitOfWorkFactory unitOfWorkFactory, Class aggregateIdType, Class aggregateRootImplementationType) { this.eventStore = requireNonNull(eventStore, "You must supply an EventStore instance"); this.aggregateType = requireNonNull(aggregateEventStreamConfiguration, "You must supply an aggregateType").aggregateType; this.unitOfWorkFactory = requireNonNull(unitOfWorkFactory, "You must supply a UnitOfWorkFactory instance"); this.aggregateRootImplementationType = requireNonNull(aggregateRootImplementationType, "You must supply an aggregateImplementationType"); this.aggregateIdType = requireNonNull(aggregateIdType, "You must supply an aggregateIdType"); this.unitOfWorkCallback = new FlexAggregateRepositoryUnitOfWorkLifecycleCallback(); eventStore.addAggregateEventStreamConfiguration(aggregateEventStreamConfiguration); } /** * Create an {@link FlexAggregateRepository} - if missing, the {@link EventStore} will be configured with the * default {@link AggregateEventStreamConfiguration} based on the {@link AggregateEventStreamConfigurationFactory} that the {@link EventStore}'s {@link AggregateEventStreamPersistenceStrategy} * is configured with
* * @param eventStore the event store that can load and persist events related to the aggregateType * @param aggregateType the aggregate type being handled by this repository - the {@link EventStore} must contain a corresponding {@link AggregateEventStreamConfiguration} * @param unitOfWorkFactory The factory that provides {@link UnitOfWork}'s * @param aggregateIdType the concrete aggregate ID type * @param aggregateRootImplementationType the concrete aggregate type (MUST be a subtype of {@link FlexAggregate}) */ private DefaultFlexAggregateRepository(ConfigurableEventStore eventStore, AggregateType aggregateType, EventStoreUnitOfWorkFactory unitOfWorkFactory, Class aggregateIdType, Class aggregateRootImplementationType) { this.eventStore = requireNonNull(eventStore, "You must supply an EventStore instance"); this.aggregateType = requireNonNull(aggregateType, "You must supply an aggregateType"); this.unitOfWorkFactory = requireNonNull(unitOfWorkFactory, "You must supply a UnitOfWorkFactory instance"); this.aggregateRootImplementationType = requireNonNull(aggregateRootImplementationType, "You must supply an aggregateImplementationType"); this.aggregateIdType = requireNonNull(aggregateIdType, "You must supply an aggregateIdType"); this.unitOfWorkCallback = new FlexAggregateRepositoryUnitOfWorkLifecycleCallback(); if (eventStore.findAggregateEventStreamConfiguration(aggregateType).isEmpty()) { eventStore.addAggregateEventStreamConfiguration(aggregateType, aggregateIdType); } } /** * The {@link EventStore} that's being used for support */ protected EventStore eventStore() { return eventStore; } @Override public AggregateType aggregateType() { return aggregateType; } @Override public String toString() { return "FlexAggregateRepository{" + "aggregateType=" + aggregateType() + ", aggregateIdType=" + aggregateIdType() + ", aggregateImplementationType=" + aggregateRootImplementationType().getName() + '}'; } @Override public Optional tryLoad(ID aggregateId, Optional expectedLatestEventOrder) { log.trace("Trying to load {} with id '{}' and expectedLatestEventOrder {}", aggregateRootImplementationType.getName(), aggregateId, expectedLatestEventOrder); UnitOfWork unitOfWork = unitOfWorkFactory.getRequiredUnitOfWork(); var potentialPersistedEventStream = eventStore.fetchStream(aggregateType, aggregateId, LongRange.from(EventOrder.FIRST_EVENT_ORDER.longValue())); // TODO: Support for looking up a snapshot version of the aggregate, where we only need to load events not included in the snapshot if (potentialPersistedEventStream.isEmpty()) { log.trace("Didn't find a {} with id '{}'", aggregateRootImplementationType.getName(), aggregateId); return Optional.empty(); } else { var persistedEventsStream = potentialPersistedEventStream.get(); if (expectedLatestEventOrder.isPresent()) { PersistedEvent lastEventPersisted = persistedEventsStream.eventList().get(persistedEventsStream.eventList().size() - 1); if (lastEventPersisted.eventOrder().longValue() != expectedLatestEventOrder.get()) { log.trace("Found {} with id '{}' but expectedLatestEventOrder {} != actualLatestEventOrder {}", aggregateRootImplementationType.getName(), aggregateId, expectedLatestEventOrder.get(), lastEventPersisted.eventOrder()); throw new OptimisticAggregateLoadException(aggregateId, aggregateRootImplementationType, expectedLatestEventOrder.map(EventOrder::of).get(), lastEventPersisted.eventOrder()); } } log.debug("Found {} with id '{}' and expectedLatestEventOrder {}", aggregateRootImplementationType.getName(), aggregateId, expectedLatestEventOrder); AGGREGATE_TYPE aggregate = Reflector.reflectOn(aggregateRootImplementationType).newInstance(); return Optional.of(aggregate.rehydrate(persistedEventsStream)); } } @Override public Class aggregateRootImplementationType() { return aggregateRootImplementationType; } @Override public void persist(EventsToPersist eventsToPersist) { log.debug("Adding {} with id '{}' to the current UnitOfWork so it will be persisted at commit time", aggregateRootImplementationType.getName(), eventsToPersist.aggregateId); unitOfWorkFactory.getRequiredUnitOfWork() .registerLifecycleCallbackForResource(eventsToPersist, unitOfWorkCallback); } @Override public Class aggregateIdType() { return aggregateIdType; } /** * The {@link UnitOfWorkLifecycleCallback} that's responsible for persisting {@link EventsToPersist} that were a side effect of command methods invoked on * {@link FlexAggregate} instances during the {@link UnitOfWork} - see {@link FlexAggregateRepository#persist(EventsToPersist)} */ private class FlexAggregateRepositoryUnitOfWorkLifecycleCallback implements UnitOfWorkLifecycleCallback> { @Override public void beforeCommit(UnitOfWork unitOfWork, List> associatedResources) { log.trace("beforeCommit processing {} '{}' registered with the UnitOfWork being committed", associatedResources.size(), aggregateRootImplementationType.getName()); associatedResources.forEach(eventsToPersist -> { log.trace("beforeCommit processing '{}' with id '{}'", aggregateRootImplementationType.getName(), eventsToPersist.aggregateId); if (eventsToPersist.events.isEmpty()) { log.trace("No changes detected for '{}' with id '{}'", aggregateRootImplementationType.getName(), eventsToPersist.aggregateId); } else { if (log.isTraceEnabled()) { log.trace("Persisting {} event(s) related to '{}' with id '{}': {}", eventsToPersist.events.size(), aggregateRootImplementationType.getName(), eventsToPersist.aggregateId, eventsToPersist.events.stream().map(persistableEvent -> persistableEvent.getClass().getName()).reduce((s, s2) -> s + ", " + s2)); } else { log.debug("Persisting {} event(s) related to '{}' with id '{}'", eventsToPersist.events.size(), aggregateRootImplementationType.getName(), eventsToPersist.aggregateId); } eventStore.appendToStream(aggregateType, eventsToPersist.aggregateId, eventsToPersist.eventOrderOfLastRehydratedEvent, eventsToPersist.events); eventsToPersist.markEventsAsCommitted(); } }); } @Override public void afterCommit(UnitOfWork unitOfWork, List> associatedResources) { } @Override public void beforeRollback(UnitOfWork unitOfWork, java.util.List> associatedResources, Exception causeOfTheRollback) { } @Override public void afterRollback(UnitOfWork unitOfWork, java.util.List> associatedResources, Exception causeOfTheRollback) { } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy