Os serviços devem sempre retornar DTOs ou também podem retornar modelos de domínio?

174

Estou (re) projetando aplicativos em larga escala, usamos arquitetura multicamada baseada em DDD.

Temos MVC com camada de dados (implementação de repositórios), camada de domínio (definição de modelo de domínio e interfaces - repositórios, serviços, unidade de trabalho), camada de serviço (implementação de serviços). Até o momento, usamos modelos de domínio (principalmente entidades) em todas as camadas e usamos DTOs apenas como modelos de exibição (no controlador, o serviço retorna modelos de domínio e o controlador cria um modelo de exibição, que é passado para a exibição).

Já li inúmeros artigos sobre como usar, não usar, mapear e transmitir DTOs. Entendo que não há resposta definitiva, mas não tenho certeza se está tudo bem ou não retornando modelos de domínio de serviços para controladores. Se eu retornar o modelo de domínio, ele ainda nunca será passado para a visualização, pois o controlador sempre cria um modelo de visualização específico da visualização - nesse caso, parece legítimo. Por outro lado, não parece certo quando o modelo de domínio sai da camada de negócios (camada de serviço). Às vezes, o serviço precisa retornar um objeto de dados que não foi definido no domínio e, em seguida, precisamos adicionar um novo objeto ao domínio que não está mapeado ou criar um objeto POCO (isso é feio, pois alguns serviços retornam modelos de domínio, alguns efetivamente retornar DTOs).

A questão é: se usarmos estritamente os modelos de exibição, não há problema em retornar modelos de domínio até os controladores ou devemos sempre usar DTOs para comunicação com a camada de serviço? Em caso afirmativo, é possível ajustar modelos de domínio com base em quais serviços precisam? (Sinceramente, acho que não, pois os serviços devem consumir o domínio que possui.) Se devemos seguir rigorosamente os DTOs, eles devem ser definidos na camada de serviço? (Acho que sim.) Às vezes, fica claro que devemos usar DTOs (por exemplo, quando o serviço executa muita lógica de negócios e cria novos objetos), outras vezes, fica claro que devemos usar apenas modelos de domínio (por exemplo, quando o serviço Membership retorna Usuário anêmico ( s) - parece que não faria muito sentido criar DTO igual ao modelo de domínio) - mas prefiro consistência e boas práticas.

Artigo Domínio vs DTO vs ViewModel - Como e quando usá-los? (e também alguns outros artigos) é muito parecido com o meu problema, mas não responde a essas perguntas. Artigo Devo implementar DTOs no padrão de repositório com EF? também é semelhante, mas não lida com DDD.

Isenção de responsabilidade: não pretendo usar nenhum padrão de design apenas porque existe e é sofisticado; por outro lado, gostaria de usar bons padrões e práticas de design também porque ajuda a projetar o aplicativo como um todo, ajuda na separação de preocupações, mesmo que o uso de determinado padrão não seja "necessário", pelo menos no momento.

Como sempre, obrigado.

Robert Goldwein
fonte
28
Para aqueles que votam em favor - por favor, gostaria de explicar por que deseja encerrar esta questão com base em opiniões?
Robert Goldwein
20
@Aron "A Revisão de Código é um site de perguntas e respostas para compartilhar código de projetos nos quais você está trabalhando para revisão por pares." - minha pergunta não é sobre código, então seria fora de tópico; SO: "Concentre-se nas perguntas sobre um problema real que você enfrentou. Inclua detalhes sobre o que você tentou e exatamente o que está tentando fazer." - Eu tenho um problema específico de especialista, que tentei resolver. Você poderia, por favor, ser mais específico, o que há de errado com essa pergunta, já que muitas perguntas aqui são sobre arquitetura e essas questões são aparentemente boas, para que eu possa evitar mais mal-entendidos?
Robert Goldwein
7
Obrigado por fazer essa pergunta. Você me fez um favor e tornou minha vida muito mais simples e feliz, obrigada.
Loa
9
@RobertGoldwein, não se importe com a SO Close Mafia, sua pergunta é legítima.
Hyankov #
3
Grande obrigado por fazer esta pergunta
Salman

