¿Deberían los servicios siempre devolver DTO, o también pueden devolver modelos de dominio?

175

Estoy (re) diseñando una aplicación a gran escala, utilizamos una arquitectura multicapa basada en DDD.

Tenemos MVC con capa de datos (implementación de repositorios), capa de dominio (definición de modelo de dominio e interfaces - repositorios, servicios, unidad de trabajo), capa de servicio (implementación de servicios). Hasta ahora, usamos modelos de dominio (en su mayoría entidades) en todas las capas, y usamos DTO solo como modelos de vista (en el controlador, el servicio devuelve modelos de dominio y el controlador crea el modelo de vista, que se pasa a la vista).

He leído innumerables artículos sobre el uso, no uso, mapeo y paso de DTO. Entiendo que no hay una respuesta definitiva, pero no estoy seguro de si está bien o no devolver los modelos de dominio de los servicios a los controladores. Si devuelvo el modelo de dominio, todavía nunca se pasa a la vista, ya que el controlador siempre crea un modelo de vista específico de la vista; en este caso, parece legítimo. Por otro lado, no se siente bien cuando el modelo de dominio abandona la capa empresarial (capa de servicio). A veces, el servicio debe devolver el objeto de datos que no se definió en el dominio y luego tenemos que agregar un nuevo objeto al dominio que no está asignado o crear un objeto POCO (esto es feo, ya que algunos servicios devuelven modelos de dominio, algunos efectivamente devolver DTO).

La pregunta es: si usamos estrictamente modelos de vista, ¿está bien devolver los modelos de dominio a los controladores, o deberíamos usar siempre DTO para la comunicación con la capa de servicio? Si es así, ¿está bien ajustar los modelos de dominio en función de qué servicios necesitan? (Francamente, no lo creo, ya que los servicios deberían consumir lo que tiene el dominio). Si debemos apegarnos estrictamente a los DTO, ¿deberían definirse en la capa de servicio? (Creo que sí). A veces está claro que deberíamos usar DTO (por ejemplo, cuando el servicio realiza mucha lógica de negocios y crea nuevos objetos), a veces está claro que deberíamos usar solo modelos de dominio (por ejemplo, cuando el servicio de Membresía devuelve Usuario anémico ( s), parece que no tendría mucho sentido crear DTO que sea lo mismo que el modelo de dominio), pero prefiero la coherencia y las buenas prácticas.

Article Domain vs DTO vs ViewModel - ¿Cómo y cuándo usarlos? (y también algunos otros artículos) es muy similar a mi problema, pero no responde a esta (s) pregunta (s). Artículo ¿Debo implementar DTO en el patrón de repositorio con EF? también es similar, pero no trata con DDD.

Descargo de responsabilidad: no tengo la intención de usar ningún patrón de diseño solo porque existe y es elegante, por otro lado, me gustaría usar buenos patrones de diseño y prácticas también porque ayuda a diseñar la aplicación en su conjunto, ayuda con la separación De preocupación, incluso si usar un patrón particular no es "necesario", al menos por el momento.

Como siempre, gracias.

Robert Goldwein
fuente
28
Para aquellos tipos que votan a favor, ¿podrían explicar por qué quieren cerrar esta pregunta según su opinión?
Robert Goldwein
20
@Aron "Code Review es un sitio de preguntas y respuestas para compartir código de proyectos en los que está trabajando para la revisión por pares". - mi pregunta no es sobre el código, por lo que estaría fuera de tema allí; SO: "Céntrese en las preguntas sobre un problema real que ha enfrentado. Incluya detalles sobre lo que ha intentado y exactamente lo que está tratando de hacer". - Tengo un problema experto específico, que intenté resolver. ¿Podría por favor ser más específico de lo que está mal con esta pregunta, ya que muchas preguntas aquí son sobre arquitectura y esas preguntas aparentemente están bien, para que pueda evitar más malentendidos?
Robert Goldwein
77
Gracias por hacer esa pregunta. Me hiciste un favor e hiciste mi vida mucho más simple y feliz, gracias.
Loa
9
@RobertGoldwein, no te preocupes por SO Close Mafia, tu pregunta es legítima.
hyankov
3
Muchas gracias por hacer esta pregunta
salman

