Class FlexAggregate<ID,AGGREGATE_TYPE extends FlexAggregate<ID,AGGREGATE_TYPE>>

java.lang.Object
dk.cloudcreate.essentials.components.eventsourced.aggregates.flex.FlexAggregate<ID,AGGREGATE_TYPE>
Type Parameters:
ID - The id type for the aggregate id
AGGREGATE_TYPE - the type of the aggregate
All Implemented Interfaces:
Aggregate<ID,AGGREGATE_TYPE>

public abstract class FlexAggregate<ID,AGGREGATE_TYPE extends FlexAggregate<ID,AGGREGATE_TYPE>> extends Object implements Aggregate<ID,AGGREGATE_TYPE>
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 FlexAggregateRepository
Events

     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 EventsToPersist instance:

 unitOfWorkFactory.usingUnitOfWork(unitOfWork -> {
      var eventsToPersist = Order.createNewOrder(orderId, CustomerId.random(), 123);
      repository.persist(eventsToPersist);
  });
 
Where the createNewOrder static methods looks like:

     public static EventsToPersist<OrderId> 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 EventsToPersist instance that can will usually be use in in a call to FlexAggregateRepository.persist(EventsToPersist)

 FlexAggregateRepository<OrderId, Order> repository =
                   FlexAggregateRepository.from(
                        eventStores,
                        standardSingleTenantConfigurationUsingJackson(
                             AggregateType.of("Orders"),
                             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:

     public EventsToPersist<OrderId> accept() {
         if (accepted) {
             return noEvents();
         }
         return events(new OrderAccepted(aggregateId()));
     }
 

Command method chaining
Command methods can be chained like this:

         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 EventStore using the FlexAggregateRepository, e.g. using FlexAggregateRepository.load(Object) or FlexAggregateRepository.tryLoad(Object), the repository will return an FlexAggregate instance that has had all events returned from the EventStore applied to it.
What happens internally is that the FlexAggregateRepository.load(Object) method will call rehydrate(AggregateEventStream), which in-order will call applyRehydratedEventToTheAggregate(Object) for each event returned from the EventStore.
You can either choose to implement the event matching using instanceof:


     @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<Integer> 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 EventHandler annotated (private) methods:

     @EventHandler
     private void on(OrderAdded e) {
         productAndQuantity = HashMap.empty();
     }

     @EventHandler
     private void on(ProductAddedToOrder e) {
         Option<Integer> 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;
     }
 
See Also: