{{announcement.body}}
{{announcement.title}}

DTO: Hipster or Deprecated?

DZone 's Guide to

DTO: Hipster or Deprecated?

The purpose of this article is to talk a little bit about the usefulness of DTO and address this question.

· Java Zone ·
Free Resource

Data Transfer Objects, known affectionately as DTOs, is the subject of a lot of discussions when we talk about the development of Java applications. DTOs were born in the Java world in EJB 2 for two purposes. 

First, to circumvent the EJB serialization problem; second, they implicitly define an assembly phase where all data that will be used for presentation is marshaled before actually going to the presentation layer. Since the EJB is no longer used on a large scale, can DTOs also be discarded? The purpose of this article is to talk a little bit about the usefulness of DTO and address this question.

After all, in an environment of several new topics, (for example, cloud and microservices) does this layer even make sense? When it comes to good software architecture, the answer is practically unanimous: It depends on how closely you want your entity to be coupled to the visualization layer.

Thinking about an underlying architecture in layers, and dividing into three interconnected parts in itself, we have the famous MVC.

model, view, controller

It is worth noting that this strategy is not exclusive to the stack of web applications like Spring MVC and JSF., Exposing your data in a restful application with JSON, The JSON data works as a visualization, even if it is not friendly to a typical user.

Once MVC is briefly explained, we will talk about the advantages and disadvantages of using DTO. Thinking of layered applications, DTO, above all, has the objective of separating the model from the view. Thinking about the problems of DTO:

  • Increases the  complexity
  • There is the possibility of duplicate code
  • Adding a new layer has the impact of the delay layer, that is, the possible loss of performance.

In simple systems that do not need a rich model as a premise, not using DTO ends upbringing great benefits to the application. The interesting point is that many serialization frameworks end up forcing the attributes to have accessor or getter and setter methods that are always present and public as mandatory, so, at some point, this will have an impact on application encapsulation and security.

The other option is to add the DTO layer, which basically guarantees the decoupling of the view and the model, as mentioned previously.

  • It makes it explicit which fields will go to the view layer. Yes, there are several annotations in various frameworks that indicate which fields will not be displayed. However, if you forget to write down, you can export a critical field accidentally, for example, the user's password.

  • Facilitates drawing in object orientation. One of the points clean code makes clear about object orientation is that  OOP hides the data to expose the behavior, and the encapsulation helps with that.

  • Facilitates updating the database. It is often essential to refactor, migrate the database without this change impacting the customer. This separation facilitates optimizations, modifications to the database without affecting the visualization.

  • Versioning, backward compatibility is an important point, especially when you have an API for public use and with multiple customers, so it is possible to have a DTO for each version and evolve the business model without worry.

  • Another benefit is found in the ease of working with the rich model and in the creation of an API that is bullet approved. For example, within my model, I can use a money API; however, within my visualization layer, I export as a simple object with only the monetary value for visualization. That is the right old String in Java.

  • CQRS. Yes, is Command Query Responsibility Segregation about separating responsibility for writing and reading data and how to do this without DTOs?

In general, adding a layer means decoupling and facilitating maintenance at the expense of adding more classes and complexity, since we also have to think about the conversion operation between these layers. This is the reason, for example, of the existence of MVC, so it is very important to understand that everything is based on impact and trade-offs or what hurts in a given application or situation.

The absence of these layers is very bad, it can result in a Highlander pattern (there can be only one) of which there is a class with all the responsibilities. In the same way, the excess layers become the onion pattern, where the developer cries when passing through each layer.

A more frequent criticism within the DTOs is at work to perform the conversion. The good news is that there are several conversion frameworks, that is, it is not necessary to make the change manually. In this article, we will choose one that is the modelmapper.

The first step is to define the project's dependencies, for example, in Maven:

XML
 




x


 
1
<dependency>
2
    <groupId>org.modelmapper</groupId>
3
    <artifactId>modelmapper</artifactId>
4
    <version>2.3.6</version>
5
</dependency>
6
 
          



To illustrate this concept of DTO, we will create an application using JAX-RS connected to MongoDB, all these thanks to Jakarta EE, using Payara as a server. We manage a user with username, salary, birthday, and list of languages the user can speak. As we will work with MongoDB in Jakarta EE, we will use Jakarta NoSQL.

Java
 




xxxxxxxxxx
1
35


1
import jakarta.nosql.mapping.Column;
2
import jakarta.nosql.mapping.Convert;
3
import jakarta.nosql.mapping.Entity;
4
import jakarta.nosql.mapping.Id;
5
import my.company.infrastructure.MonetaryAmountAttributeConverter;
6
 
          
7
import javax.money.MonetaryAmount;
8
import java.time.LocalDate;
9
import java.util.Collections;
10
import java.util.List;
11
import java.util.Map;
12
import java.util.Objects;
13
 
          
14
@Entity
15
public class User {
16
 
          
17
    @Id
18
    private String nickname;
19
 
          
20
    @Column
21
    @Convert(MonetaryAmountAttributeConverter.class)
22
    private MonetaryAmount salary;
23
 
          
24
    @Column
25
    private List<String> languages;
26
 
          
27
    @Column
28
    private LocalDate birthday;
29
 
          
30
    @Column
31
    private Map<String, String> settings;
32
 
          
33
   //only getter
34
}
35
 
          



