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 classic
The modern interpretation doesn't specify any requirement on the design of the Events, they can be Java 17+ records or simple POJO's.
The modern
If you wish to keep the state projection and
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: TheAggregateRootworks best in combination with theStatefulAggregateRepositorythat's configured to use theStatefulAggregateInstanceFactory.reflectionBasedAggregateRootFactory(), because theAggregateRootneeds to be provided its aggregated id through theAggregateRoot(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:
public class OrderState extends AggregateState<OrderId, OrderEvent, Order> {
Map<ProductId, Integer> productAndQuantity;
boolean accepted;
@EventHandler
private void on(OrderEvent.OrderAdded e) {
productAndQuantity = new HashMap<>();
}
@EventHandler
private void on(OrderEvent.ProductAddedToOrder e) {
var existingQuantity = productAndQuantity.get(e.productId);
productAndQuantity.put(e.productId, e.quantity + (existingQuantity != null ? existingQuantity : 0));
}
@EventHandler
private void on(OrderEvent.ProductOrderQuantityAdjusted e) {
productAndQuantity.put(e.productId, e.newQuantity);
}
@EventHandler
private void on(OrderEvent.ProductRemovedFromOrder e) {
productAndQuantity.remove(e.productId);
}
@EventHandler
private void on(OrderEvent.OrderAccepted e) {
accepted = true;
}
}
-
Constructor Summary
ConstructorsModifierConstructorDescriptionprotectedUsed forAggregateSnapshotdeserializationAggregateRoot(ID aggregateId) Used for rehydration -
Method Summary
Modifier and TypeMethodDescriptionThe id of the aggregate (aka.protected voidapply(EVENT_TYPE event) Apply a new non persisted/uncommitted Event to this aggregate instance.protected voidapply(Function<EventOrder, EVENT_TYPE> eventSupplier) Apply a new non persisted/uncommitted Event to this aggregate instance.protected voidApply 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 withEventHandlerGet the eventOrder of the last event during aggregate hydration (using theAggregate.rehydrate(AggregateEventStream)method)Query any changes to the Aggregate, i.e.booleanHas the aggregate been initialized using previously recorded/persisted events (aka.protected voidInitialize the aggregate, e.g.protected final booleanIs the event being supplied toapplyEventToTheAggregateState(Object)a previously persisted event (i.e.voidResets thegetUncommittedChanges()- effectively marking them as having been persisted and committed to the underlyingEventStorerehydrate(AggregateEventStream<ID> persistedEvents) Effectively performs a leftFold over all the previously persisted events related to this aggregate instancerehydrate(Stream<EVENT_TYPE> persistedEvents) Effectively performs a leftFold over all the previous events related to this aggregate instanceprotected Class<?>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>>
STATE
-
Constructor Details
-
AggregateRoot
protected AggregateRoot()Used forAggregateSnapshotdeserialization -
AggregateRoot
Used for rehydration- Parameters:
aggregateId- the id of the aggregate being initialized during the rehydration flow
-
-
Method Details
-
aggregateId
Description copied from interface:AggregateThe id of the aggregate (aka. the stream-id)- Specified by:
aggregateIdin interfaceAggregate<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, STATE stateAGGREGATE_TYPE>> (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
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
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
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
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_PERSISTEDin case no events has ever been applied to the aggregate
-
rehydrate
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
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
-
apply
Apply a new non persisted/uncommitted Event to this aggregate instance.- Parameters:
event- the event to apply to the state of the aggregate
-
applyEventToTheAggregateState
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
protected final boolean isRehydrating()Is the event being supplied toapplyEventToTheAggregateState(Object)a previously persisted event (i.e. a historic event)
-