I have a Aggregate (Operation) that contains a list of another linked Operation.
For the moment, I chose to create two events for update the Operations
What is the best practice for linking these operations?
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Aggregate
public class OperationAggregate {
@AggregateIdentifier
private UUID id;
private String title;
private List<UUID> linkedOperations;
@CommandHandler
public OperationAggregate(CreateOperationCommand command){
apply(new OperationCreatedEvent(
command.getId(),
command.getTitle(),
command.getLinkedOperations()
));
}
@EventSourcingHandler
public void on(OperationCreatedEvent event){
this.id = event.getId();
this.titre = event.getTitle();
this.linkedOperations = new ArrayList<>();
if (!event.getLinkedOperations().isEmpty()){
for(UUID linkOperationId: event.getLinkedOperations()){
apply(new OperationLinkedEvent(this.id, linkOperationId));
apply(new OperationLinkedEvent( linkOperationId,this.id));
}
}
}
@CommandHandler
public void handle(LinkOperationCommand command){
if(linkedOperations.contains(command.getOperationLinkId())){
throw new ObjectExistException("Operation to link");
}
apply(new OperationLinkedEvent(command.getId(), command.getOperationLinkId()));
apply(new OperationLinkedEvent( command.getOperationLinkId(),command.getId()));
}
@EventSourcingHandler
public void on(OperationLinkedEvent event){
this.linkedOperations.add(event.getOperationLinkId());
}
I am not entirely sure about what you intent to achieve.
Some questions that come to my mind.
Why doesn’t your OperationCreatedEvent handler add all linked operation at once using addAll on the List class?
Why do you broadcast two additional events from your OperationCreatedEvent handler? The same question for your LinkedOperationCommand handler?
I assume you want to sent an event to be handled somewhere else when one operation is linked. It might be beneficial to use dedicated events for such purpose making a distinction between events as in your aggregate to handle CQRS and additional (seperated) events (domain events if you like) to be handled elsewhere but not chancing the aggregate’s state.
As a final side remark, might the LinkedList class be of any use for your use case instead of the List class?
When i create my aggregate , I can create a link with others operations . Indeed , I can add directly the id to the list.
I broadcast two events because, I also have to do the link in the other direction. I create a specific event because after creation of a Operation, i can add/remove a link
Example:
In first time, i create an “Operation A” with a link “Operation B” and Operation C"
OperationCreatedEvent Operation A → add B and C on List linkedOperations
OperationLinkedEvent Operation B → add A on List linkedOperations
OperationLinkedEvent Operation C → add A on List linkedOperations
In second time, i add a link between “Operation A” and “Operation F”:
OperationLinkedEvent Operation A → add F on List linkedOperations
OperationLinkedEvent Operation F → add A on List linkedOperations
So for my understanding, based on the provided code.
In the first time when the CreateOperationCommand is sent, it’s id is A and contains two linked operations B en C. This results in an OperationCreatedEvent after which the linked operations collection of the aggregate is empty. Immediately (in the same event handler), two additional OperationLinkedEvents are fired from the OperationCreatedEvent handler. One as A,B and causes two events with parameters A,B and B,A. The second is related to operation C and causes two additional events with parameters A,C and C,A. This results in an aggregate state in which the collection contains [B, A, C, A] since only the second parameter of the event is added.
In the second time, a LinkOperationCommand is sent containing a linked operation F. Right? This results in two OperationLinkedEvents with parameters A,F and F,A updating the aggregate’s collection to [B, A, C, A, F, A].
How should this collection reflect the bidirectional link between the different members in the List property in your use case? Probably I am misunderstanding something vital in your question…
Just a suggestion. Could you implement the collection of linked operations as a LinkedList and define an InsertOperationCommand and OperationInsertedEvent that adds the specified operation at the desired location in the list using the add(int index, E element) method of the LinkedList class? But again, I don’t know your case enough to know if this is a viable alternative.
In my example, when i create an operation A with two linked operation B and C
*Operation A contains [B,C]
*Operation B contains [A]
*Operation C contains [C]
After, when i add a link between A and F
*Operation A contains [A,B,F]
*Operation F contains [F]
It’s a bidirectional relation between two Operation.
it’s the reverse process when i remove a relation
Thank you for taking the time to answer my question
[ps: I am apologise for my level of English… I should have worked at school ]
When i create a Operation (UUID = 0aca3614-7bcd-46fa-bf66-1da873ed8f82) and link wtih another operation (UUID = edbffe40-c5f8-41c7-9531-e6bf5c1c82d5), i see two events ( OperationLinkedEvent) to the same Operation
Looking at your code, this is exactly the behaviour you wrote. Your OperationCreatedEvent event sourcing handler method fires two events calling the OperationLinkedEvent handler twice in the same aggregate.
As long as you fire an event from an aggregate with the same @AggregateIdentifier, the same aggregate’s @EventSourcingHandlers are called. In other words, your code does not create two different aggregates linking bidirectionally towards each other by “firing” two events from within the same aggregate and swapping their id’s.
If you want to achieve this, you have to create two aggregates (calling the constructor command handler twice) with unique id’s. Afterwards command/events are responsible for linking both aggregates to each other using their id’s.
I assume the first parameter in OperationLinkedEvent’s constructor is associated with the aggregate id while the second is associated with the operation linked id. The OperationLinkedEvent event sourcing handler ignores this first event (id) argument.
I hope this is a bit clear(er) for you because it is a bit complicated to explain.
Thank you very much for the explanations ! It’s clear !
I associate the operation in my service
public class OperationCommandService {
@Autowired
private CommandGateway commandGateway;
public CompletableFuture<String> createOperation(OperationCreationDto dto){
UUID id = UUID.randomUUID();
CompletableFuture<String> operationId = commandGateway.send(new CreateOperationCommand(
id,
dto.getTitre(),
));
List<UUID> linkedOperations = new ArrayList<>();
if (!dto.getLinkedOperations().isEmpty()){
dto.getLinkedOperations().forEach(identifiant -> {
commandGateway.send(new LinkOperationCommand(id,ServiceUtils.formatUuid(identifiant)));
commandGateway.send(new LinkOperationCommand(ServiceUtils.formatUuid(identifiant),id));
});
}
return operationId;
}
public CompletableFuture<String> linkOperation(String operationId, LierOperationDto dto){
UUID operationid = ServiceUtils.formatUuid(operationId);
UUID linkedOperation = ServiceUtils.formatUuid(dto.getLinkedOperationId());
commandGateway.send(new LinkOperationCommand(operationid, linkedOperation));
return commandGateway.send(new LinkOperationCommand(linkedOperation, operationid));
}
...
When I create an operation without a link and after I link two operations, it’s work !
When I create an operation with one (or many) link, I have random this error:
o.a.c.gateway.DefaultCommandGateway : Command 'com.rte.prisk.core.operation.command.LinkOperationCommand' resulted in org.axonframework.commandhandling.CommandExecutionException(The aggregate was not found in the event store)
The Operation created haven’t Linked operations. The others operations have the link for the created Operation
I think it comes from the sequence of commands. The Operation is not created when i send the LinkOperationCommand.
How can I wait for the creation of the operation ? it’s the good way ?