In general, it makes no sense to have entities have getters and setters for all attributes; after all, that would be the same as leaving the attribute public directly. Since the focus of our article is not on the DDD or rich models, we will omit the details of that entity. For our DTO, we will have all the fields that the entity has; however, for visualization, our 'MonetaryAmount' will be a 'String', and the anniversary date will follow the same line.

Java
 




xxxxxxxxxx
1
18


1
import java.util.List;
2
import java.util.Map;
3
 
          
4
public class UserDTO {
5
 
          
6
    private String nickname;
7
 
          
8
    private String salary;
9
 
          
10
    private List<String> languages;
11
 
          
12
    private String birthday;
13
 
          
14
    private Map<String, String> settings;
15
 
          
16
    //getter and setter
17
}
18
 
          



The great benefit of the mapper is that we don't have to worry about doing this manually. The only point to note is that particular types, for example, the Money-API's 'MonetaryAmount', will need to create a convert to become 'String' and vice versa.

Java
 




xxxxxxxxxx
1
32


1
import org.modelmapper.AbstractConverter;
2
 
          
3
import javax.money.MonetaryAmount;
4
 
          
5
public class MonetaryAmountStringConverter extends AbstractConverter<MonetaryAmount, String> {
6
 
          
7
    @Override
8
    protected String convert(MonetaryAmount source) {
9
        if (source == null) {
10
            return null;
11
        }
12
        return source.toString();
13
    }
14
}
15
 
          
16
 
          
17
import org.javamoney.moneta.Money;
18
import org.modelmapper.AbstractConverter;
19
 
          
20
import javax.money.MonetaryAmount;
21
 
          
22
public class StringMonetaryAmountConverter extends AbstractConverter<String, MonetaryAmount> {
23
 
          
24
    @Override
25
    protected MonetaryAmount convert(String source) {
26
        if (source == null) {
27
            return null;
28
        }
29
        return Money.parse(source);
30
    }
31
}
32
 
          



The converters are ready; our next step is to instantiate the class that performs the 'ModelMapper' conversion, a big point of using dependency injection is that we can define it as the application level. From now on, the entire application can use the same mapper; for that, it is only necessary to use the annotation 'Inject' as we will see ahead.

Java
 




xxxxxxxxxx
1
35


 
1
import org.modelmapper.ModelMapper;
2
 
          
3
import javax.annotation.PostConstruct;
4
import javax.enterprise.context.ApplicationScoped;
5
import javax.enterprise.inject.Produces;
6
import java.util.function.Supplier;
7
 
          
8
import static org.modelmapper.config.Configuration.AccessLevel.PRIVATE;
9
 
          
10
@ApplicationScoped
11
public class MapperProducer implements Supplier<ModelMapper> {
12
 
          
13
    private ModelMapper mapper;
14
 
          
15
    @PostConstruct
16
    public void init() {
17
        this.mapper = new ModelMapper();
18
        this.mapper.getConfiguration()
19
                .setFieldMatchingEnabled(true)
20
                .setFieldAccessLevel(PRIVATE);
21
        this.mapper.addConverter(new StringMonetaryAmountConverter());
22
        this.mapper.addConverter(new MonetaryAmountStringConverter());
23
        this.mapper.addConverter(new StringLocalDateConverter());
24
        this.mapper.addConverter(new LocalDateStringConverter());
25
        this.mapper.addConverter(new UserDTOConverter());
26
    }
27
 
          
28
 
          
29
    @Override
30
    @Produces
31
    public ModelMapper get() {
32
        return mapper;
33
    }
34
}
35
 
          



One of the significant advantages of using Jakarta NoSQL is its ease of integrating the database. For example, in this article, we will use the concept of a repository from which we will create an interface for which Jakarta NoSQL will take care of this implementation.

Java
 




xxxxxxxxxx
1
11


1
import jakarta.nosql.mapping.Repository;
2
 
          
3
import javax.enterprise.context.ApplicationScoped;
4
import java.util.stream.Stream;
5
 
          
6
@ApplicationScoped
7
public interface UserRepository extends Repository<User, String> {
8
    Stream<User> findAll();
9
}
10
 
          
11
 
          



In the last step, we will make our appeal with JAX-RS. The critical point is that the data exposure will all be done from the DTO; that is, it is possible to carry out any modification within the entity without the customer knowing, thanks to the DTO. As mentioned, the mapper was injected, and the 'map' method greatly facilitates this integration between the DTO and the entity without much code for that.

Java
 




