Best practice in Aggregate to link other Object?

Hi,

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());
    }

Thanks

I am not entirely sure about what you intent to achieve.

Some questions that come to my mind.

  1. Why doesn’t your OperationCreatedEvent handler add all linked operation at once using addAll on the List class?
  2. 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?

Hello,
Thanks for the answer

The link is bidirectional

  1. When i create my aggregate , I can create a link with others operations . Indeed , I can add directly the id to the list.
  2. 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

I hope my use case is explicit

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 :wink: ]

Hi,
My function is not working :frowning:

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

{
    "id": "0aca3614-7bcd-46fa-bf66-1da873ed8f82",
    "operationLinkId": "edbffe40-c5f8-41c7-9531-e6bf5c1c82d5"
  },
  {
    "id": "edbffe40-c5f8-41c7-9531-e6bf5c1c82d5",
    "operationLinkId": "0aca3614-7bcd-46fa-bf66-1da873ed8f82"
  }

and in the other operation (UUID = edbffe40-c5f8-41c7-9531-e6bf5c1c82d5), i don’t see an event (OperationLinkedEvent)

Can you help me please

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. :wink:

1 Like

Hello,

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 ?

Hello,
I use “thenApply” on my service ant i’ts work perfectly ! :slight_smile:

    public CompletableFuture<String> createOperation(OperationCreationDto dto){
        UUID id = UUID.randomUUID();
        List<UUID> linkedOperations = new ArrayList<>();
        return commandGateway.send(new CreateOperationCommand(id, dto.getTitre(), dto.getOuvrages(), dto.getChefEus(), dto.getDonneurOrdres()))
       .thenApply( x ->
               {
                   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 id.toString();
               }
        );

    }

Thank a lot for your help !!!

2 Likes