Respuestas:

177

no se siente bien cuando el modelo de dominio abandona la capa empresarial (capa de servicio)

Te hace sentir como si estuvieras sacando las tripas, ¿verdad? Según Martin Fowler: la capa de servicio define los límites de la aplicación, encapsula el dominio. En otras palabras, protege el dominio.

A veces, el servicio necesita devolver el objeto de datos que no se definió en el dominio

¿Puede proporcionar un ejemplo de este objeto de datos?

Si deberíamos apegarnos estrictamente a los DTO, ¿deberían definirse en la capa de servicio?

Sí, porque la respuesta es parte de su capa de servicio. Si se define "en otro lugar", la capa de servicio debe hacer referencia a ese "otro lugar", agregando una nueva capa a su lasaña.

¿está bien devolver los modelos de dominio a los controladores, o deberíamos usar siempre DTO para la comunicación con la capa de servicio?

Un DTO es un objeto de respuesta / solicitud, tiene sentido si lo usa para la comunicación. Si usa modelos de dominio en su capa de presentación (MVC-Controllers / View, WebForms, ConsoleApp), entonces la capa de presentación está estrechamente acoplada a su dominio, cualquier cambio en el dominio requiere que cambie sus controladores.

parece que no tendría mucho sentido crear DTO que sea lo mismo que el modelo de dominio)

Esta es una de las desventajas de DTO para nuevos ojos. Ahora mismo estas pensando duplicación de código , pero a medida que su proyecto se expanda, tendría mucho más sentido, especialmente en un entorno de equipo donde se asignan diferentes equipos a diferentes capas.

DTO puede agregar complejidad adicional a su aplicación, pero también lo son sus capas. DTO es una característica costosa de su sistema, no son gratis.

¿Por qué usar un DTO?

Este artículo proporciona ventajas y desventajas de usar un DTO, http://guntherpopp.blogspot.com/2010/09/to-dto-or-not-to-dto.html

Resumen de la siguiente manera:

Cuándo usar

  • Para grandes proyectos.
  • La vida útil del proyecto es de 10 años o más.
  • Aplicación estratégica de misión crítica.
  • Grandes equipos (más de 5)
  • Los desarrolladores se distribuyen geográficamente.
  • El dominio y la presentación son diferentes.
  • Reduzca los intercambios de datos generales (el propósito original de DTO)

Cuando no usar

  • Proyecto pequeño a mediano (máximo 5 miembros)
  • La vida útil del proyecto es de aproximadamente 2 años.
  • No hay un equipo separado para GUI, backend, etc.

Argumentos contra DTO

Argumentos Con DTO

  • Sin DTO, la presentación y el dominio están estrechamente acoplados. (Esto está bien para pequeños proyectos).
  • Estabilidad de interfaz / API
  • Puede proporcionar optimización para la capa de presentación al devolver un DTO que contenga solo aquellos atributos que son absolutamente necesarios. Usando linq-projection , no tiene que extraer una entidad completa.
  • Para reducir el costo de desarrollo, use herramientas generadoras de código
Yorro
fuente
3
Hola, gracias por tu respuesta, es un muy buen resumen y también gracias por los enlaces. Mi oración "A veces el servicio necesita devolver un objeto de datos que no estaba definido en el dominio" fue mal elegida, significa que el servicio combina varias DO de un repositorio (por ejemplo, atributos) y produce un POCO como composición de estos atributos (basado en lógica de negocios). Nuevamente, gracias por una respuesta realmente agradable.
Robert Goldwein
1
Una consideración importante del rendimiento es cómo esos modelos de dominio o DTO se devuelven de su servicio. Con EF, si realiza una consulta para devolver una colección concreta de modelos de dominio (con .ToArray () o ToList (), por ejemplo), selecciona todas las columnas para llenar los objetos realizados. Si, en cambio, proyecta el DTO en la consulta, EF es lo suficientemente inteligente como para seleccionar solo las columnas necesarias para completar su DTO, que puede ser significativamente menos datos para transferir en algunos casos.
resopló el
10
Puede mapear sus objetos 'a mano'. Lo sé, cosas aburridas, pero toma 2-3 minutos por modelo y siempre existe la posibilidad de traer muchos problemas cuando usas mucha reflexión (AutoMapper, etc.)
Razvan Dumitru
1
gracias por responder tan simple y con tal contenido. Me hiciste un favor e hiciste mi vida mucho más simple y feliz, gracias.
Loa
1
Tuvimos que cancelar un proyecto de 10 millones porque era lento ... ¿Por qué fue lento? Transferir el objeto DTO por todo el lugar usando refelección. Ten cuidado. Automapper también usa la reflexión.
RayLoveless
11

Parece que su aplicación es lo suficientemente grande y compleja, ya que ha decidido adoptar el enfoque DDD. No devuelva sus entidades poco o las llamadas entidades de dominio y objetos de valor en su capa de servicio. Si desea hacer esto, elimine su capa de servicio porque ya no la necesita. Los objetos Ver modelo o transferencia de datos deben vivir en la capa de servicio porque deben asignarse a miembros del modelo de dominio y viceversa. Entonces, ¿por qué necesitas tener DTO? En una aplicación compleja con muchos escenarios, debe separar las preocupaciones del dominio y sus vistas de presentación, un modelo de dominio podría dividirse en varios DTO y también varios modelos de Dominio podrían colapsarse en un DTO. Por lo tanto, es mejor crear su DTO en una arquitectura en capas, incluso si fuera el mismo que su modelo.

¿Deberíamos usar siempre DTO para la comunicación con la capa de servicio? Sí, debe devolver DTO por su capa de servicio, ya que ha hablado con su repositorio en la capa de servicio con los miembros del modelo de dominio y asignarlos a DTO y regresar al controlador MVC y viceversa.

¿Está bien ajustar los modelos de dominio en función de los servicios que necesitan? Un servicio solo habla con el repositorio y los métodos de dominio y los servicios de dominio, debe resolver el negocio en su dominio en función de sus necesidades y no es la tarea del servicio decirle al dominio lo que se necesita.

Si deberíamos apegarnos estrictamente a los DTO, ¿deberían definirse en la capa de servicio? Sí, intente tener DTO o ViewModel solo en el servicio más adelante porque deberían asignarse a los miembros del dominio en la capa de servicio y no es una buena idea colocar DTO en los controladores de su aplicación (intente usar el patrón Solicitar respuesta en su capa de Servicio), saludos !

Ehsan
fuente
1
¡Lo siento por eso! puedes verlo aquí ehsanghanbari.com/blog/Post/7/…
Ehsan
10

En mi experiencia, deberías hacer lo que sea práctico. "El mejor diseño es el diseño más simple que funciona" - Einstein. Con eso es mente ...

si usamos estrictamente modelos de vista, ¿está bien devolver los modelos de dominio a los controladores, o deberíamos usar siempre DTO para la comunicación con la capa de servicio?

¡Absolutamente está bien! Si tiene entidades de dominio, DTO y modelos de vista, incluidas las tablas de base de datos, tiene todos los campos de la aplicación repetidos en 4 lugares. He trabajado en grandes proyectos donde las Entidades de dominio y los Modelos de vista funcionaron bien. La única excepción a esto es si la aplicación se distribuye y la capa de servicio reside en otro servidor, en cuyo caso se requiere que los DTO envíen a través del cable por razones de serialización.

Si es así, ¿está bien ajustar los modelos de dominio en función de qué servicios necesitan? (Francamente, no lo creo, ya que los servicios deberían consumir lo que tiene el dominio).

En general, estaría de acuerdo y diría que no, porque el modelo de Dominio generalmente es un reflejo de la lógica de negocios y, por lo general, el consumidor no lo configura.

Si deberíamos apegarnos estrictamente a los DTO, ¿deberían definirse en la capa de servicio? (Creo que sí.)

Si decide usarlos, estoy de acuerdo y le digo que sí, la capa de Servicio es el lugar perfecto, ya que devuelve los DTO al final del día.

