DataTransferObject myth-busting

DataTransferObject is one of the most popular design patterns. It has been widely used in archaic Java Enterprise application and some time ago got second live due to growth of RESTful APIs. I don't want to elaborate on DTOs pros (like for example hiding domain logic, etc) because there are whole books talking about it. I just want to cover technological aspects of DTO classes.

Usually we create just simple POJO with getters, setters and default constructor. The whole mapping configuration usually limits to @JsonIgnore annotation added on getter we want to skip.
It's the simplest way but can we do better? I'll focus on most popular in Java world Jackson Mapper and try to answer 3 basic questions which are usually skipped during development, and which in my opinion can improve the design of the transport layer.

1. Can we skip getters?

Yes! Jackson is able to serialize our objects using different strategies. Those strategies are fully customizable on two levels: for ObjectMapper and for particular object. If we for example want to serialize all fields for all pojos in our context notwithstanding presence of getter we can do something like this:

mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)

To achieve the same result for single DTO you can use JsonAutoDetect annotation on class level:

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)

Please notice that there are a lot different accessor settings - like including only public getters (and skipping package-protected), etc.

2. Are setters necessary?

No! Jackson can deserialize our objects without setters, and there is nothing special we have to do... except removing needless setters.

3. Is the default constructor required? I always add it due to "No suitable constructor found for type" exception.

No! Jackson comes with @JsonCreator annotations which can be used to specify given constructor to be used for deserialization.

public class Product {

    private final String name;

    @JsonCreator
    public Product(@JsonProperty("name") String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

This approach allows us to create immutable objects and use them for round-trip communication (serialize/deserialize). As you've probably noticed parameter name is redundant in this case. The good news is that since Java 8 it's possible to store parameter info in runtime. To do that we've to add -parameters option to javac compiler. Jackson have module activating such support. After including it in the project

<dependency>
    <groupid>com.fasterxml.jackson.module</groupid>
    <artifactid>jackson-module-parameter-names</artifactid>
    <version>2.5.0</version>
</dependency>

and registering into ObjectMapper

objectMapper.registerModule(new ParameterNamesModule());

our brand new DTO looks as follows:

public class Product {

    private final String name;

    @JsonCreator
    public Product(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Note that @JsonCreator is needed only from jackson-databind 2.5.0. As you can see there are many options to make our DTOs lighter and prettier. The only thing needed is to understand how it's made :)

Comments

Unknown said…
One small remark: according to https://github.com/FasterXML/jackson-module-parameter-names there's still need to annotate constructor with @JsonCreator when using ParameterNamesModule deserialization approach.
StackHolder said…
@Tomek - That's described in the documentation, but code works in the way it has been written not documented ;) Just try if it works without annotaiton
Unknown said…
Strange - in my case version without @JsonCreator didn't work (even for single parameter constructor). I've tested this using MockMvc endpoint. Maybe I misconfigured something ;)
StackHolder said…
Ok, I've checked it. With jackson-databind 2.4.4 it works without @JsonCreator annotation, but if you update to databind 2.5.0 then you have to annotate your constructor.
Unknown said…
This comment has been removed by the author.

Popular posts from this blog

Understanding Spring Web Initialization

Overview of the circuit breaker in Hystrix

Do we really still need a 32-bit JVM?