Respostas:

177

não parece certo quando o modelo de domínio sai da camada de negócios (camada de serviço)

Faz você se sentir como se estivesse puxando as entranhas, certo? De acordo com Martin Fowler: a Camada de Serviço define os limites do aplicativo, encapsula o domínio. Em outras palavras, protege o domínio.

Às vezes, o serviço precisa retornar um objeto de dados que não foi definido no domínio

Você pode fornecer um exemplo desse objeto de dados?

Se devemos seguir rigorosamente os DTOs, eles devem ser definidos na camada de serviço?

Sim, porque a resposta faz parte da sua camada de serviço. Se for definido "em outro lugar", a camada de serviço precisará fazer referência a "outro lugar", adicionando uma nova camada à sua lasanha.

está correto retornar modelos de domínio até os controladores ou devemos sempre usar DTOs para comunicação com a camada de serviço?

Um DTO é um objeto de resposta / solicitação, faz sentido se você o usar para comunicação. Se você usar modelos de domínio em sua camada de apresentação (MVC-Controllers / View, WebForms, ConsoleApp), a camada de apresentação será fortemente acoplada ao seu domínio, qualquer alteração no domínio exigirá que você altere seus controladores.

parece que não faria muito sentido criar DTO igual ao modelo de domínio)

Essa é uma das desvantagens do DTO para novos olhos. No momento, você está pensando na duplicação de código , mas à medida que o projeto se expande, isso faria muito mais sentido, especialmente em um ambiente de equipe em que equipes diferentes são atribuídas a diferentes camadas.

O DTO pode adicionar complexidade adicional ao seu aplicativo, mas suas camadas também. O DTO é um recurso caro do seu sistema, eles não são de graça.

Por que usar um DTO

Este artigo fornece vantagens e desvantagens do uso de um DTO, http://guntherpopp.blogspot.com/2010/09/to-dto-or-not-to-dto.html

Resumo da seguinte forma:

Quando usar

  • Para grandes projetos.
  • A vida útil do projeto é de 10 anos ou mais.
  • Aplicação estratégica e de missão crítica.
  • Equipes grandes (mais de 5)
  • Os desenvolvedores são distribuídos geograficamente.
  • O domínio e a apresentação são diferentes.
  • Reduza as trocas de dados indiretos (o objetivo original do DTO)

Quando não usar

  • Projeto de tamanho pequeno a médio (máximo de 5 membros)
  • A vida útil do projeto é de aproximadamente 2 anos.
  • Nenhuma equipe separada para GUI, back-end etc.

Argumentos contra o DTO

Argumentos com DTO

  • Sem o DTO, a apresentação e o domínio estão fortemente acoplados. (Isso é bom para pequenos projetos.)
  • Estabilidade da interface / API
  • Pode fornecer otimização para a camada de apresentação retornando um DTO contendo apenas os atributos absolutamente necessários. Usando linq-projection , você não precisa puxar uma entidade inteira.
  • Para reduzir o custo de desenvolvimento, use ferramentas de geração de código
Yorro
fonte
3
Olá, obrigado pela resposta, é um resumo muito bom e também obrigado pelos links. Minha frase "Às vezes, o serviço precisa retornar um objeto de dados que não foi definido no domínio" foi mal escolhida, significa que o serviço combina várias DOs de um repositório (por exemplo, atributos) e produz um POCO como uma composição desses atributos (com base em na lógica de negócios). Mais uma vez, obrigado pela resposta muito boa.
Robert Goldwein
1
Uma consideração importante de desempenho é exatamente como esses modelos de domínio ou DTOs são retornados de seu serviço. Com o EF, se você realizar uma consulta para retornar uma coleção concreta de modelos de domínio (com um .ToArray () ou ToList (), por exemplo), selecione todas as colunas para preencher os objetos realizados. Se você projetar o DTO na consulta, o EF é inteligente o suficiente para selecionar apenas as colunas necessárias para preencher seu DTO, o que pode significar menos dados a serem transferidos em alguns casos.
bufar
10
Você pode mapear seus objetos 'à mão'. Eu sei, chato coisas, mas leva 2-3 min por modelo e há sempre uma possibilidade de trazer um monte de problemas quando você usar um monte de reflexão (AutoMapper etc.)
Razvan Dumitru
1
obrigado por responder de forma simples e com esse conteúdo. Você me fez um favor e tornou minha vida muito mais simples e feliz, obrigada.
Loa
1
Tínhamos um projeto de 10 milhões cancelado porque era lento ... Por que foi lento? Transferindo o objeto DTO em todo o lugar usando a re-seleção. Seja cuidadoso. O Automapper também usa reflexão.
RayLoveless
11

