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.
To achieve the same result for single DTO you can use JsonAutoDetect annotation on class level:
Please notice that there are a lot different accessor settings - like including only public getters (and skipping package-protected), etc.
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
and registering into ObjectMapper
objectMapper.registerModule(new ParameterNamesModule());
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 :)
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