Please help me understand CommandGateway method responses

Hi,

I’m new to axon. I’m building a spring boot application using the framework and I’m having a hard time understanding the responses to the send* methods of CommandGateway. In the simplest case where I execute a command to create a new aggregate I get the aggregate’s id as a response. However, I can’t find any documentation that helps me understand how or why this is the response.

The application I’m building is analogous to an e-commerce shopping cart where you have a Cart with many LineItem instances. As for the CQRS parts, everything is working well. I send commands via the gateway and the aggregates are located and the commands are processed and all the events emitted are delivered to the query side event listeners… The only problem I’m having so far is trying to figure out the the gateway’s send method will return. Like I said above, the simple case is fine. Where this starts to break down for me is when I introduce I command to add multiple LineItems to my Cart (I’m not really working with a Cart, but it’s analogous). The command succeeds and all the views get updated correctly, but the send() method doesn’t return anything.

Below is a snipped of code from my REST controller. At the very bottom you can see where I’m adding a new CompletionStage to the CompletableFuture (with .thenApply()) and printing out the response, which is null:

`

// @formatter:off
@PostMapping(path = “/{id}/items”,
consumes = {MediaType.APPLICATION_JSON_VALUE})
public CompletableFuture<?> describeItems(
@PathVariable String id,
@RequestBody List request) {
// @formatter:on

Assert.hasText(id, “null PackageId String”);
Assert.notEmpty(request, “null Item descriptions”);

String[] lineIdArray = new String[request.size()];
Item[] item = new Item[request.size()];
String[][] serialNumbers = new String[request.size()][];
int i = 0;
for (ItemDescriptionDTO description : request) {
lineIdArray[i] = IdentifierFactory.getInstance().generateIdentifier();
Assert.notEmpty(description.getItemAttributes(), "null/empty item attributes: " + description);
Assert.notEmpty(description.getSerialNumbers(), "null/empty serial numbers: " + description);
Item.Builder itemBuilder = Item.builder();
description.getItemAttributes().forEach((k, v) -> {
itemBuilder.addAttribute(k, v);
});
item[i] = itemBuilder.build();
serialNumbers[i] = description.getSerialNumbers();
i++;
}
RecordItemsCommand.Builder commandBuilder =
RecordItemsCommand.builder(id, CallingAgentContext.agentId(), lineIdArray);
for (i = 0; i < lineIdArray.length; i++) {
commandBuilder.attachItem(lineIdArray[i], item[i], serialNumbers[i]);
}

return commandGateway.send(commandBuilder.build()).thenApply(l->{
System.out.println("----------------------------------------------: " + l);
return l;
});
}

`

What am I missing? How can you determine what the result of the command sent through the gateway will be?

Thanks!

To follow up with a little more detail. Below is a snippet from my aggregate root that shows the @CommandHandler and @EventSOurcingHandler methods:

`

@CommandHandler
void handle(@NotNull RecordItemsCommand command) {
Assert.notNull(command, “null RecordItemsCommand”);

command.getLines().forEach(line -> {
Assert.state(!lineItems().containsKey(line.getId()), "LineItem already attached to id: " + line.getId());
});
command.getLines().forEach(line -> {
apply(new ItemRecordedEvent(command.getPackageId(), line.getId(), line.getItem(), line.getSerialNumbers(),
command.getAgentId()));
});
}

@EventSourcingHandler
void on(ItemRecordedEvent event) {
LineItem lineItem = new LineItem(event.getLineItemId(), event.getItem(), event.getSerialNumbers());
if (lineItems == null) {
lineItems = new HashMap<>();
}
lineItems.put(lineItem.getId(), lineItem);
}

`

I cleaned up my REST controller method and added a workaround, but I think it has some serious flaws (refer to thenApply() CompletionStage of the CompletableFuture):

`

// @formatter:off
@PostMapping( path = “/{id}/items”,
consumes = {MediaType.APPLICATION_JSON_VALUE})
public CompletableFuture<List> describeItems(
@PathVariable String id,
@RequestBody List request) {
// @formatter:on

Assert.hasText(id, “null PackageId String”);
Assert.notEmpty(request, “null Item descriptions”);

RecordItemsCommand cmd = buildRecordItemsCommand(id, request);

return commandGateway.send(cmd).thenApply(commandResult -> {
List lineIds = StreamSupport.stream(cmd.getLines().spliterator(), false).map(line -> line.getId())
.collect(Collectors.toList());
return lineIds;
});
}

`

The issue I see with this approach is that it depends on the implementation of the @CommandHandler method. With my current implementation this will work (see implementation attached to this thread), however, it’s not hard to imagine how easily it would be to break down the assumption that every line item identified by the command will result in an ItemRecordedEvent.

I suspect that I am going against the grain of DDD best practices by trying to expose the LineItem entity identifiers this way, since LineItem is not an aggregate root. Maybe the response to the REST method in question should be void and the client should just refresh the view of the aggregate root?

Any guidance would be greatly appreciated!

Hello,

I would say it is not a good idea to return values from commands that is a job for query side. Think about what would happen if command execution would be distributed.

In an instance like this I would only saj ok command is accepted and procesing (HTTP response code accepted 203?) results will be avaiable at this location(response heade location: ).

Hi Troy, Rene,

it’s actually not a bad thing to return values based on a command. However, you need to be careful that the return value doesn’t become the reason to send the command. The return value should describe some outcome. In fact, even when declaring a “void” command handler, you still have a return value. It is either null or an exception.

To answer Troy question: the return value of the send method is a CompletableFuture that eventually resolves to the value returned by the CommandHandler that has been invoked. For constructors (Command Handlers that essentially create a new instance of an aggregate), it is the ID of the aggregate that was created. For other Command Handlers, it is whatever that method returned. If that method threw an exception, the CompletableFuture is “exceptionally resolved”.

The fact that you have a return value, doesn’t force any client to wait. In fact, the CompletableFuture you receive from the send() methods can also be returned from Spring’s @RequestMapping methods. The view is then asynchronously rendered when the CompletableFuture is resolved. Async all the way and without any Threads blocking.

It is often advised to use 303 to enforce async behavior. In my eyes, this is incorrect. The assumption that requests are blocking because the protocol used between the components is blocking, is not true. Not in this case, at least. Nobody forces the sender thread on the client to block for the response, nor is the receiving thread on the server forced to send the response. Using 303’s does make the process clearly async, but also adds a lot of complexity. The url given with the 303 should point to the machine that will eventually need to hold the response. Also, how long will that server need to hold on to that response, before it can be cleared? A lot of extra complexity that doesn’t really help. Just use proper async libraries on the sending and receiving side, and you’re in a great position.

Hope this helps,

Allard

Thanks Allard! It makes a lot of sense that the CommandGateway.send*(command) methods will forward the result of the CommandHandler.