.net - mvc - dto entity framework
¿Debo asignar un DTO a/desde una entidad de dominio tanto del lado del cliente como del servidor? (6)
Tengo un modelo de dominio enriquecido, donde la mayoría de las clases tienen algún comportamiento y algunas propiedades que se calculan o exponen las propiedades de los objetos miembros (es decir, los valores de estas propiedades nunca se conservan).
Mi cliente habla al servidor solo a través de WCF.
Como tal, para cada entidad de dominio, tengo una DTO correspondiente, una representación simple que contiene solo datos, así como una clase de asignador que implementa DtoMapper<DTO,Entity>
y puede convertir una entidad a su DTO equivalente o viceversa. versa través de una pasarela estática:
var employee = Map<Employee>.from_dto<EmployeeDto>();
El lado del servidor de esta aplicación es principalmente sobre persistencia, donde mis DTO provienen del servicio WCF, se deserializan, y luego un ORM arbitrario los lleva a la base de datos, o una solicitud de consulta desde WCF y el ORM ejecuta esa consulta contra El DB y retorna los objetos para ser serializados y enviados de vuelta por WCF.
Teniendo en cuenta este escenario, ¿tiene sentido asignar mi almacén de persistencia a las entidades de dominio, o debo mapear directamente a los DTO?
Si utilizo entidades de dominio, el flujo sería
- objeto de peticiones de cliente
- WCF transmite la solicitud al servidor
- ORM consulta la base de datos y devuelve entidades de dominio
- Las entidades de dominio transformadas en DTO por mapper
- WCF serializa DTO y devuelve al cliente
- cliente deserializa DTO
- DTO transformado en entidad de dominio por el mapeador
- Modelos creados, etc.
similar en el viaje de regreso
Si me asigno directamente a DTO, puedo eliminar una asignación por objeto, por solicitud. ¿Qué pierdo al hacer esto?
Lo único que me viene a la mente es otra oportunidad para validar antes de insertar / actualizar, porque no tengo ninguna garantía de que el DTO haya estado sujeto a validación alguna vez, o incluso haya existido como una entidad de dominio antes de ser enviado a través del cable. validar al seleccionar (si otro proceso podría haber puesto valores no válidos en la base de datos). ¿Hay otras razones? ¿Son estas razones suficientes para justificar los pasos de mapeo adicionales?
editar:
Dije "ORM arbitrario" más arriba, y quiero que las cosas sean lo más agnóstico posible con ORM y persistencia, pero si tiene algo especial que agregar que sea específico de NHibernate, hágalo.
Cuando dice que su aplicación del lado del servidor es "principalmente" sobre la persistencia, creo que esta es la clave en la que debe pensar. ¿Existe realmente un modelo de dominio del lado del servidor que requiera cierta información sobre los datos que recibe o su servicio WCF actúa simplemente como la puerta de enlace entre su modelo de dominio y el almacén de datos?
Además, considere si su DTO está diseñado para el dominio del cliente.
¿Es este el único dominio de cliente que necesita acceso a ese almacén de datos a través de su servicio?
¿Los DTO del lado del servidor son lo suficientemente flexibles o generales para servir a un dominio de aplicación diferente?
Si no, es probable que valga la pena esforzarse por mantener abstraídas las implementaciones de la interfaz externa.
(DB-> ORM-> EmployeeEntity-> Client1DTOAssembler-> Client1EmployeeDTO).
Deberá asignar los DTO en el lado del cliente de todos modos, por lo que, para la simetría, es mejor hacer el mapeo inverso en el lado del servidor. De esta manera aislarás tus conversiones en capas de abstracción bien separadas.
Las capas de abstracción son buenas no solo para validaciones, sino también para aislar su código de los cambios que se encuentran debajo / arriba, y hacer que su código sea más comprobable y con menos repeticiones.
Además, a menos que note un gran cuello de botella en el rendimiento de la conversión adicional, recuerde: la optimización temprana es la raíz de todo mal. :)
Definitivamente, debe mantener sus entidades de dominio separadas de sus DTO, son preocupaciones diferentes. Los DTO suelen ser modelos heriachales y autodescriptivos en los que, como entidades de dominio, por otro lado, encapsulan la lógica de su negocio y tienen un gran comportamiento asociado a ellos.
Habiendo dicho eso, no estoy seguro de dónde está el mapeo extra? ¿Recupera los datos utilizando su ORM (también conocido como entidades de dominio) y asigna esos objetos a sus DTO, de modo que solo hay 1 mapeo allí? Por cierto, si aún no está usando algo como Automapper para hacer el tedioso mapeo por usted.
Estos mismos DTO se deserializan en el cliente y desde allí se pueden asignar directamente a sus UIViewModels. Así que el panorama general se ve algo así como:
- El cliente solicita la entidad por Id desde el servicio WCF
- El servicio WCF obtiene una entidad del repositorio / ORM
- Utiliza un AutoMapper para mapear de entidad a DTO
- El cliente recibe DTO
- Utiliza un AutoMapper para asignarse al UI ViewModel
- UIViewModel está enlazado a la GUI
Personalmente recomendaría mantener su asignación en el lado del servidor. Es probable que hayas trabajado mucho en la creación de tu diseño hasta el punto en el que está ahora; no tirar eso lejos
Considere qué es un servicio web. No es simplemente una abstracción sobre tu ORM; es un contrato Es una API pública para sus clientes, tanto interna como externa.
Una API pública debería tener poca o ninguna razón para cambiar. Casi cualquier cambio en una API, además de agregar nuevos tipos y métodos, es un cambio importante. Pero tu modelo de dominio no va a ser tan estricto. Deberá cambiarlo de vez en cuando a medida que agregue nuevas funciones o descubra fallas en el diseño original. Desea poder asegurarse de que los cambios en su modelo interno no causen cambios en cascada a través del contrato del servicio.
En realidad, es una práctica común (no insultaré a los lectores con la frase "mejores prácticas") para crear clases específicas de Request
y Response
para cada mensaje por una razón similar; se vuelve mucho más sencillo ampliar la capacidad de los servicios y métodos existentes sin que se rompan los cambios.
Es probable que los clientes no deseen exactamente el mismo modelo que utiliza internamente en el servicio. Si usted es su único cliente, tal vez esto parezca transparente, pero si tiene clientes externos y ha visto cuán a menudo puede estar lejos de la interpretación de su sistema, entonces comprenderá el valor de no permitir que su modelo perfecto se filtre. Fuera de los límites del servicio API.
Y a veces, ni siquiera es posible enviar su modelo a través de la API. Hay muchas razones por las que esto puede ocurrir:
Ciclos en el gráfico de objetos. Perfectamente bien en OOP; Desastroso en la serialización. Terminará teniendo que tomar decisiones permanentes dolorosas sobre en qué "dirección" debe ser serializada la gráfica. Por otra parte, si usa un DTO, puede serializar en la dirección que desee, lo que se adapte a la tarea en cuestión.
Intentar usar ciertos tipos de mecanismos de herencia a través de SOAP / REST puede ser un gran error. El serializador XML de estilo antiguo al menos admite
xs:choice
;DataContract
no lo hace, y no voy a discutir el razonamiento, pero basta con decir que probablemente tenga algo de polimorfismo en su rico modelo de dominio y es casi imposible canalizarlo a través del servicio web.Carga diferida / diferida, que probablemente utilice si usa un ORM. Es lo suficientemente incómodo asegurarse de que se serialice correctamente; por ejemplo, al utilizar entidades Linq para SQL, WCF ni siquiera activa el cargador perezoso, solo colocará el
null
en ese campo a menos que lo cargue manualmente, pero el problema se agrava. para los datos que regresan. Algo tan simple como una propiedad automáticaList<T>
que se inicializa en el constructor, lo suficientemente común en un modelo de dominio, simplemente no funciona en WCF, porque no invoca al constructor. En su lugar, debe agregar un método de inicializador[OnDeserializing]
, y realmente no desea saturar su modelo de dominio con esta basura.También acabo de notar el comentario parentético de que usas NHibernate. ¡Tenga en cuenta que las interfaces como
IList<T>
no se pueden serializar en absoluto en un servicio web! Si utiliza clases de POCO con NHibernate, como la mayoría de nosotros, entonces esto simplemente no funcionará, punto.
También es probable que haya muchas instancias en las que su modelo de dominio interno simplemente no coincida con las necesidades del cliente, y no tiene sentido cambiar el modelo de su dominio para satisfacer esas necesidades. Como ejemplo de esto, tomemos algo tan simple como una factura. Necesita mostrar:
- Información sobre la cuenta (número de cuenta, nombre, etc.)
- Datos específicos de la factura (número de factura, fecha, fecha de vencimiento, etc.)
- Información de nivel A / R (saldo anterior, cargos por mora, saldo nuevo)
- Información de producto o servicio para todo lo que figura en la factura;
- Etc.
Esto probablemente encaja bien dentro de un modelo de dominio. ¿Pero qué sucede si el cliente desea ejecutar un informe que muestra 1200 de estas facturas? ¿Algún tipo de informe de reconciliación?
Esto apesta para la serialización. Ahora está enviando 1200 facturas con los mismos datos que se serializan una y otra vez: las mismas cuentas, los mismos productos, el mismo A / R. Internamente, su aplicación está realizando un seguimiento de todos los enlaces; sabe que la Factura # 35 y la Factura # 45 son para el mismo cliente y por lo tanto comparten una referencia del Customer
; toda esta información se pierde con la serialización y terminas enviando una cantidad ridícula de datos redundantes.
Lo que realmente desea es enviar un informe personalizado que incluya:
- Todas las cuentas incluidas en el informe, y su A / R;
- Todos los productos incluidos en el informe;
- Todas las facturas, solo con ID de producto y cuenta.
Debe realizar una "normalización" adicional en sus datos salientes antes de enviarlos al cliente si desea evitar la redundancia masiva. Esto favorece en gran medida el enfoque DTO; No tiene sentido tener esta estructura en su modelo de dominio porque su modelo de dominio ya se ocupa de las redundancias, a su manera.
Espero que sean suficientes ejemplos y suficientes razones para convencerlo de que mantenga sus asignaciones del Dominio <--> Contrato de servicio intacto. Has hecho absolutamente lo correcto hasta el momento, tienes un gran diseño y sería una pena negar todo ese esfuerzo en favor de algo que podría llevar a dolores de cabeza importantes más adelante.
Tenemos una aplicación similar en la que un servicio WCF actúa principalmente como una puerta de entrada al almacén de datos persistentes.
En nuestro caso, nuestro cliente y servidor no reutilizan el ensamblaje que contiene "DTO". Esto nos da la oportunidad de simplemente agregar código a las clases parciales generadas por la referencia del servicio, por lo que a menudo podemos usar un DTO tal como está en el lado del cliente y tratarlo como un objeto de dominio. En otras ocasiones, es posible que tengamos objetos de dominio del lado del cliente que sirven como fachadas para un grupo de objetos persistentes que recibimos del servicio WCF.
Cuando piensa en los comportamientos y las propiedades calculadas que tienen sus objetos de dominio, ¿cuánta superposición hay realmente entre su cliente y su servidor? En nuestro caso, determinamos que la división de responsabilidades entre el cliente y el servidor significaba que había muy poco código, o ninguno, que debía estar presente (y exactamente lo mismo) tanto en el cliente como en el servidor.
Para responder a sus preguntas directamente, si su objetivo es permanecer completamente independiente de la persistencia, sin duda asignaría su almacén de persistencia a los objetos de su dominio y luego asignaría a los DTO. Hay demasiadas implementaciones de persistencia que pueden sangrar en sus objetos y complicar su uso como DTO de WCF.
En el lado del cliente, puede que no sea necesario hacer un mapeo adicional si solo puede decorar o aumentar sus DTO y esa es una solución bastante simple.
Tu arquitectura parece bastante bien pensada. Mi sensatez es que, si ya ha decidido reducir los objetos a DTO para enviarlos a través de WCF, y actualmente no necesita una funcionalidad adicional de objetos en el lado del servidor, ¿por qué no simplifica las cosas y las asigna? su persistencia se almacena directamente en los DTO.
Que pierdes No creo que realmente pierdas nada. Tu arquitectura es limpia y simple. Si decide en el futuro que hay una nueva necesidad de una funcionalidad más rica en el lado del servidor, siempre puede volver a factorizar en ese punto para recrear sus entidades de dominio allí.
Me gusta mantenerlo simple y volver a factorizar según sea necesario más adelante, tratar de evitar la optimización de la maduración previa, etc.