¡Buena suerte!

Justin Ricketts
fuente
8

Llego tarde a esta fiesta, pero esta es una pregunta tan común e importante que me sentí obligado a responder.

¿Por "servicios" se refiere a la "Capa de aplicación" descrita por Evan en el libro azul ? Voy a asumir que lo hace, en cuyo caso la respuesta es que deben no volver dtos. Sugiero leer el capítulo 4 del libro azul, titulado "Aislar el dominio".

En ese capítulo, Evans dice lo siguiente sobre las capas:

Particionar un programa complejo en capas. Desarrolle un diseño dentro de cada capa que sea coherente y que solo dependa de las capas siguientes.

Hay una buena razón para esto. Si usa el concepto de orden parcial como una medida de la complejidad del software , tener una capa que dependa de una capa superior aumenta la complejidad, lo que disminuye la capacidad de mantenimiento.

Aplicando esto a su pregunta, los DTO son realmente un adaptador que es una preocupación de la capa de interfaz de usuario / presentación. Recuerde que la comunicación remota / entre procesos es exactamente el propósito de un DTO (vale la pena señalar que en esa publicación Fowler también argumenta en contra de que los DTO sean parte de una capa de servicio, aunque no necesariamente habla el lenguaje DDD).

Si su capa de aplicación depende de esos DTO, depende de una capa por encima de sí misma y su complejidad aumenta. Puedo garantizar que esto aumentará la dificultad de mantener su software.

Por ejemplo, ¿qué sucede si su sistema interactúa con varios otros sistemas o tipos de clientes, cada uno de los cuales requiere su propio DTO? ¿Cómo sabe qué DTO debe devolver un método de su servicio de aplicación? ¿Cómo resolvería ese problema si su idioma de elección no permite sobrecargar un método (método de servicio, en este caso) basado en el tipo de retorno? E incluso si encuentra una manera, ¿por qué violar su capa de aplicación para admitir una preocupación de la capa de presentación?

En términos prácticos, este es un paso por un camino que terminará en una arquitectura de espagueti. He visto este tipo de devolución y sus resultados en mi propia experiencia.

Donde actualmente trabajo, los servicios en nuestra capa de aplicación devuelven objetos de dominio. No consideramos que esto sea un problema ya que la capa de interfaz (es decir, UI / Presentación) depende de la capa de dominio, que está debajo de ella. Además, esta dependencia se minimiza a un tipo de dependencia "solo de referencia" porque:

a) la capa de interfaz solo puede acceder a estos objetos de dominio como valores de retorno de solo lectura obtenidos mediante llamadas a la capa de aplicación

b) los métodos de los servicios en la capa de aplicación aceptan como entrada solo entrada "en bruto" (valores de datos) o parámetros de objeto (para reducir el recuento de parámetros cuando sea necesario) definidos en esa capa. Específicamente, los servicios de aplicaciones nunca aceptan objetos de dominio como entrada.

La capa de interfaz utiliza técnicas de mapeo definidas dentro de la propia capa de interfaz para mapear desde objetos de dominio a DTO. Nuevamente, esto mantiene a los DTO enfocados en ser adaptadores controlados por la capa de interfaz.

