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 idAGGREGATE_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
Events
The method that creates a new aggregate instance may be a static method that returns a
Each individual command method MUST also return a
Command method chaining
Command methods can be chained like this:
FlexAggregateRepositoryEvents
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 instanceThe 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 methodsEach 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:
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionThe id of the aggregate (aka.protected voidApply the previously recorded/persisted event (aka historic 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 withEventHandlerfinal dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.types.EventOrderGet the eventOrder of the last event that was applied to theFlexAggregateUsing:rehydrate(AggregateEventStream)rehydrate(EventsToPersist)rehydrate(Object, Stream)rehydrate(Object, List)protected EventsToPersist<ID,Object> Wrap the events that should be persisted as a side effect of a command methodbooleanHas the aggregate been initialized using previously recorded/persisted events?protected voidstatic <ID> EventsToPersist<ID,Object> newAggregateEvents(ID aggregateId, Object... eventsToPersist) Wrap the events that should be persisted for this new aggregateprotected EventsToPersist<ID,Object> noEvents()Creates an emptyEventsToPersistwhich is handy for a commanded method didn't have a side-effect (e.g.rehydrate(EventsToPersist<ID, Object> eventsToPersist) Initialize an aggregate instance fromEventsToPersist
Effectively performs a leftFold over all the previous events related to this aggregate instancerehydrate(dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.eventstream.AggregateEventStream<ID> persistedEvents) Initialize an aggregate instance from historic events.
Effectively performs a leftFold over all the previous persisted events related to this aggregate instanceEffectively performs a leftFold over all the previous events related to this aggregate instance
-
Constructor Details
-
FlexAggregate
public FlexAggregate()
-
-
Method Details
-
initialize
protected void initialize() -
rehydrate
public AGGREGATE_TYPE rehydrate(dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.eventstream.AggregateEventStream<ID> persistedEvents) Initialize an aggregate instance from historic events.
Effectively performs a leftFold over all the previous persisted events related to this aggregate instance- Specified by:
rehydratein interfaceAggregate<ID,AGGREGATE_TYPE extends FlexAggregate<ID, AGGREGATE_TYPE>> - Parameters:
persistedEvents- the previous recorded/persisted events related to this aggregate instance, aka. the aggregates history- Returns:
- the aggregate instance
-
rehydrate
Initialize an aggregate instance fromEventsToPersist
Effectively performs a leftFold over all the previous events related to this aggregate instance- Parameters:
eventsToPersist-- Returns:
- the aggregate instance
-
rehydrate
-
rehydrate
Effectively performs a leftFold over all the previous events related to this aggregate instance- Parameters:
persistedEvents- the previous recorded/persisted events related to this aggregate instance, aka. the aggregates history- Returns:
- the aggregate instance
-
newAggregateEvents
public static <ID> EventsToPersist<ID,Object> newAggregateEvents(ID aggregateId, Object... eventsToPersist) Wrap the events that should be persisted for this new aggregate- Type Parameters:
ID- the aggregate id type- Parameters:
aggregateId- the aggregate id this relates toeventsToPersist- the events to persist, which will be the result/side-effect of a command method invocation in anFlexAggregate). May be empty if the command method invocation didn't result in any events (e.g. due to idempotency checks)
-
events
Wrap the events that should be persisted as a side effect of a command method- Parameters:
eventsToPersist- the events to persist, which will be the result/side-effect of a command method invocation in anFlexAggregate). May be empty if the command method invocation didn't result in any events (e.g. due to idempotency checks)- See Also:
-
noEvents
Creates an emptyEventsToPersistwhich is handy for a commanded method didn't have a side-effect (e.g. due to idempotent handling)- See Also:
-
applyRehydratedEventToTheAggregate
Apply the previously recorded/persisted event (aka historic 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
-
aggregateId
Description copied from interface:AggregateThe id of the aggregate (aka. the stream-id)- Specified by:
aggregateIdin interfaceAggregate<ID,AGGREGATE_TYPE extends FlexAggregate<ID, AGGREGATE_TYPE>>
-
hasBeenRehydrated
public boolean hasBeenRehydrated()Has the aggregate been initialized using previously recorded/persisted events?- Specified by:
hasBeenRehydratedin interfaceAggregate<ID,AGGREGATE_TYPE extends FlexAggregate<ID, AGGREGATE_TYPE>> - See Also:
-
eventOrderOfLastRehydratedEvent
public final dk.cloudcreate.essentials.components.eventsourced.eventstore.postgresql.types.EventOrder eventOrderOfLastRehydratedEvent()Get the eventOrder of the last event that was applied to theFlexAggregateUsing:- Specified by:
eventOrderOfLastRehydratedEventin interfaceAggregate<ID,AGGREGATE_TYPE extends FlexAggregate<ID, AGGREGATE_TYPE>> - Returns:
- the event order of the last applied
EventorEventOrder.NO_EVENTS_PREVIOUSLY_PERSISTEDin case no events has ever been applied to the aggregate
-