1

I have an API with two entities Foo and Bar with a one-to-many relationship, i.e. each Bar must reference a Foo.

I have endpoints to create both Foo and Bar - these endpoints have their respective DTOs defined with javax validations.

public class CreateFooDto {
  @NotNull public Integer field1;
  @Length(min=1, max=8) public String field2;
}

public class CreateBarDto {
  @NotNull public Boolean field1;
  @NotEmpty public String field2;

  @NotNull public String fooId; // the foreign key to Foo
}

Now, I want a new endpoint which creates a Bar and Foo together and links them, rather than first creating the Foo and then creating the Bar passing the fooId. I'm going to need all the same fields, with the same validations, with the exception of the CreateBarDto.fooId being required. Ideally I'd like to reuse the javax validations I've already defined, rather than repeating them.

I wanted to nest the existing DTOs inside a combined DTO for the new endpoint, but there's a problem with that @NotNull on CreateBarDto.fooId - it is in fact not required. So far the best solution I've come up with is this:

public class CreateBarWithFooDto {
  @Valid public CreateFooDto foo;
  @Valid public CreateBarDto bar;
}

public class CreateBarDto {
  @NotNull public Boolean field1;
  @NotEmpty public String field2;

  public String fooId; // the foreign key to Foo
  public boolean fooIdNotRequired; // optional flag to indicate fooId not required

  @AssertTrue
  public boolean isFooIdRequired() {
    return fooIdNotRequired || fooId != null;
  }
}

While this works, it's really quite clunky. Just wondering if anyone can suggest a better pattern for reuse of javax validations like this, or if there's any javax annotations I'm not aware of that might help out with this?

davnicwil
  • 28,487
  • 16
  • 107
  • 123

1 Answers1

1

One option would be to work with validation groups. The javadoc for javax.validation.groups says:

A group defines a subset of constraints. Instead of validating all constraints for a given object graph, only a subset is validated depending on the group targeted.

Each constraint declaration defines the list of groups it belongs to. If no group is explicitly declared, a constraint belongs to the Default group.

When applying validation, the list of target groups is passed along. If no group is explicitly passed along, the javax.validation.groups.Default group is used.

So to give you an example in your CreateBarDto we can have a new group MyFirstGrup.class which is defined only for the validations for field1 and field2

public class CreateBarDto {
  @NotNull(groups = {MyFirstGrup.class}) 
  public Boolean field1;
  @NotEmpty(groups = {MyFirstGrup.class})
  public String field2;

  @NotNull public String fooId; // the foreign key to Foo
}

You need now to trigger that specific group which is usually via

validator.validate(yourBean, MyFirstGrup.class);

If you use Spring check the @Validated annotation which has the support for specifying the group you need to run. More info here

Community
  • 1
  • 1
Ioan M
  • 1,105
  • 6
  • 16
  • Thanks, this is a great solution. Exactly what I was thinking might exist, with the annotation-based declarative approach. – davnicwil May 07 '19 at 14:50