-1

I've got a record that I use for making update requests via json/Jackson:

public record UpdateRequest(
    Optional<String> field1,
    Optional<LocalDate> field2,
    Optional<@Max(24) Integer> field3,
    ...
    Optional<String> specialField) {}

I want to prevent updates from happening if the entity is marked as closed, except for specialField. That can always be used to update the entity.

While the entity is closed, I'd want to allow an update request like:

{
  "specialField": "newValue"
}

But prevent one like:

{
  "field1": "newValue"
}

I'm struggling to come up with a good solution for checking whether the update can be applied. This is what I have:

public boolean isUpdateValid(Entity entity, UpdateRequest updateRequest) {
  return !entity.isClosed() 
      || (updateRequest.field1().isEmpty()
          && updateRequest.field2().isEmpty()
          && updateRequest.field3().isEmpty());
}

But this is pretty error-prone, because no one will update isUpdateValid when new fields are added to UpdateRequest. Does anyone have suggestions about better ways to structure this so that it is more maintainable as UpdateRequest changes?

2
  • 1
    Not an answer for the question, but just pointing out that best practices do not use Optional as a field, see this StackOverflow answer. Commented Oct 8, 2024 at 21:05
  • I'm well aware of that, but have plenty of industry experience that has led me to conclude that this is way better than having nullable fields. Commented Oct 8, 2024 at 21:42

3 Answers 3

1

I came up with the following solution that might be a little janky, but should work.

public boolean isUpdateValid(Entity entity, UpdateRequest updateRequest) {
  return !entity.isClosed() || new UpdateRequest(Optional.empty(), Optional.empty(), ..., updateRequest.specialField()).equals(updateRequest);
}

This should work since records have generate an equals method, but won't work if you override it in your record.

Sign up to request clarification or add additional context in comments.

1 Comment

I think this is a great idea, and it's made even better because we have auto-generated builders for these records, so I can just do: new UpdateRequestBuilder().specialField(updateRequest.specialField()).build().
0

First of all, UpdateRequest should contain the isValid-method. This way, you minimize the problem of anyone "forgetting" to update the list of fields when a new field is introduced.

Then, you can either play around with reflection, to make your test 100% dynamic, or simply declare a list of your attributes, maybe in the form of functions, which access them.

Something along the lines of:

public record UpdateTest(
        Optional<String> field1,
        Optional<Integer> field2,
        Optional<String> field3,
        Optional<String> specialField) {
        
    private final static List<Function<UpdateTest, Optional<?>>> NORMAL_FIELD_ACCESSORS = List.of(
        UpdateTest::field1,
        UpdateTest::field2,
        UpdateTest::field3);
    
    public boolean isAllNomalFieldsEmpty() {
        return NORMAL_FIELD_ACCESSORS.stream().allMatch(f -> f.apply(this).isEmpty());
    }
    
    // And whatever other validation methods you need
}

Comments

0

You can use reflection to check whether all fields, but the special one are empty. That's probably the most maintainable in terms of future changes to the class, but it comes at the cost that reflection is slower (probably negligible).

public boolean isUpdateValid(Entity entity, UpdateRequest updateRequest) {
  if (!entity.isClosed()) {
    return true;
  }
  String specialFieldName = "specialField";
  boolean areNonSpecialEmpty = Stream.of(updateRequest.getClass().getDeclaredFields())
          .filter(field -> !field.getName().equals(specialFieldName))
          .map(field -> {
            try {
              field.setAccessible(true);
              return field.get(updateRequest);
            } catch (IllegalAccessException e) {
              throw new RuntimeException("Handle accordingly", e);
            }
          })
          .map(val -> (Optional<?>) val)
          .allMatch(Optional::isEmpty);
  //not sure if special field must be present, remove check if not required
  return areNonSpecialEmpty && updateRequest.specialField().isPresent();
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.