xxxxxxxxxx
1
57


 
1
import javax.inject.Inject;
2
import javax.ws.rs.Consumes;
3
import javax.ws.rs.DELETE;
4
import javax.ws.rs.GET;
5
import javax.ws.rs.POST;
6
import javax.ws.rs.Path;
7
import javax.ws.rs.PathParam;
8
import javax.ws.rs.Produces;
9
import javax.ws.rs.WebApplicationException;
10
import javax.ws.rs.core.MediaType;
11
import javax.ws.rs.core.Response;
12
import java.util.List;
13
import java.util.stream.Collectors;
14
import java.util.stream.Stream;
15
 
          
16
@Path("users")
17
@Consumes(MediaType.APPLICATION_JSON)
18
@Produces(MediaType.APPLICATION_JSON)
19
public class UserResource {
20
 
          
21
    @Inject
22
    private UserRepository repository;
23
 
          
24
    @Inject
25
    private ModelMapper mapper;
26
 
          
27
    @GET
28
    public List<UserDTO> getAll() {
29
        Stream<User> users = repository.findAll();
30
        return users.map(u -> mapper.map(u, UserDTO.class))
31
                .collect(Collectors.toList());
32
    }
33
 
          
34
    @POST
35
    public void insert(UserDTO dto) {
36
        User map = mapper.map(dto, User.class);
37
        repository.save(map);
38
 
          
39
    }
40
 
          
41
    @POST
42
    @Path("id")
43
    public void update(@PathParam("id") String id, UserDTO dto) {
44
        User user = repository.findById(id).orElseThrow(() ->
45
                new WebApplicationException(Response.Status.NOT_FOUND));
46
        User map = mapper.map(dto, User.class);
47
        user.update(map);
48
        repository.save(map);
49
    }
50
 
          
51
    @DELETE
52
    @Path("id")
53
    public void delete(@PathParam("id") String id) {
54
       repository.deleteById(id);
55
    }
56
}
57
 
          



Managing databases, code, and integrations is always hard, even on the cloud. Indeed, the server is still there, and someone needs to watch it, run installations and backups, and maintain health in general. The twelve-factor APP requires a strict separation of config from code. 

Thankfully, Platform.sh provides a PaaS that manages services, such as databases and message queues, with support for several languages, including Java. Everything is built on the concept of Infrastructure as Code (IaC), managing and provisioning services through YAML files.

In previous posts, we mentioned how this is done on Platform.sh primarily with three files:

 1) One to define the services used by the applications (services.yaml).

YAML
 




x


1
mongodb:
2
  type: mongodb:3.6
3
  disk: 1024



2) One to define public routes (routes.yaml).

YAML
 




xxxxxxxxxx
1


 
1
"https://{default}/":
2
  type: upstream
3
  upstream: "app:http"
4
 
          
5
"https://www.{default}/":
6
  type: redirect
7
  to: "https://{default}/"
8
 
          



It's important to stress that the routes are for applications that we want to share publicly. Therefore, if we want the client to only access these microservices, we can remove their access to the conferences, sessions, and speakers from the routes.yaml file.

3) Platform.sh makes configuring single applications and microservices simple with the '.platform.app.yaml' file. Unlike single applications, each microservice application will have its own directory in the project root and its own '.platform.app.yaml' file associated with that single application. Each application will describe its language and the services it will connect to. Since the client application will coordinate each of the microservices of our application, it will specify those connections using the 'relationships' block of its '.platform.app.yaml' file.

YAML
 




xxxxxxxxxx
1
26


 
1
name: app
2
 
          
3
type: "java:11"
4
disk: 1024
5
 
          
6
hooks:
7
    build:  mvn clean package payara-micro:bundle
8
 
          
9
relationships:
10
    mongodb: 'mongodb:mongodb'
11
 
          
12
web:
13
    commands:
14
        start: |
15
          export MONGO_PORT='echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|jq -r ".mongodb[0].port"'
16
          export MONGO_HOST='echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|jq -r ".mongodb[0].host"'
17
          export MONGO_ADDRESS="${MONGO_HOST}:${MONGO_PORT}"
18
          export MONGO_PASSWORD='echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|jq -r ".mongodb[0].password"'
19
          export MONGO_USER='echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|jq -r ".mongodb[0].username"'
20
          export MONGO_DATABASE='echo $PLATFORM_RELATIONSHIPS|base64 -d|json_pp|jq -r ".mongodb[0].path"'
21
          java -jar -Xmx1024m -Ddocument.settings.jakarta.nosql.host=$MONGO_ADDRESS \
22
          -Ddocument.database=$MONGO_DATABASE -Ddocument.settings.jakarta.nosql.user=$MONGO_USER \
23
          -Ddocument.settings.jakarta.nosql.password=$MONGO_PASSWORD \
24
          -Ddocument.settings.mongodb.authentication.source=$MONGO_DATABASE \
25
          target/microprofile-microbundle.jar --port $PORT
26
 
          



In this presentation, we talked about integrating an application with the DTO, in addition to the tools to deliver and map your DTO with your entity in a straightforward way. We also covered the advantages and disadvantages of this layer.

Topics:
architecture ,big data ,clean code ,design ,dto ,jakarta ,jakarta ee ,java ,nosql

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}