dk.cloudcreate.essentials.components.eventsourced.aggregates.flex.FlexAggregate Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eventsourced-aggregates Show documentation
Show all versions of eventsourced-aggregates Show documentation
This library focuses on providing different flavors of eventsourced aggregrates
/*
* 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.aggregates.stateful.classic.Event;
import dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.EventStore;
import dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.eventstream.AggregateEventStream;
import dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.types.EventOrder;
import dk.cloudcreate.essentials.shared.reflection.invocation.*;
import java.util.List;
import java.util.stream.Stream;
import static dk.cloudcreate.essentials.shared.FailFast.requireNonNull;
/**
* Simple, easy and opinionated aggregate that allows you to apply events that don't have any requirements
* with regards to inheritance (i.e. you can use Records) in combination with an {@link FlexAggregateRepository}
* Events
* {@code
* public record OrderAdded(OrderId orderId, CustomerId orderingCustomerId, long orderNumber) {
* }
*
* public static class OrderAccepted {
* public final OrderId orderId;
*
* public OrderAccepted(OrderId orderId) {
* this.orderId = orderId;
* }
* }
* }
* New Aggregate instance
* The method that creates a new aggregate instance may be a static method that returns a {@link EventsToPersist}
* instance:
* {@code
* unitOfWorkFactory.usingUnitOfWork(unitOfWork -> {
* var eventsToPersist = Order.createNewOrder(orderId, CustomerId.random(), 123);
* repository.persist(eventsToPersist);
* });
* }
* Where the createNewOrder static methods looks like:
* {@code
* public static EventsToPersist createNewOrder(OrderId orderId,
* CustomerId orderingCustomerId,
* int orderNumber) {
* FailFast.requireNonNull(orderId, "You must provide an orderId");
* FailFast.requireNonNull(orderingCustomerId, "You must provide an orderingCustomerId");
* return initialAggregateEvents(orderId, new OrderAdded(orderId, orderingCustomerId, orderNumber));
* }
* }
*
* Aggregate command methods
* Each individual command method MUST also return a {@link EventsToPersist} instance
* that can will usually be use in in a call to {@link FlexAggregateRepository#persist(EventsToPersist)}
* {@code
* FlexAggregateRepository repository =
* FlexAggregateRepository.from(
* eventStores,
* standardSingleTenantConfiguration(
* AggregateType.of("Orders"),
* new JacksonJSONEventSerializer(createObjectMapper()),
* AggregateIdSerializer.serializerFor(OrderId.class),
* IdentifierColumnType.UUID,
* JSONColumnType.JSONB),
* unitOfWorkFactory,
* OrderId.class,
* Order.class
* );
*
* unitOfWorkFactory.usingUnitOfWork(unitOfWork -> {
* var order = repository.load(orderId);
* var eventsToPersist = order.accept();
* repository.persist(eventsToPersist);
* });
* }
*
* And where the accept() command method looks like this:
* {@code
* public EventsToPersist accept() {
* if (accepted) {
* return noEvents();
* }
* return events(new OrderAccepted(aggregateId()));
* }
* }
*
* Command method chaining
* Command methods can be chained like this:
* {@code
* unitOfWorkFactory.usingUnitOfWork(unitOfWork -> {
* var eventsToPersist = Order.createNewOrder(orderId, CustomerId.random(), 123);
* eventsToPersist = eventsToPersist.append(new Order().rehydrate(eventsToPersist).addProduct(productId, 10));
* repository.persist(eventsToPersist);
* });
* }
* Aggregate event handling methods
*
* When an aggregate is loaded from the {@link EventStore} using the {@link FlexAggregateRepository},
* e.g. using {@link FlexAggregateRepository#load(Object)} or {@link FlexAggregateRepository#tryLoad(Object)},
* the repository will return an {@link FlexAggregate} instance that has had all events returned from the {@link EventStore} applied to it.
*
* What happens internally is that the {@link FlexAggregateRepository#load(Object)} method will call {@link #rehydrate(AggregateEventStream)},
* which in-order will call {@link FlexAggregate#applyRehydratedEventToTheAggregate(Object)} for each
* event returned from the EventStore.
*
* You can either choose to implement the event matching using instanceof:
*
{@code
* @Override
* protected void applyHistoricEventToTheAggregate(Object event) {
* if (event instanceof OrderAdded e) {
* // You don't need to store all properties from an Event inside the Aggregate.
* // Only do this IF it actually is needed for business logic and in this cases none of them are needed
* // for further command processing
*
* // To support instantiation using e.g. Objenesis we initialize productAndQuantity here
* productAndQuantity = HashMap.empty();
* } else if (event instanceof ProductAddedToOrder e) {
* Option existingQuantity = productAndQuantity.get(e.productId);
* productAndQuantity = productAndQuantity.put(e.productId, e.quantity + existingQuantity.getOrElse(0));
* } else if (event instanceof ProductOrderQuantityAdjusted e) {
* productAndQuantity = productAndQuantity.put(e.productId, e.newQuantity);
* } else if (event instanceof ProductRemovedFromOrder e) {
* productAndQuantity = productAndQuantity.remove(e.productId);
* } else if (event instanceof OrderAccepted) {
* accepted = true;
* }
* }
* }
*
* or use {@link EventHandler} annotated (private) methods:
* {@code
* @EventHandler
* private void on(OrderAdded e) {
* productAndQuantity = HashMap.empty();
* }
*
* @EventHandler
* private void on(ProductAddedToOrder e) {
* Option existingQuantity = productAndQuantity.get(e.productId);
* productAndQuantity = productAndQuantity.put(e.productId, e.quantity + existingQuantity.getOrElse(0));
* }
*
* @EventHandler
* private void on(ProductOrderQuantityAdjusted e) {
* productAndQuantity = productAndQuantity.put(e.productId, e.newQuantity);
* }
*
* @EventHandler
* private void on(ProductRemovedFromOrder e) {
* productAndQuantity = productAndQuantity.remove(e.productId);
* }
*
* @EventHandler
* private void on(OrderAccepted e) {
* accepted = true;
* }
* }
*
* @param The id type for the aggregate id
* @param the type of the aggregate
* @see FlexAggregateRepository
*/
@SuppressWarnings("unchecked")
public abstract class FlexAggregate> implements Aggregate {
private transient PatternMatchingMethodInvoker
© 2015 - 2024 Weber Informatics LLC | Privacy Policy