Parece que seu aplicativo é grande e complexo o suficiente, pois você decidiu passar pela abordagem DDD. Não retorne suas entidades poco ou as chamadas entidades de domínio e objetos de valor na sua camada de serviço. Se você quiser fazer isso, exclua sua camada de serviço porque não precisa mais dela! Os objetos Exibir modelo ou transferência de dados devem estar na camada Serviço, porque devem ser mapeados para membros do modelo de domínio e vice-versa. Então, por que você precisa ter DTO? Em aplicativos complexos com muitos cenários, você deve separar as preocupações das exibições de domínio e de apresentação, um modelo de domínio pode ser dividido em vários DTO e também vários modelos de Domínio podem ser recolhidos em um DTO. Portanto, é melhor criar seu DTO na arquitetura em camadas, mesmo que seja o mesmo que seu modelo.

Devemos sempre usar DTOs para comunicação com a camada de serviço? Sim, você deve retornar o DTO por sua camada de serviço enquanto conversa com seu repositório na camada de serviço com membros do modelo de domínio e mapeá-los para o DTO e retornar ao controlador MVC e vice-versa.

É possível ajustar modelos de domínio com base em quais serviços precisam? Um serviço apenas fala com métodos e serviços de domínio e repositório, você deve resolver os negócios em seu domínio com base em suas necessidades e não é tarefa do serviço informar ao domínio o que é necessário.

Se devemos seguir rigorosamente os DTOs, eles devem ser definidos na camada de serviço? Sim, tente ter o DTO ou o ViewModel apenas em serviço mais tarde, porque eles devem ser mapeados para membros do domínio na camada de serviço e não é uma boa ideia colocar o DTO nos controladores do seu aplicativo (tente usar o padrão de Resposta de Solicitação na camada de Serviço). !

Ehsan
fonte
1
me desculpe por isso! você pode vê-lo aqui ehsanghanbari.com/blog/Post/7/…
Ehsan
10

Na minha experiência, você deve fazer o que é prático. "O melhor design é o design mais simples que funciona" - Einstein. Com isso é mente ...

se usarmos estritamente os modelos de exibição, está correto retornar modelos de domínio até os controladores ou devemos sempre usar DTOs para comunicação com a camada de serviço?

Absolutamente tudo bem! Se você possui Entidades de Domínio, DTOs e Modelos de Visualização, incluindo tabelas de banco de dados, todos os campos do aplicativo são repetidos em 4 locais. Trabalhei em grandes projetos nos quais as Entidades de Domínio e os Modelos de Exibição funcionaram bem. A única exceção é se o aplicativo for distribuído e a camada de serviço residir em outro servidor. Nesse caso, os DTOs deverão enviar através da conexão por motivos de serialização.

Em caso afirmativo, é possível ajustar modelos de domínio com base em quais serviços precisam? (Francamente, acho que não, pois os serviços devem consumir o domínio que possui.)

Geralmente, eu concordo e digo não, porque o modelo de domínio é geralmente um reflexo da lógica de negócios e geralmente não é moldado pelo consumidor dessa lógica.

Se devemos seguir rigorosamente os DTOs, eles devem ser definidos na camada de serviço? (Acho que sim.)

Se você decidir usá-los, eu concordo e digo que sim, a camada de Serviço é o lugar perfeito, pois retorna os DTOs no final do dia.

Boa sorte!

Justin Ricketts
fonte
8

