¿Mejores prácticas para mapear DTO a un objeto de dominio?

81

He visto muchas preguntas relacionadas con la asignación de DTO a objetos de dominio, pero no sentí que respondieran a mi pregunta. He usado muchos métodos antes y tengo mis propias opiniones, pero estoy buscando algo un poco más concreto.

La situación:

Tenemos muchos objetos de dominio. Estamos utilizando un modelo CSLA, por lo que nuestros objetos de dominio pueden ser bastante complejos y contienen su propio acceso a datos. No conviene pasarlos por el cable. Vamos a escribir algunos servicios nuevos que devolverán datos en varios formatos (.Net, JSON, etc.). Por esta (y otras razones) también estamos creando un objeto de transferencia de datos esbelto para pasar por el cable.

Mi pregunta es: ¿Cómo se deben conectar el objeto DTO y el dominio?

Mi primera reacción es usar una solución de tipo patrón DTO de Fowler . He visto esto muchas veces y me parece bien. El objeto de dominio no contiene ninguna referencia al DTO. Se llama a una entidad externa (un "mapeador" o "ensamblador") para crear un DTO a partir de un objeto de dominio. Normalmente hay un ORM en el lado del objeto de dominio. La desventaja de esto es que el "mapeador" tiende a volverse extremadamente complejo para cualquier situación real y puede ser muy frágil.

Otra idea presentada es que el objeto de dominio "contenga" el DTO, ya que es solo un objeto de datos ajustado. Las propiedades del objeto de dominio harían referencia internamente a las propiedades del DTO y podrían devolver el DTO si se lo solicita. No veo ningún problema con esto, pero se siente mal. He visto algunos artículos en los que las personas que utilizan NHibernate parecen utilizar este método.

¿Hay otras formas? ¿Vale la pena usar una de las formas anteriores? Si es así o no, ¿por qué?

Brian Ellis
fuente
4
El automapper parece interesante. He visto mucho código antes que lo hubiera reemplazado. Mi problema principal es que si me voy a quedar atrapado con una tonelada de código de mapeo por cualquier motivo, preferiría tener el control sobre él.
Brian Ellis
2
Cuando pasamos de DTO a Objetos de dominio, ese mapeo es 100% manual. Es un problema mucho más difícil de resolver, ya que intentamos mantener nuestros objetos de dominio basados ​​en operaciones, en lugar de simplemente contenedores de datos. Ir a un DTO es un problema fácil de resolver.
Jimmy Bogard
Otra opción es la versión beta de ServiceToolkit.NET, que iniciamos durante nuestro último proyecto. Quizás pueda ayudarte: http://servicetoolkit.codeplex.com/
Estoy de acuerdo en que es incorrecto que el objeto de dominio no tenga conocimiento del objeto dto. Si bien pueden estar relacionados en este caso, su propósito es completamente independiente (los dtos generalmente se hacen con un propósito específico) y estaría creando una dependencia innecesaria.
Sinaesthetic

Respuestas:

40

Un beneficio de tener un mapeador que se encuentra entre su dominio y su DTO no es tan aparente cuando solo admite un único mapeo, pero a medida que aumenta el número de mapeos, tener ese código aislado del dominio ayuda a mantener el dominio más simple y ágil. No abarrotarás tu dominio con mucho peso extra.

Personalmente, trato de mantener el mapeo fuera de las entidades de mi dominio y pongo la responsabilidad en lo que llamo "Capa de Administrador / Servicio". Esta es una capa que se encuentra entre la aplicación y el (los) repositorio (s), y proporciona lógica empresarial como la coordinación del flujo de trabajo (si modifica A, es posible que también deba modificar B para que el servicio A funcione con el Servicio B).

Si tuviera muchos formatos finales posibles, podría considerar la creación de un formateador conectable que pudiera usar el patrón Visitor, por ejemplo, para transformar mis entidades, pero todavía no he encontrado la necesidad de algo tan complejo.

JoshBerke
fuente
"(Si modifica A, es posible que también deba modificar B para que el servicio A funcione con el Servicio B)". ¿No se incluye esto en la lógica empresarial? Creo que esta parte debería ir al controlador ¿verdad en lugar de al servicio?
Ayyappa
24

Puede utilizar un automapper como el escrito por Jimmy Bogard, que no tiene conexión entre los objetos y se basa en las convenciones de nomenclatura que se cumplen.

