Data Transfer Object - DTO
Kaique GarciaTradução: Objeto de Transferência de Dados.
Função: centralizar múltiplos dados em um único objeto a ser compartilhado entre diversas camadas de uma execução de uma aplicação.
Exemplo: Requisição web
Quando você recebe uma requisição web, os dados de entrada acabam passando por várias camadas da aplicação. Por exemplo: Middleware, Controller, Service, Repository e Entity. Em algumas dessas camadas, o DTO sequer faz sentido existir, como entre middlewares e controllers. Mas do Controller para baixo, faz total sentido ter um objeto carregando esses dados ao invés de ficar injetando argumentos em toda transição de camadas.
Como assim?
Se sua requisição tem no máximo três argumentos que não são objetos, os benefícios do DTO não são tão claros assim. Mas quando você lida com uma entrada com dez variáveis, por exemplo, aí é possível ver com clareza, pois, ao chamar uma função, você não precisará mais passar dez variáveis, somente o DTO.
Responsabilidades
Antes de começar a criar um DTO, você precisa saber o que um DTO pode fazer. Por exemplo, além de carregar os dados, ele também pode ser responsável por sanitizá-los ou transformá-los de modo a funcionar como um adaptador. Por exemplo, digamos que você tenha um DTO com o campo "telefone". Ele pode receber o telefone mascarado (ex: "(79) 91234-1234") e, ao ser consultado, já retornar o número sanitizado ("+5579912341234").
Note que não é papel dele validar nada. A validação precisa ter acontecido antes de chegar no DTO! Ele apenas recebe os dados e os transforma quando necessário.
Com isso, o benefício de reduzir o número de argumentos em chamadas de outras funções ou construções de outros objetos, você também centraliza a camada de transformação de dado, fazendo-se valer uma única formatação para todo o fluxo.
No exemplo do telefone, no entanto, é preciso ressaltar que o processo de sanitização do dado não é exclusivo de um DTO. Pode ser que você queira usar a mesma sanitização em outro, então o ideal é que a execução da sanitização seja de outra classe ou função que o DTO possa chamar para sanitizar o dado que carrega.
Reforçando: o DTO pode sanitizar seus dados, mas o processo de sanitização não fica dentro dele - é apenas acionado por ele.
Objetos comuns que funcionam como um DTO mas não são
Ainda falando em requisição web, algumas linguagens e/ou frameworks criam uma entidade "HttpRequest" que representam a requisição HTTP sendo recebida na aplicação. Essa entidade carrega todos os dados da requisição, funcionando de forma parecida como um DTO, mas não é, pois, além de carregar os dados de entrada, também recebe dados do client, dados de autenticação, etc. Além disso, a depender do protocolo de comunicação, a requisição se mantém aberta, recebendo dados continuamente, o que não pode acontecer em um DTO (dados imutáveis, que não podem mudar uma vez que o DTO foi instanciado).
Outro conceito parecido é o de entidades do DDD (Domain-Driven Design), porém ele é a última instância a ser gerada nas camadas de desenvolvimento. O DTO pode ser criado em qualquer parte do projeto e não deve ser diretamente vinculado a regra de negócios. Nesse caso, se estiver na dúvida se precisa criar um DTO ou só repassar uma entidade para outra chamada, reflita: essa entidade já estará persistida na base de dados da aplicação quando acionar as demais partes? Se sim, você pode contar com a entidade. Se não, é melhor criar um DTO. Ao usar explicitamente um DTO, você deixa claro para outros desenvolvedores que aqueles dados podem não estar persistidos num banco, diferente de uma entidade, que pode carregar mais dados do que o fluxo pede.
E falando em "carregar mais dados", é vital que todos os dados do DTO sejam necessários no fluxo onde for inserido. Se tem um campo a mais ou a menos, você precisa criar outro DTO específico para aquele fluxo. Exemplificando: se a requisição para o cadastro de uma receita de bolo tem dois objetos "ingredientes" e "passo-a-passo", você pode inicialmente criar um DTO agregando os dois valores e, nas camadas inferiores onde os dados forem bifurcados, criar outros DTOs contendo só a parte necessária. Essa estratégia pode parecer redundante, mas serve para evitar confusões durante manutenções (acessar uma propriedade que não está populada é um acidente bem comum).
Ah, então eu tenho de ficar instanciando cada DTO em cada camada??
Não! Você lembra que pode transformar os dados? Você pode gerar DTOs a partir de um DTO! Basta criar uma função que faça essa transformação. Ex: "ReciptCreationDTO.toIngredientDTOs()", onde ReciptCreationDTO é o DTO da requisição (exemplo da receita) e IngredientDTO é o DTO que representa um ingrediente (por isso na função tem um "s" no final, indicando que retorna uma lista de DTOs de ingrediente).
Conclusão
É possível viver sem DTO? Sim. O uso de DTO pode gerar mais tempo durante a criação do código? Com certeza. Mas quando você for dar manutenção, você terá menos dúvidas sobre o código e, por já ter o padrão de centralizar algumas coisas, menos lugares para mexer. Lembrando que: se você tem um fluxo com muitos argumentos, isso aqui salva vidas.
E é isso aí, abraço e até mais!