Double M
Double M

Reputation: 1503

How are aggregates instantiated to test other aggregates with?

Suppose I have an aggregate that, for some operation, requires the existence of another aggregate. Let's assume I have a car and a garage. There might be a command called ParkInGarage that looks like this:

public class ParkInGarage {

    @TargetAggregateIdentifier
    public final UUID carId;

    public final Garage garage;

    //... constructor omitted
}

I've read that to validate the existence of an aggregate, it is good practice to use the loaded aggregate in commands since that already implies its existence (as opposed to passing a garageId).

Now when unit-testing the Car using Axon's fixtures, I can not simply instantiate my Garage by saying new Garage(buildGarageCmd). It will say:

java.lang.IllegalStateException: Cannot request current Scope if none is active

Because no infrastructure was set up. How would I test such a case, or should I design the aggregate differently?

Abstracted, real-world example

The aggregate root I am working with may have a reference to itself to form a tree-structure of said aggregate root. Let's call it Node.

@Aggregate
public class Node {
    private Node parentNode;
}

Upon creation, I can pass an Optional<Node> as parent, or set the parent at a later time using a separate command. Whether the parent should be defined as instance or by ID is part of the question.

public class AttachNodeCmd {
    @TargetAggregateIdentifier
    public final UUID nodeId;
    public final Optional<Node> parentNode;
}

In the command handler, I need to check if attaching the node to given parent would introduce a cycle (the structure is supposed to be a tree, not a common graph).

@CommandHandler
public Node(AttachNodeCmd command) {
    if (command.parentNode.isPresent()) {
        Node currentNode = command.parentNode.get();
        while (currentNode != null) {
            if (currentNode.equals(this)) throw new RecursionException();
            currentNode = currentNode.parentNode.orElse(null);
        }
    }

    //Accept the command by applying() an Event
}

At some point, the parent needs to be instantiated to perform those checks. This could either be done by supplying the aggregate instance in the command (discouraged), or by supplying a Repository<Node> and the nodeId to the command handler, which is the aggregate itself and also discouraged. Currently I don't see a right way to do this and further down the road a way to test it.

Upvotes: 1

Views: 682

Answers (2)

Steven
Steven

Reputation: 7275

I think @plalx is putting you on the right track. Commands are part of your API/Message Contract and exposing the Aggregate in there isn't that great an idea. Additionally I'd like to note that the AggregateFixtures in Axon are there to test a single Aggregate, not the coordination of operations between Aggregates.

Coordination between aggregates/bounded contexts is typically where you see sagas coming in to play. Now to be honest, I am a bit in doubt whether this use case justifies a Saga, but I could imagine that if the ParkCarInGarageCommand fails because the Garage Aggregate is full (for example), that you need to instruct the Car Aggregate through another command telling it it's a no-go. The Saga set up in Axon might help you with this as you can easily catch (1) the exception from handling the command or (2) handle the event notifying the operation wasn't successful.

Upvotes: 1

plalx
plalx

Reputation: 43718

I wouldn't put AR instances in commands. Command schemas should be stable and easy to serialize/reserialize as they are message contracts.

What you could do instead is resolving the dependency in the command handler.

//ParkInGarage command handler
Garage garage = garageRepository.garageOfId(command.garageId);
Car car = carRepository.carOfId(command.carId);

car.parkIn(garage);

I don't know Axon Framework at all, but that should be relatively easy to test now.

Upvotes: 2

Related Questions