0

After reading about Value Objects, I think they're pretty cool and should be used, but I am not sure if I am doing it the right way.

Let's assume that I have a simple DTO to create a user, which contains information that should be validated (e.g., the email must be valid, the birthdate cannot be in the future, etc.) from a technical point of view. At the same time, I have a business requirement such as the user can't be younger than 14 years old, and I don't think that hardcoding this into the Birthdate class is a good idea. I think it would be better to move this validation to another service.

public class CreateUserPayloadDto {
  @JsonPropery("email")
  Email email; // user's email
  @JsonPropery("birthdate")
  Birthdate birthdate; // user's birthdate
}

If I use Value Objects in the DTO, I can't test negative cases properly (for example, if the birthdate is in the future, then BAD REQUEST must be returned).

I have come up with three solutions:

  • Do not use Value Objects but use Java Bean Validation (then convert it to a POJO with Value Objects inside).
  • Use Value Objects and do not write tests for negative cases.
  • Create a separate DTO that uses primitives but is the same as the DTO with Value Objects, and test it this way. See example of code below
public class CreateUserPayloadDto {
  @JsonPropery("email")
  String email; // user's email
  @JsonPropery("birthdate")
  String birthdate; // user's birthdate
}

If you need more info, I will provide it as soon as I can. Thank you!

UPD:

Here is an example of my integration test:

@SpringBootTest
@AutoConfigureWebTestClient
public CreateUserEndpointTest {

  @Autowired
  WebTestClient webTestClient;

  @Test
  public void shouldReturn400BadRequestIfBirthdateIsInTheFuture() {
    final CreateUserPayloadDto body = CreateUserPayloadDto.builder()
          // I can't do this, because Birthdate cannot be instantiated 
          // since it is invalid!
         .birthdate(Birthdate.of(LocalDate.now().plusYears(1)))
         .build();

    final WebTestClient.ResponseSpec responseSpec = sendRequest(body);
    
    responseSpec.expectStatus().isBadRequest();
  }
}

7
  • Your question doesn't entirely make sense to me as stated (though I can guess what you're getting at). But for clarity - what have you read, and as a result of this, what do you think value objects are? Value Objects are just objects that have their identity (and equality) determined solely by their value. As in, two instances are equal and interchangeable if they have the same value (you're not testing for reference equality). Both of your properties are already value objects of a kind. Everything else you add on top of that (constraints, etc.) is up to you as the designer of the type. Commented May 25 at 19:59
  • Thanks for your reply! For me, value objects are simple immutable wrappers around primitive or other object, that can be validated(or not) before being instantiated. Like, email address will be checked that format is correct, birthdate is not in the future, etc. II am curious about validation: should Value Objects contain business rules (like a minimum age requirement or a domain that can't be used for email)? Hope this helps.
    – Mercury
    Commented May 25 at 20:16
  • Why wouldn't the validation logic be tested for negative cases when using Value objects? If the contract that you are exposing to external consumers of this service states something along the lines of "If the user is under 14 years of age, then expect a 400 Bad Request", then this should absolutely be tested, and splitting the logic out to a separate class shouldn't prevent this from happening. Is there some other problem here with the testing approach which hasn't been mentioned? Commented May 26 at 9:51
  • I updated post with an example of my integration test. I hope it is more clear now
    – Mercury
    Commented May 26 at 10:06
  • @FilipMilovanović, you've got to be careful about explaining the difference between reference and value equality. Reference equality is about comparing the implicit value of the in-memory storage address for the structure, whereas value equality is about comparing the explicit values and ignoring the implicit one. It matters when it comes to things like serialisation, because at that point the objects cease to have an in-memory representation, so the memory address values (or some alternative numerical scheme) have to be made explicit at that point and stored the same as the rest. (1/2)
    – Steve
    Commented May 26 at 11:02

2 Answers 2

1

VO, DTO, POJO this is a joggling between terminology in the end they are just encapsulations for transferring data. If there are getters involved then it is about value object (VO), if the transfer it is over the network then it is about data transfer object (DTO), if there it is a random data container (without getters and setters) then it is about plain old java object (POJO), thus wrap value objects into data transfer objects to transfer information over the network.

To talk about the validation there are two different validations: data integrity and data consistency. In a web application the layer receiving requests validates data for integrity (e.g. data sent for age is numeric), the layer processing the request validates data for consistency with the application business(e.g. age it is at least fourteen) and the layer persisting data validates data for persistence environment integrity (e.g. the data to be persisted contains just information that could be persisted by the persistence environment. Applied to database persistence that would be using prepared statements that transparently ensure information validity).

When in doubt avoiding using well known terminology while thinking about what needs to be done and things start to get simpler.

1

If you are able to use your value objects with thier built in validation on the client side of the DTO it begs the question of why you have a DTO at all.

The purpose of a DTO in this case would be to send raw unprocessed data needed to create a object to the backend for validation and transformation into that object.

But it seems like you could just do all that client side and send the competed User object, validation and processing already done to the server for persistence if required at all.

On a side note, using a value object to restrict a date range like this is probably not a great idea. Its going to rely on you injecting the current date somehow and obviously the validity of the object can change while the data is at rest, as time progresses.

2
  • Thanks for your answer! Validation logic is not performed on client side. Everything should be validated on backend. So, if I understand your answer correctly - I need to use raw objects such as LocalDate(for birthdate), String(for email), validate it and then map it to domain objects that use Value Objects?
    – Mercury
    Commented May 26 at 10:10
  • i would say "need" its more that your approach seems to lack coherency. you have picked aspects of several solutions and are using them all.
    – Ewan
    Commented May 26 at 12:27

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.