Estou atrasado para esta festa, mas essa é uma pergunta tão comum e importante que me senti obrigado a responder.

Por "serviços" você quer dizer a "Camada de Aplicação" descrita por Evan no livro azul ? Suponho que sim, nesse caso a resposta é que eles não devem retornar DTOs. Sugiro a leitura do capítulo 4 do livro azul, intitulado "Isolando o domínio".

Nesse capítulo, Evans diz o seguinte sobre as camadas:

Particione um programa complexo em camadas. Desenvolva um design dentro de cada camada que seja coeso e que dependa apenas das camadas abaixo.

Há uma boa razão para isso. Se você usar o conceito de ordem parcial como uma medida da complexidade do software , ter uma camada depende da camada acima dela aumenta a complexidade, o que diminui a capacidade de manutenção.

Aplicando isso à sua pergunta, os DTOs são realmente um adaptador que é uma preocupação da camada Interface do Usuário / Apresentação. Lembre-se de que a comunicação remota / entre processos é exatamente o objetivo de um DTO (vale a pena notar que, nesse post, Fowler também argumenta que os DTOs fazem parte de uma camada de serviço, embora ele não esteja necessariamente falando a linguagem DDD).

Se sua camada de aplicativo depende desses DTOs, depende de uma camada acima de si mesma e sua complexidade aumenta. Posso garantir que isso aumentará a dificuldade de manutenção do seu software.

Por exemplo, e se o seu sistema fizer interface com vários outros sistemas ou tipos de clientes, cada um exigindo seu próprio DTO? Como você sabe qual DTO deve retornar um método do seu serviço de aplicativo? Como você resolveria esse problema se o seu idioma de escolha não permitir sobrecarregar um método (método de serviço, neste caso) com base no tipo de retorno? E mesmo se você descobrir uma maneira, por que violar sua camada de aplicativos para dar suporte a uma preocupação da camada de apresentação?

Em termos práticos, este é um passo em frente que terminará em uma arquitetura de espaguete. Eu já vi esse tipo de devolução e seus resultados em minha própria experiência.

Onde trabalho atualmente, os serviços em nossa Camada de Aplicativos retornam objetos de domínio. Não consideramos isso um problema, pois a camada Interface (por exemplo, UI / Apresentação) depende da camada Domínio, que está abaixo dela. Além disso, essa dependência é minimizada para um tipo de dependência "apenas referência" porque:

a) a camada de interface só pode acessar esses objetos de domínio como valores de retorno somente leitura obtidos por chamadas para a camada de aplicativo

b) os métodos nos serviços da Camada de Aplicação aceitam como entrada apenas entradas "brutas" (valores de dados) ou parâmetros de objeto (para reduzir a contagem de parâmetros, quando necessário) definidos nessa camada. Especificamente, os serviços de aplicativos nunca aceitam objetos de Domínio como entrada.

A camada de interface usa técnicas de mapeamento definidas na própria camada de interface para mapear dos objetos do domínio para os DTOs. Novamente, isso mantém os DTOs focados em adaptadores controlados pela Camada de Interface.

