Validation logic: command handler or API?

Sorry about all the questions being posted, I’ve been working on a proof-of-concept and I’m trying to understand the best way to construct a project based on Axon. This time has to do with validation.

I’ve been using Spring-based JSR-303 validation to ensure the consistency of data provided on API requests and commands. For example, I have this data structure for a create user request:

data class NewUserRequest(
    @Length(min = 4, max = 30)
    val username: String?,
    @Email
    val emailAddress: String?,
    @NotEmpty
    @Length(min = 1, max = 50)
    val firstName: String,
    @NotEmpty
    @Length(min = 1, max = 50)
    val lastName: String
)

And the command to create a new user:

data class CreateUserCommand(
    @NotBlank
    @Size(min = 6, max = 40)
    @Unique(byAggregate = User::class, mode = CrudMode.Create)
    val username: String,

    @NotBlank
    @Size(min = 1, max = 40)
    val firstName: String,

    @NotBlank
    @Size(min = 1, max = 40)
    val lastName: String,

    @Email
    @Unique(byAggregate = User::class, mode = CrudMode.Create)
    val emailAddress: String?
): Command {
    val id: UUID
        @RoutingKey
        get() = UUID.randomUUID()
}

I use a parameter resolver to run all of the constraint validators and generate an Errors object if any violations were found. The command handler injects this errors object and, if an error exists, throws an exception:

    @CommandHandler
    constructor(command: CreateUserCommand, validationErrors: Errors): this() {
        handleValidationErrors(validationErrors)

        applyEvent(NewUserEvent(command.id, command.username, command.firstName, command.lastName, command.emailAddress))
    }

This all works great, but the API ends up throwing a 500 error because the exception thrown by the commandGateway is an AxonException, and the cause chain doesn’t appear to preserve original exception type. Is there a way to determine the source exception type? Or is it better to not validate at all in the command handlers and always validate in the API? This gives me pause simply because I could have multiple places in code that trigger the command, and all of them would now need the same validation logic.

Hi @jesse.docken,

As a piece of info, this kind of validation you are doing (structural) shouldn’t be part of the Aggregate IMO. The Aggregate should “only” care about business decisions.

But to your question, you can’t preserve it but you can control it. Let me expand a bit.
Axon wraps the Exception because it cant be sure that the receiver knows about that specific Exception since it’s supposed to be a distributed environment. In that case, if the framework just serialized the Exception as is, you could very much get a deserialization problem or having to add/import every possible Exception into every possible caller.

To mitigate that, the Framework wraps it. There are several ways to achieve what you are trying to do and to know more about that, I would like to point you to our code-samples repo, specially to the distributed-exceptions module. Have a look and let me know if that helps you!

KR,

What’s the recommended practice for keeping structural validation all in one place? I’m fine with doing it at other points, but I don’t want to duplicate the data contract rules in multiple areas of the code, since that makes it much more likely that they’ll fall out of sync.

Also, though I think I understand what you mean, it’s hard to differentiate between structural versus business rules sometimes. For example, requiring a username to be between 5 and 30 characters is a business rule, but it’s enforced on the data structure.