Search code examples
javaspringspring-bootbean-validationhibernate-validator

Validation through "mixins"


I am developing a RESTful API in Spring Boot 2+, for which I need to perform several validations. Nothing really fancy, just the typical @NotNull, @NotEmpty, @Max, @Min, @Email, @Regex, @Future, etc stuff...

Except that I have beans from an API that I must use yet cannot modify. This means that I cannot annotate the fields and methods in those DTOs.

It would be great if I could create mixin-like classes or interfaces with the same structure of the real DTOs I must use in the API, on which I would happily place bean-validation's annotations.

For example, if I had the following DTOs that I couldn't modify:

public class Person {
    private String name;
    private String dateOfBirth;
    private Address address;

    // constructors, getters and setters ommited
}

public class Address {
    private String street;
    private String number;
    private String zipCode;

    // constructors, getters and setters ommited
}

I would create the following 2 interfaces that mimic their structure and annotate them as I need:

public interface PersonMixin {
    @NotBlank String name();
    @Past String dateOfBirth();
    @Valid @NotNull Address address();
}

public interface AddressMixin {
    @NotBlank String street();
    @Positive int number();
    @NotBlank String zipCode(); // Or maybe a custom validator
}

As you see, the name of the methods in the interfaces match the names of the properties of the bean classes. This is just one possible convention...

Then, ideally, somewhere while the app is loading (typically some @Configuration bean) I would be very happy to do something along the lines of:

ValidationMixinsSetup.addMixinFor(Person.class, PersonMixin.class);
ValidationMixinsSetup.addMixinFor(Address.class, AddressMixin.class);

Except that ValidationMixinsSetup.addMixinFor is pure fantasy, i.e. it doesn't exist.

I know that there exists a similar construct for Jackson regarding JSON serialization/deserialization. I've found it extremely useful many times.

Now, I've been looking at both Spring and Hibernate Validator's source code. But it's not a piece of cake... I've dug into ValidatorFactory, LocalValidatorFactoryBean, TraversableResolver implementations, but I haven't been able to even start a proof-of-concept. Could anyone shed some light into this? I.e. not how to implement the whole functionality, but just how and where to start. I'm after some hints regarding which are the essential classes or interfaces to extend and/or implement, which methods to override, etc.


EDIT 1: Maybe this approach is not the best one. If you think there's a better approach please let me know.


EDIT 2: As to this approach being overly complicated, too convoluted, Rube Goldberg, etc, I appreciate and respect these points of view, but I'm not asking whether validation through mixins is good or bad, convenient or inconvenient, neither why it might be like so. Validation through mixins has pros on its own and I think it could be a good approach for some valid use cases, i.e. having declarative validation instead of scripted or programmatic validation while also separating validation from the model, letting the underlying framework do the actual validation job while I only specify the constraints, etc.


Solution

  • Using programmatic API (as mentioned in the comment) in case of Person you could apply next mappings for your constraints:

        HibernateValidatorConfiguration config = Validation.byProvider( HibernateValidator.class ).configure();
        ConstraintMapping mapping = config.createConstraintMapping();
        mapping.type( Person.class )
                .field( "name" )
                    .constraint( new NotNullDef() )
                .field( "number" )
                    .constraint( new PositiveDef() )
                .field( "address" )
                    .constraint( new NotNullDef() )
                    .valid();
    
        Validator validator = config.addMapping( mapping )
                .buildValidatorFactory()
                .getValidator();
    

    And as you are using Spring - you would need to do that in one of your sping config files where you define a validator bean.