Enterprise Java

(De)serialization and Validation of Custom Primitives and DTOs

Lately, we presented you with our new HTTP framework – HttpMate. In the introduction article, we referred to the mapping of requests and responses to domain objects as “the most complicated technical detail”, and how another mate – MapMate, is there to help us out.

Indeed, MapMate takes the load off of HttpMate when it comes to mapping the request attributes to your domain objects. It takes care of converting the responses to the appropriate format (JSON, XML, YAML, etc), essentially performing deserialization and serialization, but also so much more.

In this article, we’ll focus on how MapMate helps us deal with (de)serializing the request/response objects in a controlled and predictable manner.

Custom Primitives

Let’s recap our example from the previous article; we have a simple UseCase of sending an email. For that we need an Email object, that would have:

  • Sender
  • Receiver
  • Subject
  • Body

All of these fields can be represented as Strings, or even as a byte array. The more generic type you choose to represent your data with, the more possibilities you have interpreting it later on. Imagine the following method:

public Object sendEmail(final Object sender, final Object receiver, final Object subject, final Object body) {
        ...
}

This leaves us with a lot of open questions:

  • is sender instanceOf String or byte[]?
  • what is the encoding?
  • is the body zip compressed?

The list goes on. While there are cases where this might be appropriate, I bet you would feel more comfortable with:

public String sendEmail(final String sender, final String receiver, final String subject, final String body) {
        ...
}

The latter leaves less room for interpretation: for instance, we no longer have to assume an encoding or question the type of the parameter altogether.

It’s still ambiguous though, is the sender field carrying the name of the user or her email address? This same ambiguity is the reason of endless uncertainty when writing unit tests… to the degree of using random string generators to test a method one knows must accept only email addresses.

The following method signature does way better in terms of ambiguity, both for humans and compilers:

public Receipt sendEmail(final EmailAddress sender, final EmailAddress receiver, final Subject subject, final Body body) {
        ...
}

The same way we can trust that String is a String and Integer is an Integer, we can now trust that the EmailAddress is an email address, and a Subject is actually a subject – they became custom primitives to the send email method.

Sender and Receiver are not faceless “Strings”, and they are very much different from “Subject” and “Body”. They are email addresses, and we can represent them as such by validating their value using, for example, some sane Regular Expressions. (beware ofReDoS)

The rationality of using factory methods as a means of creating “always valid” objects has been widely discussed and validated. Having that in mind, we’d create an EmailAddress class for our example use case that would then be used as a custom primitive type for Sender and Receiver fields.

public final class EmailAddress {
    private final String value;

    private EmailAddress(final String value) {
        this.value = value;
    }

    public static EmailAddress fromStringValue(final String value) {
        final String validated = EmailAddressValidator.ensureEmailAddress(value, "emailAddress");
        return new EmailAddress(validated);
    }
}

Since – the only instance variable is private and final, it can only be assigned using the private constructor, which can only be invoked from outside of the class using the public factory method which validates the input, before passing it to the constructor – we can be sure whenever we receive an instance of EmailAddress it is valid.

If you are curious at this point about the EmailAddressValidator implementation, be sure to checkout thesource code of this example project.

Now our domain objects can use not only the default primitives such as String, Double, Integer, etc but also Custom Primitives like EmailAddress and Body, Subject, etc. Typically though we’d need to be able to store a domain object in a database or communicate it to another service or UI. No other party though knows about a Custom Primitive called EmailAddress. Hence we need a “representation” of it, something both HTTP, persistence and human-friendly – a String.

public final class EmailAddress {
    private final String value;

    public static EmailAddress fromStringValue(final String value) {
        final String validated = EmailAddressValidator.ensureEmailAddress(value, "emailAddress");
        return new EmailAddress(validated);
    }

    public String stringValue() {
        return this.value;
    }
}

The method “stringValue” we added is the String representation of our Custom Primitive. Now we can send the “stringValue” of the EmailAddress and then reconstruct it based on the value received. In essence, the “fromString” and “stringValue” methods are the “deserialization” and “serialization” mechanisms of the EmailAddress, respectively.

Following this approach, we can create Custom Primitives for the Body and Subject of our Email as well:

public final class Body {
    private final String value;

    public static Body fromStringValue(final String value) {
        final String emailAddress = LengthValidator.ensureLength(value, 1, 1000, "body");
        return new Body(emailAddress);
    }

    public String stringValue() {
        return this.value;
    }
}

public final class Subject {
    private final String value;

    public static Subject fromStringValue(final String value) {
        final String validated = LengthValidator.ensureLength(value, 1, 256, "subject");
        return new Subject(validated);
    }

    public String stringValue() {
        return this.value;
    }
}

Data Transfer Objects

Armed with our Custom Primitives, we are now ready to create the proper Data Transfer Object – the Email, which is a pretty straight forward task, since it’s basically an immutable struct:

public final class Email {
    public final EmailAddress sender;
    public final EmailAddress receiver;
    public final Subject subject;
    public final Body body;
}

The same “always valid” approach applies to the Data Transfer Objects as well, except here we have an easier time, as we are leveraging our Custom Primitives.

The factory method of the DTO can be as simple as validating the presence of the mandatory fields, or as complex as applying cross-field validations.

