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.

Hi Jesse,
just came across your post here. I hope it is not too late. Lucas gave you a hint on handling exceptions. I would like to share a bit on business rules.

I have seen a business rule like that (a username to be between 5 and 30 characters) in too many projects. Although it is quite often expressed by business people as a rule, it actually is not in many cases. As a developer you need to ask the business people questions like:

  • what is the purpose to have a username within 5 to 30 characters?
  • if you have more than one customer of your application, do they all agree on the same rule?
  • a rule for length isn’t almost always enough. Is five spaces still a valid username? Well, with this rule it actually is. So more questions must be asked. Can it contain whitespaces? Can it contain special characters? Should username be a mix of upper-cased and lower-cased letters? Does case sensitivity matter here?
  • you might argue that for example, a bank account number has a business rule to be a valid number. However, I don’t see it that way. Bank account numbers (such as IBAN) have a very specific structure which is based on a standard. I can’t imagine business people (such as product owner) to overrule it. If they can’t change it, because it is a standard (applies to emails as well) then it is a technical rule, not a business rule.

And technical (structural) rules should be enforced at API level. Keep your aggregates small nice clean and tidy. Delegate non-business logic to other components/layers.

Also, do not mix domain classes (such as commands) with other layers classes (such as CrudMode in your example above) and vice versa. For example, you have @Unique annotation which references a User class, is this an API layer class or is your aggregate class? If it is an API layer class, then you have polluted your aggregate command class with API - not a good choice. If it is an aggregate class, then you leaked your precious aggregate to your API layer - also not a good choice.

A hexagonal or onion architecture should help you navigate these dangerous waters of software design :slight_smile:

HTH,
David

I recall reading a book about DDD stating you should avoid basic data types (like string) in your aggregate but define a dedicated entity or value object for each aggregate member instead.

A field username, now defined as string, would be replaced with an immutable value object Username. This value object could then define the rules which are applicable for a username in your particular problem space (or boundary context if you narrow it down) and verify them on construction throwing a ValidationException on constraint violation. The aggregate doesn’t have to do any non-business rule related validation since you simply cannot set an attribute to an invalid value.

@BribedSeeker904 Do you consider this alternative a valid approach according to the remarks in your post?

Hey David, thanks for your helpful post. The annotations I had in the explanation were primarily for me to test using Java validation annotations and seeing how that worked in the context of command execution within the Axon Framework and wasn’t representative of any actual business rules. The points you raise are absolutely valid, however, and those types of schema-like requirements often don’t make sense at an aggregate level unless there’s a codified rule within the business domain that represents that (i.e., bank account numbers or email addresses), in which case it completely makes sense to follow Kurt’s suggestion of using data classes that can enforce those requirements in that context.

Regarding your comment about CrudMode, that annotation is specifically for enforcing uniqueness of attributes across all aggregates (something I worked with on a separate thread). The byAggregate points to the aggregate class, and the mode identifies under what operations the command is operating under. So it’s all operating under the scope of the aggregate itself and doesn’t bleed into other layers, as you fear. Since the commands are what drives the changes to an aggregate and the creation of new events, the enforcement of uniqueness has to be done at the point of when the aggregate receives the command, and this annotation is meant to make expressing that more convenient. The specifying of the aggregate class is necessary just so the whole enforcement can occur automagically, though I could capture that better with a class annotation specifying what aggregate the command belongs to.

Hello Kurt, I do consider this as a valid approach. Besides being mentioned in DDD book(s) such wrapping class is logical and practical as it provides encapsulation. Which leads us to a very interesting feature.

Imagine you have one customer who requires the username to be lowercased with minimum of five characters. And another customer who requires a bit different rules.

By wrapping the string in a class Username with embedded validation rules you can unleash the power of class loading and maven dependency management to build two applications for two customers from a single source code just by toggling a single property :slight_smile: effectively replacing one Username class with another while the rest of the code remains exactly the same.

David

Hey Jesse, thanks for the clarification. Now I understand your solution better. Let me just explain my fears about the snippet above.

Axon is primarily an event sourcing approach so the word ‘Crud’ rings a warning bell in my head due a 20+ years of writing crud applications. Sorry for that. And the way you explain it is quite okay for me.

I am still concerned about referencing the aggregate class in your command. In your situation, your code is still valid, I totally agree with that. This change though once you separate commands into a separate module so you can share it between modules such as domain logic and API, for example. Reasons to split an application into such modules might be different (scaling comes to mind) but when it happens you won’t be able to “share” the command class that easily. You can of course make a duplicate of the command class in your API module without the annotation and use it in your controller while the original annotated command would be used by the domain module. We did exactly that, started with a single application containing both (REST) API and business logic in separate packages. Then we split API and BL into its own modules and then we also split API into a command API and a query API so we can run multiple instances for query requests (reading happens in our system more often) and only a few instances for commands API and BL. And then we added a new spring boot app with a websocket API reusing the same commands. So you see, our commands need to stay “pure”.

Please take my notes and ideas really as hints that I would normally give you during a code review. Some may be valid and some not.

Another interesting point I would like to make on the topic of validations is importing data from legacy system. These system may contain data that does not comply with validation rules in the new system. But again this might not be your case.

Went through the code again :smiley: and noticed some discrepancies in request DTO and command class. The validations do not match exactly:

  • username has minLength of 4 and maxLength of 30 in request DTO but minLength of 6 and maxLength of 40 in command
  • username is optional in request DTO but not in command
  • firstName and lastName have maxLength of 50 in request DTO but 40 in command
  • firstName and lastName use NotEmpty in request DTO but NotBlank in command

Which brings me to a question… If the class of the request is almost identical to the command, why don’t you use the command in the API so you don’t have to maintain the validations in two places? Except for the fact, you don’t want to leak the aggregate class into your API layer.

And one more thing… Axon Framework provides a validating command interceptor that will execute JSR-303 validation on a command before it reaches your aggregate. You wouldn’t need to code your own handleValidationErrors method and inject the Errors bean. Effectively rendering your code above into this:

@CommandHandler
constructor(command: CreateUserCommand): this() {
   with (command) {
    applyEvent(NewUserEvent(id, username, firstName, lastName, emailAddress);
  }
}