Reputation: 1503
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?
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
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
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