public final class Email {
    public final EmailAddress sender;
    public final EmailAddress receiver;
    public final Subject subject;
    public final Body body;

    public static Email restore(final EmailAddress sender,
                                final EmailAddress receiver,
                                final Subject subject,
                                final Body body) {
        RequiredParameterValidator.ensureNotNull(sender, "sender");
        RequiredParameterValidator.ensureNotNull(receiver, "receiver");
        RequiredParameterValidator.ensureNotNull(body, "body");
        return new Email(sender, receiver, subject, body);
}

Unfortunately, modern (de)serialization and validation frameworks don’t play well with this kind of DTOs.

Here is an example of a JSON you would get in the best case if you feed an email DTO to such a framework with its default configuration:

{
  "sender": {
    "value": "sender@example.com"
  },
  "receiver": {
    "value": "receiver@example.com"
  },
  "subject": {
    "value": "subject"
  },
  "body": {
    "value": "body"
  }
}

While what one would expect is:

{
  "sender": "sender@example.com",
  "receiver": "receiver@example.com",
  "subject": "subject",
  "body": "body"
}

While this problem can be mitigated using a ton of boilerplate code, validation is a different kind of beast, which becomes deadly the moment you want to “report all validation errors at once” from your server. Why not tell the user right away that both sender and receiver are invalid, instead of sending her on a quest to obtain permit A38. In fact, that is how we felt when trying to write modern microservices while adhering to the best practices of the Clean Code, the “always valid” approach of Domain Driven Design, Domain Driven Security…

And that is the problem MapMate is there to solve.

MapMate

As withHttpMate we made sure to provide an easy builder to start with while keeping the possibility of fine grain customization. Here is the absolute minimum configuration that would make our Email example serialize, deserialize and validate our Custom Primitives and DTOs.

public static MapMate mapMate() {
    return MapMate.aMapMate("com.envimate.examples.email_use_case")
            .usingJsonMarshallers(new Gson()::toJson, new Gson()::fromJson)
            .build();
}

This piece will make the following JSON a valid request:

{
    "sender": "sender@example.com",
    "receiver": "receiver@example.com",
    "subject": "Hello world!",
    "body": "Hello from Sender to Receiver!"
}

You must specify the package to scan(recursively) and a pair of (un)marshallers. This can be anything that can make a string out of a Map and vice-versa. Here’s an example using ObjectMapper:

final ObjectMapper objectMapper = new ObjectMapper();
return MapMate.aMapMate("com.envimate.examples.email_use_case")
        .usingJsonMarshallers(value -> {
            try {
                return objectMapper.writeValueAsString(value);
            } catch (JsonProcessingException e) {
                throw new UnsupportedOperationException("Could not parse value " + value, e);
            }
        }, new Unmarshaller() {
            @Override
            public  T unmarshal(final String input, final Class type) {
                try {
                    return objectMapper.readValue(input, type);
                } catch (final IOException e) {
                    throw new UnsupportedOperationException("Could not parse value " + input + " to type " + type, e);
                }
            }
        })
        .withExceptionIndicatingValidationError(CustomTypeValidationException.class)
        .build();

What about the promised validation exception aggregation?
In our example, all of our validations return an instance of CustomTypeValidationException if the Custom Primitive or the DTO are not valid.

Add the following line, to instruct MapMate to recognize your Exception class as an indication of a validation error.

public static MapMate mapMate() {
    return MapMate.aMapMate("com.envimate.examples.email_use_case")
            .usingJsonMarshallers(new Gson()::toJson, new Gson()::fromJson)
            .withExceptionIndicatingValidationError(CustomTypeValidationException.class)
            .build();
}

Now if we try the following request:

{
  "sender": "not-a-valid-sender-value",
  "receiver": "not-a-valid-receiver-value",
  "subject": "Hello world!",
  "body": "Hello from Sender to Receiver!"
}

We will get the following response:

HTTP/1.1 400 Bad Request
Date: Tue, 04 Jun 2019 18:30:51 GMT
Transfer-encoding: chunked

{"message":"receiver: Invalid email address: 'not-a-valid-receiver-value',sender: Invalid email address: 'not-a-valid-sender-value'"}

Final words

The presented builder of MapMate is there to make the initial usage easy. However, all of the described defaults are configurable, furthermore you can exclude packages and classes both from the Custom Primitives and DTOs, you can configure which exceptions are considered Validation Errors and how they are handled, you can specify a different method name for the Custom Primitive serialization, or provide your lambda for (de)serialization of both altogether.

For more examples and details about MapMate, take a look at the MapMate repository.

Let us know what you think and what features would you like to see in MapMate next!

Published on Java Code Geeks with permission by Envimate, partner at our JCG program. See the original article here: (De)serialization and Validation of Custom Primitives and DTOs

Opinions expressed by Java Code Geeks contributors are their own.

Envimate

Envimate is a consulting and software engineering company that provides teams with additional brainpower and capacity in areas of DevOps, CI/CD, AWS, Java/Golang Development, Architecture and Clean Code, while also providing the community with opensource projects and increasing the exposure to best practices of software engineering
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Jenny
Jenny
4 years ago

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry’s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

Back to top button