How return error for asynchronous API call

Hi,
I have REST controller to processing POST requests asynchronously:

    @PostMapping("/signup")
    public CompletableFuture<ResponseEntity<String>> signUpUser(@RequestBody UserDTO 
               credentials) {
    return commandGateway.send(new SignupUserCommand(UUID.randomUUID().toString(), 
               credentials.getUsername(), credentials.getPassword(), credentials.getEmail()))
                .thenApply(result -> new ResponseEntity<>("User created successfully", 
                HttpStatus.CREATED));
    }

It sends SignupUserCommand to User aggregate’s CommandHandler which in turn fires UserCreatedEvent to this service method:

    @EventHandler
    @Override
    public void createUser(UserCreatedEvent event) {
        try {
            jdbcUserDetailsManager.loadUserByUsername(event.getUsername());
            throw new UsernameAlreadyExistsException("Username already exists");
        } catch (UsernameNotFoundException e) {
            jdbcUserDetailsManager.createUser(
                    User.withUsername(event.getUsername()).password("{bcrypt}" + event.getPassword()).roles(Roles.USER_ROLE).build());
        }
    }

As I understood from Axon documentation I have to implement in service class the ExceptionHandler like this:

    @ExceptionHandler
    public void handleUserExistsExceptions(UsernameAlreadyExistsException exception) {
       ...
    }

But how this service handler can transfer error message to return in controller ?

Hi,

your approach of using the @ExceptionHandler is a valid one.
Your ExceptionHandler can act upon a UsernameAlreadyExistsException which is “local” to the application handling the command. It can then map this exception to a “global” exception extending CommandExecutionException. In the details parameter, it can supply a globally understood error-code (think of what you would get back from calling an HTTP endpoint). This error code can be implemented as an enum shared between your applications and specified in your API.

You can watch Steven explain this in the live coding session here: Steven van Beelen - Live coding session: Axon 4.4 in practice - YouTube

For documentation on how to control which exception handler is invoked, refer to these docs: Message Intercepting - Axon Reference Guide

There are several reasons for not returning the UsernameAlreadyExistsException from the send(...) method directly:

  • The calling application does not necessarily have to have UsernameAlreadyExistsException on it’s classpath.
  • The stack trace of an exception is not serializable, hence the sender would not receive a “true” exception.
  • You would expose implementation-specific details in your public API, which makes changes later on harder.

If you want to have more control on this mapping, you can have a look at our sample repo here: code-samples/distributed-exceptions at master · AxonIQ/code-samples · GitHub . It showcases how to use interceptors directly instead of using the @ExceptionHandler abstraction.

Kind regards,
Marco

1 Like

Hi, Marco
Thank you much for help!
I saw the video and sources. It is what I need!

At fist I decided to go simplest way. Instead of mapping by using @ExceptionHandler, I just extend UsernameNotFoundException from CommandExecutionException:

public class UsernameAlreadyExistsException extends CommandExecutionException {
    public UsernameAlreadyExistsException(String message, Throwable cause, Object details) {

        super(message, cause, details);
    }
}

Accordingly my service method has been updated with additional parameters, including details (which is not used so far):

    @EventHandler
    @Override
    public void createUser(UserCreatedEvent event) {
        try {
            jdbcUserDetailsManager.loadUserByUsername(event.getUsername());
            throw new UsernameAlreadyExistsException("Username already exists", null, ExceptionStatusCode.USER_EXISTS);
        } catch (UsernameNotFoundException e) {
            jdbcUserDetailsManager.createUser(
                    User.withUsername(event.getUsername()).password("{bcrypt}" + event.getPassword()).roles(Roles.USER_ROLE).build());
        }
    }

Then in controller I expect returning error (in simplest form so far):

    @PostMapping(SIGNUP_ENDPOINT)
    public CompletableFuture<ResponseEntity<String>> signUpUser(@RequestBody UserDTO credentials) {
        return commandGateway.send(new SignupUserCommand(UUID.randomUUID().toString(), credentials.getUsername(), credentials.getPassword(), credentials.getEmail()))
                .thenApply(result -> new ResponseEntity<>("User created successfully", HttpStatus.CREATED))
                .exceptionally(e -> {
                    return ResponseEntity.badRequest().body("Error");
                });
    }

