Class AggregateRoot<ID,EVENT_TYPE,AGGREGATE_TYPE extends AggregateRoot<ID,EVENT_TYPE,AGGREGATE_TYPE>>
- java.lang.Object
-
- dk.cloudcreate.essentials.components.eventsourced.aggregates.stateful.modern.AggregateRoot<ID,EVENT_TYPE,AGGREGATE_TYPE>
-
- Type Parameters:
ID- the type of idEVENT_TYPE- the type of eventAGGREGATE_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 classicAggregateRootdesign, where theEvent'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
AggregateRootworks best in combination with theStatefulAggregateRepositorythat's configured to use theStatefulAggregateInstanceFactory.reflectionBasedAggregateRootFactory(), because theAggregateRootneeds to be provided its aggregated id through theAggregateRoot(Object)constructor!
The modernAggregateRootsupports keeping the state projection andEventHandlerannotated methods within theAggregateRootinstance or within anAggregateStateinstance.
If you wish to keep the state projection andEventHandlerannotated methods within anAggregateStateinstance, then you only need to implement theWithStateinterface:
And state class: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)); } }{@code public class OrderState extends AggregateState{ Map productAndQuantity; boolean accepted;
-
-
Constructor Summary
Constructors Modifier Constructor Description protectedAggregateRoot()Used forAggregateSnapshotdeserializationAggregateRoot(ID aggregateId)Used for rehydration
-
Method Summary
All Methods Instance Methods Concrete Methods Modifier and Type Method Description IDaggregateId()The id of the aggregate (aka.protected voidapply(EVENT_TYPE event)Apply a new non persisted/uncommitted Event to this aggregate instance.protected voidapply(Function<dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.types.EventOrder,EVENT_TYPE> eventSupplier)Apply a new non persisted/uncommitted Event to this aggregate instance.protected voidapplyEventToTheAggregateState(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 withEventHandlerdk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.types.EventOrdereventOrderOfLastAppliedEvent()dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.types.EventOrdereventOrderOfLastRehydratedEvent()Get the eventOrder of the last event during aggregate hydration (using theAggregate.rehydrate(AggregateEventStream)method)EventsToPersist<ID,EVENT_TYPE>getUncommittedChanges()Query any changes to the Aggregate, i.e.booleanhasBeenRehydrated()Has the aggregate been initialized using previously recorded/persisted events (aka.protected voidinitialize()Initialize the aggregate, e.g.protected booleanisRehydrating()Is the event being supplied toapplyEventToTheAggregateState(Object)a previously persisted event (i.e.voidmarkChangesAsCommitted()Resets thegetUncommittedChanges()- effectively marking them as having been persisted and committed to the underlyingEventStoreAGGREGATE_TYPErehydrate(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 instanceAGGREGATE_TYPErehydrate(Stream<EVENT_TYPE> persistedEvents)Effectively performs a leftFold over all the previous events related to this aggregate instanceprotected Class<?>resolveStateImplementationClass()Override this method to provide a non reflection based look up of the Type Argument provided to theAggregateRootclassprotected <STATE extends AggregateState<ID,EVENT_TYPE,AGGREGATE_TYPE>>
STATEstate()If the aggregate implementsWithStatethen you can call this method to access the state object
This method requires that the return type information is available at compile time:protected <STATE extends AggregateState<ID,EVENT_TYPE,AGGREGATE_TYPE>>
STATEstate(Class<STATE> stateClass)
-
-
-
Constructor Detail
-
AggregateRoot
protected AggregateRoot()
Used forAggregateSnapshotdeserialization
-
AggregateRoot
public AggregateRoot(ID aggregateId)
Used for rehydration- Parameters:
aggregateId- the id of the aggregate being initialized during the rehydration flow
-
-
Method Detail
-
aggregateId
public ID aggregateId()
Description copied from interface:AggregateThe id of the aggregate (aka. the stream-id)- Specified by:
aggregateIdin interfaceAggregate<ID,EVENT_TYPE>
-
getUncommittedChanges
public EventsToPersist<ID,EVENT_TYPE> getUncommittedChanges()
Description copied from interface:StatefulAggregateQuery any changes to the Aggregate, i.e. Events applied as the result of calling command methods on the aggregate instance,- Specified by:
getUncommittedChangesin interfaceStatefulAggregate<ID,EVENT_TYPE,AGGREGATE_TYPE extends AggregateRoot<ID,EVENT_TYPE,AGGREGATE_TYPE>>- Returns:
- the changes to the aggregate
-
markChangesAsCommitted
public void markChangesAsCommitted()
Resets thegetUncommittedChanges()- effectively marking them as having been persisted and committed to the underlyingEventStore- Specified by:
markChangesAsCommittedin interfaceStatefulAggregate<ID,EVENT_TYPE,AGGREGATE_TYPE extends AggregateRoot<ID,EVENT_TYPE,AGGREGATE_TYPE>>
-
hasBeenRehydrated
public boolean hasBeenRehydrated()
Description copied from interface:AggregateHas the aggregate been initialized using previously recorded/persisted events (aka. historic events) using theAggregate.rehydrate(AggregateEventStream)method- Specified by:
hasBeenRehydratedin interfaceAggregate<ID,EVENT_TYPE>
-
state
protected <STATE extends AggregateState<ID,EVENT_TYPE,AGGREGATE_TYPE>> STATE state(Class<STATE> stateClass)
Variant of thestate()where the concrete State type is specified in the argument
If the aggregate implementsWithStatethen 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 implementWithState
-
state
protected <STATE extends AggregateState<ID,EVENT_TYPE,AGGREGATE_TYPE>> STATE state()
If the aggregate implementsWithStatethen 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 usestate(Class):public void accept() { if (state(OrderState.class).accepted) { return; } apply(eventOrder -> new OrderEvent.OrderAccepted(aggregateId(), eventOrder)); }
Alternatively you can override thestate()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 implementWithState
-
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 theAggregateRootclass- Returns:
- the
AggregateStateimplementation to use for thestateinstance
-
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:
rehydratein interfaceAggregate<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:AggregateGet the eventOrder of the last event during aggregate hydration (using theAggregate.rehydrate(AggregateEventStream)method)- Specified by:
eventOrderOfLastRehydratedEventin interfaceAggregate<ID,EVENT_TYPE>- Returns:
- the event order of the last applied
EventorEventOrder.NO_EVENTS_PREVIOUSLY_PERSISTEDin 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 theEventOrderfor the event being applied, then you can just call theapply(Object)function instead.- Parameters:
eventSupplier- function that as argument/input receives theEventOrderfor the next event to be applied (in case you want to store theEventOrderwith the event being produced by theeventSupplier
The result of the event supplier will be forwarded to theapply(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 withEventHandler- Parameters:
event- the event to apply to the aggregate- See Also:
isRehydrating()
-
isRehydrating
protected final boolean isRehydrating()
Is the event being supplied toapplyEventToTheAggregateState(Object)a previously persisted event (i.e. a historic event)
-
-