Atualmente, estou criando uma API REST para um projeto e tenho lido artigos e artigos sobre melhores práticas. Muitos parecem ser contra os DTOs e simplesmente expõem o modelo de domínio, enquanto outros parecem pensar que os DTOs (ou Modelos de Usuário ou o que você quiser chamar) são uma má prática. Pessoalmente, pensei que este artigo fazia muito sentido.
No entanto, também entendo as desvantagens dos DTOs com todo o código de mapeamento extra, modelos de domínio que podem ser 100% idênticos aos de seus equivalentes e assim por diante.
Nossa API é criada principalmente para que outros clientes possam consumir dados; no entanto, se fizermos o certo, também gostaríamos de usá-lo para nossa própria GUI da Web, se possível.
A questão é que talvez não desejemos expor todos os dados do domínio para os outros usuários clientes. Muitos dos dados só farão sentido em nosso próprio aplicativo da web. Além disso, talvez não desejemos expor todos os dados sobre um objeto em todos os cenários, especialmente relacionamentos com outros objetos e assim por diante. Por exemplo, se expusermos uma lista de um objeto em particular, não desejaríamos necessariamente expor toda a hierarquia do objeto; para que os filhos do objeto não sejam expostos, mas possam ser descobertos através de links (hateoas).
Como devo resolver esse problema? Eu estava pensando em usar mixins Jackson em nossos modelos de domínio para controlar quais dados seriam expostos em diferentes cenários. Ou devemos apenas usar DTOs o tempo todo - mesmo considerando suas desvantagens e controvérsias?
Respostas:
Por que você deve usar DTOs em sua API REST
DTO significa D ata T ransferência O bject .
Esse padrão foi criado com uma finalidade muito bem definida: transferir dados para interfaces remotas , assim como serviços da web . Esse padrão se encaixa muito bem em uma API REST e os DTOs fornecerão mais flexibilidade a longo prazo.
Os modelos que representam o domínio do seu aplicativo e os modelos que representam os dados manipulados por sua API são (ou pelo menos devem ser) preocupações diferentes e devem ser dissociados . Você não deseja interromper seus clientes de API ao adicionar, remover ou renomear um campo do modelo de domínio do aplicativo.
Enquanto sua camada de serviço opera sobre os modelos de domínio / persistência, seus controladores de API devem operar sobre um conjunto diferente de modelos. À medida que seus modelos de domínio / persistência evoluem para oferecer suporte a novos requisitos de negócios, por exemplo, convém criar novas versões dos modelos de API para suportar essas alterações. Você também pode descontinuar as versões antigas da sua API à medida que novas versões são lançadas. E é perfeitamente possível conseguir quando as coisas são dissociadas.
Apenas para mencionar alguns benefícios da exposição de DTOs em vez de modelos de persistência:
Separar modelos de persistência de modelos de API.
Os DTOs podem ser adaptados às suas necessidades e são ótimos ao expor apenas um conjunto de atributos de suas entidades de persistência. Você não precisará de anotações como
@XmlTransient
e@JsonIgnore
para evitar a serialização de alguns atributos.Ao usar DTOs, você evitará muitas anotações em suas entidades de persistência, ou seja, suas entidades de persistência não serão inchadas com anotações não relacionadas à persistência.
Você terá controle total sobre os atributos que está recebendo ao criar ou atualizar um recurso.
Se você estiver usando o Swagger , poderá usar
@ApiModel
e@ApiModelProperty
anotações para documentar seus modelos de API sem prejudicar suas entidades de persistência.Você pode ter DTOs diferentes para cada versão da sua API.
Você terá mais flexibilidade ao mapear relacionamentos.
Você pode ter DTOs diferentes para diferentes tipos de mídia.
Seus DTOs podem ter uma lista de links para HATEOAS . Esse é o tipo de coisa que não deve ser adicionada aos objetos de persistência. Ao usar o Spring HATEOAS , você pode estender suas classes DTO
RepresentationModel
(anteriormente conhecidas comoResourceSupport
) ou envolvê-las comEntityModel
(anteriormente conhecidas comoResource<T>
).Lidando com o código padrão
Você não precisará mapear suas entidades de persistência para DTOs e vice-versa manualmente . Existem muitas estruturas de mapeamento que você pode usar para fazer isso. Por exemplo, dê uma olhada no MapStruct , que é baseado em anotação e funciona como um Processador de anotação Maven. Funciona bem em aplicativos baseados em CDI e Spring.
Você também pode querer considerar Lombok para gerar getters, setters,
equals()
,hashcode()
etoString()
métodos para você.Relacionado: Para dar um nome melhor às suas aulas de DTO, consulte esta resposta .
fonte
Quando sua API é pública e você precisa oferecer suporte a várias versões, é necessário usar DTOs.
Por outro lado, se é uma API privada e você controla o cliente e o servidor, tendem a pular os DTOs e expor diretamente o modelo de domínio.
fonte
Eu costumo usar DTOs.
Não gosto das desvantagens, mas parece que as outras opções são ainda piores:
A exposição de objetos do domínio pode levar a problemas de segurança e vazamento de dados. As anotações de Jackson podem parecer resolver o problema, mas é muito fácil cometer um erro e expor dados que não devem ser expostos. Ao projetar uma classe de DTO, é muito mais difícil cometer esse erro.
Por outro lado, as desvantagens da abordagem de DTO podem ser reduzidas com coisas como mapeamento de objeto a objeto e Lombok por menos clichê.
fonte
Como você já se declarou, essa é claramente uma questão relacionada à opinião. Eu mesmo sou mais atraído pela abordagem Sem DTOs, simplesmente por causa de todo o código padrão necessário.
Isto é principalmente verdade para o lado da resposta de uma API json / rest. Até escrevi um addon jackson para evitar escrever muitas visualizações / filtros json para esses casos: https://github.com/Antibrumm/jackson-antpathfilter
Por outro lado, os DTOs são bons no lado da entrada de solicitação dessas APIs. Trabalhar diretamente em entidades pode ser bastante difícil, levando em consideração as relações bidirecionais, por exemplo. Além disso, você realmente não deseja permitir que um chamador modifique um atributo "criador", por exemplo. Portanto, você não deve permitir certos campos durante o mapeamento de tais solicitações.
fonte