Garry Shutler
fuente
9
Automapper podría dar lugar a propiedades expuestas accidentalmente -> agujero de seguridad. Sería mejor decir explícitamente qué debería exponerse como DTO.
demonio
4
@deamon: preocupación válida, pero también lo son los errores (y los posibles agujeros de seguridad debido a la supervisión humana) que se pueden crear escribiendo todo ese código de mapeo pegajoso. Iré por el camino automágico y manejaré el 5% usando su función de mapeo personalizado incorporada.
Merritt
@deamon: ¿no puede simplemente hacer el mapeo condicional para esas propiedades que no debería exponer? ¿Piensa que AutoMapper maneja ese escenario?
Richard B
Si usa AutoMapper, creo que es extremadamente importante que tenga todas las pruebas unitarias en su lugar para probar si el mapeo se realiza correctamente.
L-Four
7

Usamos plantillas T4 para crear las clases de mapeo.

Pro's: código legible por humanos disponible en tiempo de compilación, más rápido que un mapeador en tiempo de ejecución. Control del 100% sobre el código (puede utilizar métodos parciales / patrón de plantilla para ampliar la funcionalidad de forma ad-hoc)

Contras: excluir ciertas propiedades, colecciones de objetos de dominio, etc., aprender la sintaxis T4.

SturmUndDrang
fuente
6

Mantener la lógica de mapeo dentro de su entidad significa que su Objeto de Dominio ahora es consciente de un "detalle de implementación" que no necesita conocer. Generalmente, un DTO es su puerta de entrada al mundo exterior (ya sea desde una solicitud entrante o mediante una lectura de un servicio / base de datos externo). Dado que la entidad es parte de su lógica empresarial, probablemente sea mejor mantener esos detalles fuera de la entidad.

Mantener el mapeo en otro lugar sería la única alternativa, pero ¿a dónde debería ir? Intenté introducir objetos / servicios de mapeo pero, después de todo lo dicho y hecho, parecía una sobreingeniería (y probablemente lo fue). He tenido cierto éxito con Automapper y demás para proyectos más pequeños, pero las herramientas como Automapper tienen sus propios inconvenientes. He tenido algunos problemas bastante difíciles de encontrar relacionados con las asignaciones porque las asignaciones de Automapper están implícitas y están completamente desacopladas del resto de su código (no como una "separación de preocupaciones", sino más bien como un "dónde vive el mapeo olvidado de Dios"), por lo que a veces puede ser difícil de localizar. Por no decir que Automapper no tiene sus usos, porque los tiene. Creo que el mapeo debería ser algo tan obvio y transparente como sea posible para evitar problemas.

En lugar de crear una capa de servicio de mapeo, he tenido mucho éxito al mantener mis mapeos dentro de mis DTO. Dado que los DTO siempre se ubican en el límite de la aplicación, pueden conocer el Business Object y descubrir cómo mapear desde / hacia ellos. Incluso cuando el número de asignaciones escala a una cantidad razonable, funciona de forma limpia. Todos los mapeos están en un solo lugar y no tiene que administrar un montón de servicios de mapeo dentro de su capa de datos, capa anticorrupción o capa de presentación. En cambio, el mapeo es solo un detalle de implementación delegado al DTO involucrado con la solicitud / respuesta. Dado que los serializadores generalmente solo serializan propiedades y campos cuando los envía a través del cable, no debería tener ningún problema. Personalmente, he encontrado que esta es la opción más limpia y puedo decir, en mi experiencia,

Kyle Goode
fuente
3

¿Cómo ve implementar un constructor dentro de la clase DTO que toma como parámetro un objeto de dominio?

Di ... algo como esto

class DTO {

     // attributes 

     public DTO (DomainObject domainObject) {
          this.prop = domainObject.getProp();
     }

     // methods
}
Víctor
fuente
9
Por favor, nunca hagas esto. No desea que su capa DTO sea consciente de su capa de dominio o dependa de ella. La ventaja del mapeo es que las capas inferiores se pueden cambiar fácilmente cambiando el mapeo, o que las modificaciones en la capa inferior pueden controlarse cambiando el mapeo. Digamos que dtoA se asigna a domainObjectA hoy, pero mañana el requisito es que se asigne a domainObjectB. En su caso, debe modificar el objeto DTO, lo cual es un gran no-no. Ha perdido muchos beneficios del mapeador.
Frederik Prijck
2
Primero que nada, ¡gracias! :RE. Entonces @FrederikPrijck insertando una capa entre el DTOy el DomainObject, básicamente nos esforzamos en que este problema del DTO depende del objeto de dominio, por lo que todo el "trabajo de construcción" se realiza en una capa intermedia (clase) llamada mapper, que depende de ambos DTO y DomainObjects. Entonces, ¿cuál es el mejor enfoque, o generalmente el más recomendable, para este asunto? Solo pido asegurarme de que se entendió el punto.
Victor
4
Sí, la capa se llama "Ensamblador". Al usar una tercera capa para definir las asignaciones, permite la posibilidad de reemplazar fácilmente la capa del ensamblador por otra implementación (por ejemplo: eliminar Automapper y usar asignaciones manuales), que siempre es una mejor opción. La mejor manera de entenderlo es pensar en dónde le daría el Objeto A, y alguien más le da el Objeto B. No tiene acceso a cada uno de esos objetos (solo dll), por lo que el mapeo solo se puede hacer creando un tercer capa. Pero incluso si puede acceder a cualquiera de los objetos, las asignaciones siempre deben realizarse en el exterior, ya que no están relacionados.
Frederik Prijck
1
Pero esta respuesta es de hecho "más que útil" con los comentarios y las correcciones, trae a cualquier lector reconocimiento y consejos sobre el problema ... realmente contribuye a aprender, no veo por qué donwvote ... me ayuda .. pero no quiero comenzar una discusión sobre eso .. depende de usted. De todos modos gracias por la respuesta.
Victor
3
En realidad, me gusta este enfoque, actualmente uso el constructor para mapear la entidad a DTO, y uso una clase de mapeador para mapear la entrada dto a la entidad.
dream83619
1

