I am working on a small event-sourced proof-of-concept using Spring Boot and Axon Framework to compare it to a more traditional way of working with Spring Boot.
A request comes in via a Spring Boot controller, which then sends a command or query over Axon’s command gateway or query gateway. These both work with CompletableFuture. How do I handle errors and convert them to the appropriate HTTP status code?
I am used to converting custom core exceptions (i.e. ActionNotAllowed or ResourceNotFound) to their respective HTTP status code by catching it and throwing a ResponseStatusException. I haven’t used CompletableFuture before in this context. What would be a good way to go about this and still keep my application’s core presentation-agnostic?
instead of using try-catch, which is an imperative programming style, you can use the completableFuture.exceptionally() or completableFuture.exceptionallyCompose() methods to translate exceptions from a CompletableFuture to another type of response.
Note that the first allows you to map an exception to a “regular” response, which would be useful if you use Spring’s ResponseEntity. If you wish to map an exceptionally completed CompletableFuture to one that is exceptionally completed with a different exception, the second one is more flexible as you can return any new CompletableFuture you wish.
we have a quite similar setup and what we usually do in the REST controller is using the org.axonframework.commandhandling.gateway.CommandGateway#sendAndWait() variant of the command gateway to send a command and synchronousy wait for the command to complete or throw an exception. We have a central class containing spring web exception handlers for all known exceptions to expect (see Web on Servlet Stack) that transforms an exception into a ResponseEntity with the appropriate status code. Since we’re using Axon Server to distribute command handling we implemented a MessageHandlerInterceptor that allows forwarding exception details from remote command handler to the controller (also see this thread: Get the entire stack trace fpr remote command handlers)
If you would use the org.axonframework.commandhandling.gateway.CommandGateway#send() variant of the command gateway to send the commands in your controller, you would need to block until the completeable future completes to get the exceptional result as described by @allardbz or e.g. return a HTTP 202 accepted response status code to indicate only that the command is submitted (allowing the outcome to be queried with a separate request on the query side).
Using your @allardbz and @lfgcampos’ suggestions, I am now working on wrapping my custom exceptions in exceptions that AxonIQ understands. This is where a MessageInterceptor will come in handy.
If you would use the org.axonframework.commandhandling.gateway.CommandGateway#send() variant of the command gateway to send the commands in your controller, you would need to block until the completeable future completes
Actually, Spring supports CompletableFuture as a return type for controller methods, and will switch to Async mode to return the response to the client. No need for any blocking
I typically would just use the sendAndWait method as has been said already in the thread because it lets you write synchronous and (arguably) more readable code.
Just to add - with Project Loom around the corner the performance of sendAndWait will not neccesarily be worse than the asynchronous alternative and so the potential performance impact of a synchronous approach may be gone in a future Java + Spring + Axon version.
It seems like there is no queryAndWait equivalent.
But you could always just call get() on the CompletableFuture<R> which is returned. It is annoying you might have to deal with a checked exception, but it is still totally workable.