BitMask777
fonte
1
Pergunta rápida. Atualmente, estou analisando o que retornar da minha camada de aplicativo. Retornar entidades de domínio da camada de aplicativo parece errado. Eu realmente quero vazar o domínio para o "fora"? Então, eu estava contemplando DTOs da camada de aplicação. Mas isso acrescenta outro modelo. Na sua resposta, você disse que retorna modelos de domínio como "valores de retorno somente leitura". Como você faz isso? Ou seja, como você os faz somente leitura?
22618 Michael Jacksons
Acho que vou adotar sua posição. Os serviços de aplicativo retornam modelos de domínio. As camadas do adaptador de porta (REST, apresentação etc.) as convertem em seu próprio modelo (exibir modelo ou representações). Adicionar um modelo DTO entre os aplicativos e os adaptadores de porta parece um exagero. Os modelos de domínio retornados ainda aderem ao DIP, e a lógica do domínio permanece dentro do contexto limitado (não necessariamente dentro do limite do aplicativo. Mas isso parece ser um bom compromisso).
22618 Michael Jacksons
@ MichaelAndrews, feliz em saber que minha resposta ajudou. Re: sua pergunta sobre os objetos retornados serem somente leitura, os objetos em si não são verdadeiramente somente leitura (ou seja, imutáveis). O que quero dizer é que isso não acontece na prática (pelo menos na minha experiência). Para atualizar um objeto de domínio, a Camada de interface teria que: a) referenciar o repositório do objeto de domínio ou b) chamar novamente a Camada de aplicativos para atualizar o objeto que acabou de receber. Qualquer uma dessas são violações tão claras das boas práticas de DDD que eu acho que elas são auto-aplicadas. Sinta-se livre para votar a resposta se você quiser.
BomMask777
Esta resposta é muito intuitiva para mim por várias razões. Primeiro, podemos reutilizar a mesma camada de aplicativo para várias interfaces de usuário (APIs, controladores) e cada uma pode transformar o modelo como entender. Segundo, se transformarmos o modelo em DTO no App. Camada, isso significaria que o DTO está definido no aplicativo. Camada, que por sua vez significa que o DTO agora faz parte do nosso contexto limitado (não necessariamente domínio!) - isso parece errado.
Robotron
1
Eu estava prestes a fazer uma pergunta de acompanhamento e vi que você já a respondeu: "os serviços de aplicativos nunca aceitam objetos de Domínio como entrada". Eu faria +1 novamente se pudesse.
Robotron
5

Cheguei atrasado à festa, mas estou enfrentando exatamente o mesmo tipo de arquitetura e estou inclinado a “apenas DTOs de serviço”. Isso ocorre principalmente porque eu decidi usar apenas objetos / agregados de domínio para manter a validade dentro do objeto, portanto, somente ao atualizar, criar ou excluir. Quando estamos consultando dados, usamos o EF apenas como repositório e mapeia o resultado para DTOs. Isso nos torna livres para otimizar as consultas de leitura e não adaptá-las aos objetos de negócios, geralmente usando as funções do banco de dados à medida que são rápidas.

Cada método de serviço define seu próprio contrato e, portanto, é mais fácil de manter com o tempo. Eu espero.

Niklas Wulff
fonte
1
Depois de anos, chegamos à mesma conclusão, exatamente pelas razões que você mencionou aqui.
Robert Goldwein 18/11/19
@RobertGoldwein Isso é ótimo! Sinto-me mais confiante na minha decisão agora. :-)
Niklas Wulff
@ NiklasWulff: então, os Dtos em questão agora fazem parte do contrato da Camada de Aplicação, ou seja, fazem parte do núcleo / domínio. E os tipos de retorno da API da Web ? Você expõe os Dtos mencionados em sua resposta ou possui modelos de exibição dedicados definidos na camada de API da Web? Ou, de outra forma: você mapeia os Dtos para exibir modelos?
Robotron
1
@ Robotron Não temos API Web, usamos MVC. Então, sim, mapeamos o dto: s para diferentes modelos de exibição. Freqüentemente, um modelo de exibição contém muitas outras coisas para exibir a página da Web, portanto, os dados do dto: s compõem apenas uma parte do modelo de exibição
Niklas Wulff
4

Até o momento, usamos modelos de domínio (principalmente entidades) em todas as camadas e usamos DTOs apenas como modelos de exibição (no controlador, o serviço retorna modelos de domínio e o controlador cria um modelo de exibição, que é passado para a exibição).

Como o Modelo de Domínio fornece terminologia ( Linguagem Ubíqua ) para toda a sua aplicação, é melhor usar amplamente o Modelo de Domínio.

O único motivo para usar os ViewModels / DTOs é uma implementação do padrão MVC em seu aplicativo para separar View(qualquer tipo de camada de apresentação) e Model(Modelo de Domínio). Nesse caso, sua apresentação e modelo de domínio são fracamente acoplados.

