zeynab
(Zeynab)
February 28, 2023, 10:42am
#1
I have a tow aggregates:
A=aggregates root
B= aggregates root
A and B have a Composition relationship
how Axon do this type of relationship?
1
In DDD
we model the domain using several aggregates (root + entities). One such aggregate or entity can hold a reference to another aggregate root through its id
.
In axon
, I see the concept of aggregates and member entities, but I do not see the notion of references to other aggregates.
Gerard
(Gerard Klijs)
February 28, 2023, 11:14am
#2
Hi Zeynab,
To coordinate between aggregates, you can use sagas . We sometimes call the same component process manager, which might be less confusing.
In short, a saga can hold references to multiple aggregates. A specific event processor backs it, which will check for each event if there is a saga that should handle it or whether a new saga should be created.
1 Like
zeynab
(Zeynab)
February 28, 2023, 11:23am
#3
Thank you, I will do it.
Can you give me an example? please
zeynab
(Zeynab)
March 1, 2023, 5:42am
#4
Please give me one example for this subject
Gerard
(Gerard Klijs)
March 1, 2023, 4:46pm
#5
Sure, this is from an example project,
package com.example.axon.saga;
import com.example.axon.api.MarkValidationFailedCommand;
import com.example.axon.api.MarkValidationSucceededCommand;
import com.example.axon.api.ProductCreatedEvent;
import com.example.axon.api.StartValidationCommand;
import com.example.axon.api.ValidateDelayedCommand;
import com.example.axon.api.ValidateDelayedEvent;
import com.example.axon.api.ValidateTrueCommand;
import com.example.axon.api.ValidateTrueEvent;
import com.example.axon.api.ValidationEnded;
import com.example.axon.api.ValidationStarted;
import lombok.extern.slf4j.Slf4j;
import org.axonframework.commandhandling.gateway.CommandGateway;
import org.axonframework.eventhandling.scheduling.EventScheduler;
import org.axonframework.eventhandling.scheduling.ScheduleToken;
import org.axonframework.modelling.saga.EndSaga;
import org.axonframework.modelling.saga.SagaEventHandler;
import org.axonframework.modelling.saga.SagaLifecycle;
import org.axonframework.modelling.saga.StartSaga;
import org.axonframework.spring.stereotype.Saga;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Transient;
import java.time.Duration;
import java.util.UUID;
import static java.util.Objects.isNull;
@Saga
@Slf4j
public class ValidatingSaga {
private String productId;
private ScheduleToken scheduleId;
private boolean alwaysTrueValidated = false;
private boolean delayedValidated = false;
@Transient
CommandGateway commandGateway;
@Transient
EventScheduler eventScheduler;
@Autowired
void setCommandGateway(CommandGateway commandGateway) {
this.commandGateway = commandGateway;
}
@Autowired
void setDeadlineManager(EventScheduler eventScheduler) {
this.eventScheduler = eventScheduler;
}
@StartSaga
@SagaEventHandler(associationProperty = "productId")
public void handle(ProductCreatedEvent event) {
this.productId = event.getProductId();
commandGateway.send(StartValidationCommand.builder().productId(event.getProductId()).build());
}
@SagaEventHandler(associationProperty = "productId")
public void handle(ValidationStarted event) {
String validateTrueId = UUID.randomUUID().toString();
String validateDelayedId = UUID.randomUUID().toString();
SagaLifecycle.associateWith("validateTrueId", validateTrueId);
SagaLifecycle.associateWith("validateDelayedId", validateDelayedId);
scheduleId = eventScheduler.schedule(
Duration.ofSeconds(5L),
ValidateDelayedEvent.builder()
.validateDelayedId(validateDelayedId)
.success(false)
.fromScheduler(true)
.build());
commandGateway.send(ValidateTrueCommand.builder().validateTrueId(validateTrueId).build());
commandGateway.send(ValidateDelayedCommand.builder().validateDelayedId(validateDelayedId).build());
}
@SagaEventHandler(associationProperty = "validateTrueId")
public void handle(ValidateTrueEvent event) {
if (event.isSuccess()) {
alwaysTrueValidated = true;
bothCompleteCheck();
} else {
markFailed();
}
}
@SagaEventHandler(associationProperty = "validateDelayedId")
public void handle(ValidateDelayedEvent event) {
log.info("validating delayed event: {} while validated: {}", event, delayedValidated);
if (event.isFromScheduler()){
scheduleId = null;
}
if (event.isSuccess()) {
delayedValidated = true;
bothCompleteCheck();
} else {
markFailed();
}
}
private void bothCompleteCheck() {
if (alwaysTrueValidated && delayedValidated) {
commandGateway.send(MarkValidationSucceededCommand.builder().productId(productId).build());
}
}
private void markFailed() {
commandGateway.send(MarkValidationFailedCommand.builder().productId(productId).build());
}
@SagaEventHandler(associationProperty = "productId")
@EndSaga
public void handle(ValidationEnded event) {
if (!isNull(scheduleId)) {
eventScheduler.cancelSchedule(scheduleId);
scheduleId = null;
log.info("Set scheduleId to null.");
} else {
log.info("No deadline to cancel.");
}
log.info("Saga with product id: [{}] was ended.", productId);
}
public String getProductId() {
return productId;
}
public ScheduleToken getScheduleId() {
return scheduleId;
}
public boolean isAlwaysTrueValidated() {
return alwaysTrueValidated;
}
public boolean isDelayedValidated() {
return delayedValidated;
}
}
uberSpotz
(uberSpotz)
March 17, 2023, 5:12pm
#6
Great example. But could he have meant something like a one-to-one, one-to-many, many-to-many relationship type of thing by “Composition” ?
Like:
// BossAggregate.java
@Aggregate
public class Boss {
@AggregateIdentifier
private UUID bossId;
String name;
private List<Employee> employees = new ArrayList<Employee>;
@CommandHandler
public void handle(ListEmployeesCommand command) {
AggregateLifecycle.apply(new ListEmployeesEvent(bossId, getEmployees()));
}
@EventHandler
public void on(GetEmployeesEvent event) {
return getEmployees();
}
// getter, setter
public ArrayList<Employee> getEmployees() {
return this.employees
}
}
// EmployeeAggregate.java
@Aggregate
public class Employee {
@AggregateIdentifier
private UUID employeeId;
private UUID bossId;
// ...
}