BitMask777
fuente
1
Pregunta rápida. Actualmente estoy girando sobre qué regresar de mi capa de aplicación. Volver entidades de dominio de la capa de aplicación se siente mal. ¿Realmente quiero filtrar el dominio al "exterior"? Entonces estaba contemplando DTOs desde la capa de aplicación. Pero eso agrega otro modelo. En su respuesta, dijo que devuelve los modelos de Dominio como "valores de retorno de solo lectura". ¿Cómo haces eso? Es decir, ¿cómo los haces de solo lectura?
Michael Andrews
Creo que voy a adoptar tu posición. Los servicios de aplicaciones devuelven modelos de dominio. Las capas del adaptador de puerto (REST, presentación, etc.) luego las traducen a su propio modelo (ver modelo o representaciones). Agregar un modelo DTO entre la aplicación y los adaptadores de puerto parece excesivo. Los modelos de dominio que regresan todavía se adhieren a DIP, y la lógica de dominio permanece dentro del contexto acotado (no necesariamente dentro del límite de la aplicación. Pero eso parece un buen compromiso).
Michael Andrews
@MichaelAndrews, me alegra saber que mi respuesta ayudó. Re: su pregunta sobre los objetos devueltos como de solo lectura, los objetos en sí mismos no son realmente de solo lectura (es decir, inmutables). Lo que quiero decir es que no sucede en la práctica (al menos en mi experiencia). Para actualizar un objeto de dominio, la capa de interfaz tendría que a) hacer referencia al repositorio del objeto de dominio, o b) volver a llamar a la capa de aplicación para actualizar el objeto que acaba de recibir. Cualquiera de estas son violaciones tan claras de la buena práctica de DDD que considero que se hacen cumplir por sí mismas. Siéntase libre de votar la respuesta si lo desea.
BitMask777
Esta respuesta es muy intuitiva para mí por varias razones. Primero, podemos reutilizar la misma capa de aplicación para varias IU (API, controladores) y cada una puede transformar el modelo como mejor le parezca. Segundo, si tuviéramos que transformar el modelo a DTO en la aplicación. Capa, eso significaría que DTO se define en la aplicación. Capa, que a su vez significa que DTO ahora es parte de nuestro Contexto limitado (¡no necesariamente Dominio!), Esto simplemente se siente mal.
Robotron
1
Estaba a punto de hacerle una pregunta de seguimiento y luego vi que ya la había respondido: "los servicios de aplicaciones nunca aceptan objetos de dominio como entrada". Volvería a hacer +1 si pudiera.
Robotron
5

Tarde a la fiesta, pero me enfrento al mismo tipo de arquitectura y me estoy inclinando hacia "solo DTOs del servicio". Esto se debe principalmente a que he decidido usar solo objetos / agregados de dominio para mantener la validez dentro del objeto, por lo tanto, solo al actualizar, crear o eliminar. Cuando consultamos datos, solo usamos EF como depósito y asigna el resultado a los DTO. Esto nos permite optimizar las consultas de lectura y no adaptarlas a los objetos comerciales, a menudo utilizando funciones de base de datos, ya que son rápidas.

Cada método de servicio define su propio contrato y, por lo tanto, es más fácil de mantener con el tiempo. Espero.

Niklas Wulff
fuente
1
Después de años llegamos a la misma conclusión, exactamente por las razones que mencionó aquí.
Robert Goldwein
@RobertGoldwein ¡Eso es genial! Me siento más confiado en mi decisión ahora. :-)
Niklas Wulff
@NiklasWulff: entonces, los Dtos en cuestión ahora son parte del contrato de la capa de aplicación, es decir, son parte del núcleo / dominio. ¿Qué pasa con los tipos de retorno de la API web ? ¿Expone los Dtos mencionados en su respuesta o tiene modelos de vista dedicados definidos en la capa de API web? O para decirlo de otra manera: ¿mapea el Dtos para Ver Modelos?
Robotron
1
@Robotron No tenemos API web, usamos MVC. Entonces sí, asignamos los dto: s a diferentes modelos de vista. A menudo, un modelo de vista contiene muchas otras cosas para mostrar la página web, por lo que los datos de los dto: s solo forman parte del modelo de vista
Niklas Wulff
4

Hasta ahora, usamos modelos de dominio (principalmente entidades) en todas las capas, y usamos DTO solo como modelos de vista (en el controlador, el servicio devuelve modelos de dominio y el controlador crea el modelo de vista, que se pasa a la vista).

Dado que el modelo de dominio proporciona terminología ( lenguaje ubicuo ) para toda su aplicación, es mejor utilizar el Modelo de dominio ampliamente.

La única razón para usar ViewModels / DTO es una implementación del patrón MVC en su aplicación para separar View(cualquier tipo de capa de presentación) y Model(Modelo de dominio). En este caso, su presentación y modelo de dominio están poco vinculados.