I got UsernameAlreadyExistsException in console but signUpUser method returns “User created successfully”. Debug and logging shows that code of .exceptionally even not triggered.

I think the problem is that I expect error returned in controller which send SignupUserCommand. But service which throws exception, processes not this command but UserCreatedEvent issued on next step by User aggregate. Sequence logic was: firstly I should create the aggregate and only next step should be aggregate view creation by service. So my question still the same: how return this error to controller?

Hi,
given your code, i see the following problem: You are using the UsernameAlreadyExistsException that inherits CommandExecutionException and throw that from an EventHandler. This will not work. CommandExecutionExceptions are meant to originate from CommandHandlers.
This makes me realize, i apparently have not read your initial question thoroughly enough.

We need to discuss the intention behind events here in more detail: Events represent a fact that something has happened in the system. An event should only be emitted, when the system is sure that the fact the event represents is allowed to happen. This decision should be done in the CommandHandler, because Commands are allowed to fail (or be rejected).

To illustrate, consider the following scenario: Your event store contains events of users being created. Creation of two users with the same name is forbidden. The event-store is considered immutable.
What would be a valid action to take for any system reading duplicate creation events?
Does every event handler have to check uniqueness on their own?
EventHandlers must be able to trust the “truthyness” of the EventStore and we therefore have to prevent the creation of faulty events in the fist place.

Further, AxonFramework will only wait for the execution of the CommandHandler when using send() on the commandGateway. Invoking the EventHandlers is done asynchronously. In the case of a replay, that might as well be years from when the command is processed.

So the first step is to ensure the decision in your code, whether a user can be created or not, should be done in the CommandHandler, not the EventHandler.

Since we cannot avoid erros in software, there of course are ways to handle erros in EventHandlers. You can read up on them here. Note however, there is (by design) no way to propagate that to the sender of a command.

1 Like

Thanx again, Marco!
The reason of problem and concept are crystal clear now.

As far as I understand the event store is a write-store. Despite of that there are mechanism to query it for aggregates with duplicate fields? Or It is acceptable practice to call (directly or by query) read-store service methods from aggregate’s constructor CommandHandler in order to check duplicates?

The idea for that is to have a projection used by the command model. What you are looking for is called “set based validation”. We do have a code sample for that, which happens to exactly match your use-case of ensuring uniqueness for accounts: code-samples/set-based-validation at master · AxonIQ/code-samples · GitHub .
I hope the sample code explains the implementation details, otherwise please let me know!

1 Like

Hi,

Interestingly enough, another approach was suggested in one of the Axon podcasts, I’ll try to find the link again. As far as I remember, it’s not exactly the same use case, the goal was to maintain some sequences on the command side as well.

While the solution mentioned above is perfectly valid and quite straightforward to implement, it still implies some kind of coupling between your write and read sides. In other terms, your application needs two types of storage to work properly while you expect your event store to be enough for the command side. Also your commands are supposed to be processed as fast as possible, especially in a distributed context. Here your command can fail for the wrong reasons if you can’t access to your read model.

As an aggregate is a transaction boundary, the idea is to instead represent the username as an aggregate root itself and uses its identifier to prevent duplicates. Therefore, if you try to sign up twice with the same username, the unique constraint validation will be covered by the event store because it will detect that you already have some existing events for that unique id. This would of course require some kind of synchronization between the Username and User aggregate roots but that could be done with stateless event handlers sending commands.
By doing so, you don’t have to use the unique username as the User aggregate identifier, you could just send a command to create a new one, link it to the User and mark the previous one as unused.

I hope it makes sense,

1 Like

Hi, Jerome!
Interesting idea. I sure it will work. But in my opinion - the drawback is breaking of logical User aggregate to two more synthetic aggregates.