Às vezes, o serviço precisa retornar um objeto de dados que não foi definido no domínio e, em seguida, precisamos adicionar um novo objeto ao domínio que não está mapeado ou criar um objeto POCO (isso é feio, pois alguns serviços retornam modelos de domínio, alguns efetivamente retornar DTOs).

Suponho que você fale sobre os serviços de aplicativos / negócios / lógica de domínio.

Sugiro que você retorne entidades de domínio quando puder. Se for necessário retornar informações adicionais, é aceitável retornar o DTO que contém várias entidades de domínio.

Às vezes, as pessoas que usam estruturas de terceira parte, que geram proxies sobre entidades de domínio, enfrentam dificuldades para expor entidades de domínio de seus serviços, mas isso é apenas uma questão de uso incorreto.

A questão é: se usarmos estritamente os modelos de exibição, não há problema em retornar modelos de domínio até os controladores ou devemos sempre usar DTOs para comunicação com a camada de serviço?

Eu diria que é suficiente retornar entidades de domínio em 99,9% dos casos.

Para simplificar a criação de DTOs e mapear suas entidades de domínio para eles, você pode usar o AutoMapper .

Ilya Palkin
fonte
4

Se você retornar parte do seu modelo de domínio, ele se tornará parte de um contrato. É difícil mudar um contrato, pois as coisas fora do seu contexto dependem dele. Dessa forma, você tornaria difícil alterar parte do seu modelo de domínio.

Um aspecto muito importante de um modelo de domínio é que é fácil mudar. Isso nos torna flexíveis aos requisitos de mudança do domínio.

Timo
fonte
2

Eu sugeriria analisar essas duas perguntas:

  1. Suas camadas superiores (ou seja, exibir e visualizar modelos / controladores) estão consumindo os dados de uma maneira diferente do que a camada de domínio expõe? Se houver muito mapeamento sendo feito ou mesmo lógica, sugiro revisitar seu design: provavelmente deve estar mais perto de como os dados são realmente usados.

  2. Qual a probabilidade de você alterar profundamente suas camadas superiores? (por exemplo, trocando o ASP.NET por WPF). Se isso for altamente diferente e sua arquitetura não for muito complexa, é melhor expor o maior número possível de entidades de domínio.

Receio que seja um tópico bastante amplo e que realmente se refira à complexidade do seu sistema e aos seus requisitos.

jnovo
fonte
No nosso caso, a camada superior certamente não muda. Em alguns casos, o serviço retorna um objeto POCO bastante exclusivo (construído a partir de mais domínios - por exemplo, usuário e arquivos que ele possui); em alguns casos, um serviço retorna simplesmente um modelo de domínio - por exemplo, o resultado de "FindUserByEmail () deve retornar o modelo de domínio do usuário - e eis a minha preocupação, às vezes nosso (s) serviço (s) retorna (s) o modelo de domínio, às vezes o novo DTO - e eu não gosto dessa inconsistência, leio o máximo de artigos que pude e a maioria parece concordar que, embora o modelo de domínio de mapeamento <-> DTO é de 1: 1, modelo de domínio não deve deixar camada de serviço - por isso estou rasgado.
Robert Goldwein
Nesse cenário, e desde que você possa suportar o esforço extra de desenvolvimento, eu também utilizaria o mapeamento para que suas camadas sejam mais consistentes.
jnovo
1

Na minha experiência, a menos que você esteja usando um padrão de interface do usuário OO (como objetos nus), expor os objetos de domínio à interface do usuário é uma má idéia. Isso porque, conforme o aplicativo cresce, as necessidades da interface do usuário são alteradas e forçam seus objetos a acomodar essas alterações. Você acaba servindo 2 mestres: UI e DOMAIN, o que é uma experiência muito dolorosa. Acredite, você não quer estar lá. O modelo de interface do usuário tem a função de se comunicar com o usuário, o modelo DOMAIN para manter as regras de negócios e os modelos de persistência lidam com o armazenamento de dados de maneira eficaz. Todos eles atendem a diferentes necessidades do aplicativo. Estou no meio de escrever uma postagem no blog sobre isso, e a adicionarei quando terminar.

max_cervantes
fonte