A veces, el servicio debe devolver el objeto de datos que no se definió en el dominio y luego tenemos que agregar un nuevo objeto al dominio que no está asignado o crear un objeto POCO (esto es feo, ya que algunos servicios devuelven modelos de dominio, algunos efectivamente devolver DTO).

Supongo que usted habla sobre los servicios de Aplicación / Negocio / Dominio lógico.

Le sugiero que devuelva las entidades de dominio cuando pueda. Si es necesario devolver información adicional, es aceptable devolver DTO que contenga varias entidades de dominio.

A veces, las personas que usan marcos de tercera parte, que generan proxies sobre entidades de dominio, enfrentan dificultades para exponer entidades de dominio de sus servicios, pero es solo una cuestión de uso incorrecto.

La pregunta es: si usamos estrictamente modelos de vista, ¿está bien devolver los modelos de dominio a los controladores, o deberíamos usar siempre DTO para la comunicación con la capa de servicio?

Yo diría que es suficiente para devolver entidades de dominio en el 99,9% de los casos.

Para simplificar la creación de DTO y mapear sus entidades de dominio en ellos, puede usar AutoMapper .

Ilya Palkin
fuente
4

Si devuelve parte de su modelo de dominio, se convierte en parte de un contrato. Es difícil cambiar un contrato, ya que las cosas fuera de su contexto dependen de él. Como tal, haría que parte de su modelo de dominio sea difícil de cambiar.

Un aspecto muy importante de un modelo de dominio es que es fácil de cambiar. Esto nos hace flexibles a los requisitos cambiantes del dominio.

Timo
fuente
2

Sugeriría analizar estas dos preguntas:

  1. ¿Sus capas superiores (es decir, ver y ver modelos / controladores) consumen los datos de una manera diferente de lo que expone la capa de dominio? Si se está realizando una gran cantidad de mapeo o incluso si hay lógica involucrada, sugeriré volver a visitar su diseño: probablemente debería estar más cerca de cómo se usan realmente los datos.

  2. ¿Qué tan probable es que cambies profundamente tus capas superiores? (por ejemplo, cambiar ASP.NET por WPF). Si esto es muy diferente y su arquitectura no es muy compleja, es mejor que exponga tantas entidades de dominio como sea posible.

Me temo que es un tema bastante amplio y realmente se reduce a lo complejo que es su sistema y sus requisitos.

jnovo
fuente
En nuestro caso, la capa superior seguramente no cambiará. En algunos casos, el servicio devuelve un objeto POCO bastante único (construido a partir de más dominios, por ejemplo, el usuario y los archivos que posee), en algunos casos, un servicio devuelve simplemente el modelo de dominio, por ejemplo, el resultado de "FindUserByEmail () debería devolver el modelo de dominio del usuario, y Aquí está mi preocupación, a veces nuestro servicio (s) devuelve el modelo de dominio, a veces nuevo DTO, y no me gusta esta inconsistencia, leí tantos artículos como pude y la mayoría parece estar de acuerdo en que, aunque el modelo de dominio de mapeo <-> DTO es de 1: 1, modelo de dominio no debe salir de capa de servicios - por lo que estoy rota.
Robert Goldwein
En tal escenario y siempre que pueda soportar el esfuerzo de desarrollo adicional, también iría con el mapeo para que su estratificación sea más consistente.
jnovo
1

En mi experiencia, a menos que esté utilizando un patrón de interfaz de usuario OO (como objetos desnudos), exponer los objetos de dominio a la interfaz de usuario es una mala idea. Esto se debe a que a medida que la aplicación crece, las necesidades de la interfaz de usuario cambian y obligan a sus objetos a acomodar esos cambios. Terminas sirviendo a 2 maestros: UI y DOMAIN, que es una experiencia muy dolorosa. Créeme, no quieres estar allí. El modelo de IU tiene la función de comunicarse con el usuario, el modelo DOMAIN para mantener las reglas de negocio y el modelo de persistencia se ocupa de almacenar datos de manera efectiva. Todos abordan diferentes necesidades de la aplicación. Estoy escribiendo una publicación de blog sobre esto, la agregaré cuando esté lista.

max_cervantes
fuente