Class AggregateRoot<ID,​EVENT_TYPE,​AGGREGATE_TYPE extends AggregateRoot<ID,​EVENT_TYPE,​AGGREGATE_TYPE>>

  • Type Parameters:
    ID - the type of id
    EVENT_TYPE - the type of event
    AGGREGATE_TYPE - the aggregate type
    All Implemented Interfaces:
    Aggregate<ID,​AGGREGATE_TYPE>, StatefulAggregate<ID,​EVENT_TYPE,​AGGREGATE_TYPE>

    public abstract class AggregateRoot<ID,​EVENT_TYPE,​AGGREGATE_TYPE extends AggregateRoot<ID,​EVENT_TYPE,​AGGREGATE_TYPE>>
    extends Object
    implements StatefulAggregate<ID,​EVENT_TYPE,​AGGREGATE_TYPE>
    A modern opinionated interpretation of the classic AggregateRoot design, where the Event's are mutable.
    The modern interpretation doesn't specify any requirement on the design of the Events, they can be Java 17+ records or simple POJO's.

    Note: The AggregateRoot works best in combination with the StatefulAggregateRepository that's configured to use the StatefulAggregateInstanceFactory.reflectionBasedAggregateRootFactory(), because the AggregateRoot needs to be provided its aggregated id through the AggregateRoot(Object) constructor!

    The modern AggregateRoot supports keeping the state projection and EventHandler annotated methods within the AggregateRoot instance or within an AggregateState instance.
    If you wish to keep the state projection and EventHandler annotated methods within an AggregateState instance, then you only need to implement the WithState interface:
    
     public class Order extends AggregateRoot<OrderId, OrderEvent, Order> implements WithState<OrderId, OrderEvent, Order, OrderState> {
     public Order(OrderId orderId,
                      CustomerId orderingCustomerId,
                      int orderNumber) {
             super(orderId);
             requireNonNull(orderingCustomerId, "You must provide an orderingCustomerId");
    
             apply(new OrderEvent.OrderAdded(orderId,
                                             orderingCustomerId,
                                             orderNumber));
         }
    
         public void addProduct(ProductId productId, int quantity) {
             requireNonNull(productId, "You must provide a productId");
             if (state(OrderState.class).accepted) {
                 throw new IllegalStateException("Order is already accepted");
             }
             apply(new OrderEvent.ProductAddedToOrder(aggregateId(),
                                                      productId,
                                                      quantity));
         }
    
         public void accept() {
             if (state(OrderState.class).accepted) {
                 return;
             }
             apply(eventOrder -> new OrderEvent.OrderAccepted(aggregateId(),
                                                              eventOrder));
         }
     }
     
    And state class:
    {@code
     public class OrderState extends AggregateState {
         Map productAndQuantity;
         boolean                 accepted;
    • Constructor Detail

      • AggregateRoot

        protected AggregateRoot()
        Used for AggregateSnapshot deserialization
      • AggregateRoot

        public AggregateRoot​(ID aggregateId)
        Used for rehydration
        Parameters:
        aggregateId - the id of the aggregate being initialized during the rehydration flow
    • Method Detail

      • state

        protected <STATE extends AggregateState<ID,​EVENT_TYPE,​AGGREGATE_TYPE>> STATE state​(Class<STATE> stateClass)
        Variant of the state() where the concrete State type is specified in the argument
        If the aggregate implements WithState then you can call this method to access the state object
        Type Parameters:
        STATE - the type of state object
        Returns:
        the state object
        Throws:
        AggregateException - if the aggregate doesn't implement WithState
      • state

        protected <STATE extends AggregateState<ID,​EVENT_TYPE,​AGGREGATE_TYPE>> STATE state()
        If the aggregate implements WithState then you can call this method to access the state object
        This method requires that the return type information is available at compile time:
        OrderState state = state();
        If this isn't the case, then you can use state(Class):
        
             public void accept() {
                 if (state(OrderState.class).accepted) {
                     return;
                 }
                 apply(eventOrder -> new OrderEvent.OrderAccepted(aggregateId(),
                                                                  eventOrder));
             }

        Alternatively you can override the state() within your concrete Aggregate class and use Java's feature of overriding methods being allowed to define a covariant return type:
        
         public class Order extends AggregateRoot<OrderId, OrderEvent, Order> implements WithState<OrderId, OrderEvent, Order, OrderState> {
             protected OrderState state() {
                 return super.state();
             }
         }
         
        Type Parameters:
        STATE - the type of state object
        Returns:
        the state object
        Throws:
        AggregateException - if the aggregate doesn't implement WithState
      • initialize

        protected void initialize()
        Initialize the aggregate, e.g. setting up state objects, PatternMatchingMethodInvoker, etc.
      • resolveStateImplementationClass

        protected Class<?> resolveStateImplementationClass()
        Override this method to provide a non reflection based look up of the Type Argument provided to the AggregateRoot class
        Returns:
        the AggregateState implementation to use for the state instance
      • rehydrate

        public AGGREGATE_TYPE rehydrate​(dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.eventstream.AggregateEventStream<ID> persistedEvents)
        Effectively performs a leftFold over all the previously persisted events related to this aggregate instance
        Specified by:
        rehydrate in interface Aggregate<ID,​EVENT_TYPE>
        Parameters:
        persistedEvents - the previous persisted events related to this aggregate instance, aka. the aggregates history
        Returns:
        the same aggregate instance (self)
      • eventOrderOfLastRehydratedEvent

        public dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.types.EventOrder eventOrderOfLastRehydratedEvent()
        Description copied from interface: Aggregate
        Get the eventOrder of the last event during aggregate hydration (using the Aggregate.rehydrate(AggregateEventStream) method)
        Specified by:
        eventOrderOfLastRehydratedEvent in interface Aggregate<ID,​EVENT_TYPE>
        Returns:
        the event order of the last applied Event or EventOrder.NO_EVENTS_PREVIOUSLY_PERSISTED in case no events has ever been applied to the aggregate
      • rehydrate

        public AGGREGATE_TYPE rehydrate​(Stream<EVENT_TYPE> persistedEvents)
        Effectively performs a leftFold over all the previous events related to this aggregate instance
        Parameters:
        persistedEvents - the previous events related to this aggregate instance, aka. the aggregates history
        Returns:
        the same aggregate instance (self)
      • apply

        protected void apply​(Function<dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.types.EventOrder,​EVENT_TYPE> eventSupplier)
        Apply a new non persisted/uncommitted Event to this aggregate instance. If you don't need to store the EventOrder for the event being applied, then you can just call the apply(Object) function instead.
        Parameters:
        eventSupplier - function that as argument/input receives the EventOrder for the next event to be applied (in case you want to store the EventOrder with the event being produced by the eventSupplier
        The result of the event supplier will be forwarded to the apply(Object) method.
      • eventOrderOfLastAppliedEvent

        public dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.types.EventOrder eventOrderOfLastAppliedEvent()
      • apply

        protected void apply​(EVENT_TYPE event)
        Apply a new non persisted/uncommitted Event to this aggregate instance.
        Parameters:
        event - the event to apply to the state of the aggregate
      • applyEventToTheAggregateState

        protected void applyEventToTheAggregateState​(Object event)
        Apply the event to the aggregate instance to reflect the event as a state change to the aggregate
        The default implementation will automatically call any (private) methods annotated with EventHandler
        Parameters:
        event - the event to apply to the aggregate
        See Also:
        isRehydrating()