Otra posible solución: http://glue.codeplex.com .

caracteristicas:

  • Mapeo bidireccional
  • Mapeo automático
  • Mapeo entre diferentes tipos
  • Mapeo anidado y aplanamiento
  • Listas y matrices
  • Verificación de relaciones
  • Probando el mapeo
  • Propiedades, campos y métodos
Saly
fuente
0

Puedo sugerir una herramienta que creé y es de código abierto alojada en CodePlex: EntitiesToDTOs .

El mapeo de DTO a Entity y viceversa se implementa mediante métodos de extensión, estos componen el lado de ensamblador de cada extremo.

Terminas con un código como:

Foo entity = new Foo();
FooDTO dto = entity.ToDTO();
entity = dto.ToEntity();

List<Foo> entityList = new List<Foo>();
List<FooDTO> dtoList = entityList.ToDTOs();
entityList = dtoList.ToEntities();
kzfabi
fuente
esto es arquitectónicamente incorrecto porque hace que las entidades de dominio y DTO se conozcan entre sí.
Raffaeu
5
@Raffaeu No lo creo ya que los métodos ToDTO / ToDTOs / ToEntity / ToEntities se definen como métodos de extensión que representan a los ensambladores. La lógica de convertir una entidad en un DTO y viceversa está en los métodos de extensión (ensambladores), no en la entidad / DTO de hecho.
kzfabi
2
Si habla de "Ensamblador", impleméntelos de la manera correcta. Hazlos modulares, hazlos fácilmente intercambiables, usa la inyección de dependencia. No es necesario que el modelo de dominio en sí sea consciente de una conversión a DTO. Digamos que tengo 1 objeto de dominio pero 50 aplicaciones diferentes usando el mismo dominio, cada una con su propio DTO. No vas a crear 50 extensiones. En su lugar, creará un servicio de aplicación para cada aplicación con los ensambladores necesarios que se inyectan como una dependencia en el servicio.
Frederik Prijck
0

¿Por qué no podemos hacer así?

class UserDTO {
}

class AdminDTO {
}

class DomainObject {

 // attributes
 public DomainObject(DTO dto) {
      this.dto = dto;
 }     

 // methods
 public function isActive() {
      return (this.dto.getStatus() == 'ACTIVE')
 }

 public function isModeratorAdmin() {
      return (this.dto.getAdminRole() == 'moderator')
 }

}


userdto = new UserDTO();
userdto.setStatus('ACTIVE');

obj = new DomainObject(userdto)
if(obj.isActive()) {
   //print active
}

admindto = new AdminDTO();
admindto.setAdminRole('moderator');

obj = new DomainObject(admindto)
if(obj.isModeratorAdmin()) {
   //print some thing
}

@FrederikPrijck (o) alguien: sugiera. En el ejemplo anterior, DomainObject depende de DTO. De esta manera puedo evitar que el código haga el mapeo del dto <--> domainobject.

o la clase DomainObject puede extender la clase DTO?

usuario3767551
fuente
0

Otra opción sería utilizar ModelProjector . Es compatible con todos los escenarios posibles y es muy fácil de usar con una huella mínima.

user10269
fuente
0

Podemos usar el patrón Factory, Memento y Builder para eso. Factory oculta los detalles sobre cómo crear una instancia de modelo de dominio desde DTO. Memento se encargará de la serialización / deserialización del modelo de dominio hacia / desde DTO e incluso puede acceder a miembros privados. El constructor permitirá el mapeo de DTO al dominio con una interfaz fluida.

Irwansyah
fuente