¿Qué son los DTOs?
Son entidades serializables que se suelen utilizar en la transferencia de información entre capas físicas de la aplicación. También son útiles en situaciones donde queremos desvincular capas aunque no exista una división física real. Los DTOs representan la información de las BEs a partir de cierto nivel (o componentes) de nuestra aplicación.
Usando BEs y DTOs
En este caso, vamos a usar BEs para la parte mas interna de nuestra aplicación mientras que DTOs para la parte mas externa. Ejemplos de esto podrían ser:
- A partir de los servicios del tipo WS y WCF pues debemos usar entidades serializables (y nuestro dominio muy posiblemente no lo sea).
- Para la capa de presentación en aplicaciones web con un dominio de cierta complejidad y con uso exhaustivo de ajax.
En el gráfico de la derecha, el límite entre BEs y DTOs es Services, mientras que los Mappers son quienes van a hacer esta conversión por lo que operan en ambos “mundos”.
Usando simplemente las BEs
También podemos optar por no usar DTOs sino que simplemente usamos las BEs en toda nuestra aplicación, ejemplos de estos casos pueden ser:
- En aplicaciones de escritorio (tipo winforms o de consola) es mas común encontrar este tipo de gestión de objetos ya que, al estar ejecutándose todo en el mismo espacio de memoria, es mas simple el manejo de los aspectos relacionados al lazy loading y no es necesaria la serialización.
- En aplicaciones web con dominios relativamente simples, con un buen manejo de los lazies loading o sin ajax que interaccione con BEs que se cargan en distintos request.
En estos casos puede ser mejor usar las BEs en nuestra capa de presentación para no agregar mas types y evitarnos la conversión entre BEs y DTOs, así como no cargar algunos lazy loading innecesariamente (hecho que puede ocurrir con la serialización).
Y si usamos las BEs en toda nuestra aplicación ¿que debemos tener en cuenta?
- Que los ciclos de vida de los objetos asociados al de las BEs, por ejemplo el ciclo de vida de la session de nh por el lazy loading (ojo con las aplicaciones que usen ajax y el manejo de la session de nh por request).
- De no terminar serializando mucha mas información de la necesaria en los casos de servicios que exponen BEs (imaginen en el ejemplo serializar los 4 objetos del diagrama cuando solo eran necesarios los datos del DTO).
- Que las propiedades de tipo IEnumerable<T> no se van a serializar automáticamente, tampoco las ICollection<T> ni las IList<T>, etc.
Siguiendo el camino de los DTOs
Si elegimos ir por la separación en DTOs y BEs, seguramente queramos contar con algún experto (o algunos) para convertir unos en otros. Algunos de estos pueden ser AutoMapper o EmitMapper.
AutoMapper
AutoMapper es un componente que nos ayuda a mapear BEs a DTOs, ¿que quiere decir esto?, que si respetamos ciertas convenciones de nombres podemos transferir la información desde BEs en DTOs con unas pocas líneas de código.
Si miramos nuestro DTO y nuestras BEs, podemos inferir que en las propiedades:
- BillingCity, BillingCountry, BillingState, InvoiceDate, y Id queremos los datos correspondientes a sus equivalentes BEs.Invoice
- CustomerFirstName y CustomerLastName son los datos correspondientes a Customer.FirstName y Customer.LastName respectivamente.
- Total es el resultado del método GetTotal() de Invoice.
Si respetamos estas convenciones de nombres (donde, básicamente, quitamos los “.” y respetamos los nombres), podemos pedirle a AutoMapper que nos pase la información desde nuestras BEs a nuestro DTO con solo 2 líneas de código:
Mapper.CreateMap<Invoice, InvoiceDTO>();
InvoiceDTO dto = Mapper.Map<Invoice, InvoiceDTO>(invoice);
En la primer línea estamos configurando AutoMapper para que relacione las propiedades de estas clases. Esta línea es necesaria solo por única vez primera vez.
En la segunda le estamos pidiendo que nos de un nuevo DTO a partir del objeto invoice.
¿Cómo decidimos si usamos AutoMapper, EmitMapper u otro?
No lo decidimos, como ya vimos en un post anterior, usamos un bajo acoplamiento entre quienes consuman este mapeador y la implementación real. Lo que nos permitirá cambiar la implementación cuando lo consideremos necesario, sin necesidad de cambiar mas que la configuración de IoC Container en el resto de nuestra aplicación.
En este caso definimos una interface que van a cumplir nuestras implementaciones:
Luego hacemos las implementaciones, la de automapper sería algo así:
public class MapperBEtoDTO : IMapperBEtoDTO
{
public MapperBEtoDTO()
{
Mapper.CreateMap<Invoice, InvoiceDTO>();
}
public TD Map<TB, TD>(TB be) where TD : IDTO
{
return Mapper.Map<TB, TD>(be);
}
}
Y, como dijimos en el post anterior, en toda nuestras aplicación hacemos referencia a IMapperBEtoDTO que está en un proyecto diferente a las implementaciones y mediante IoC inyectamos la clase concreta que queremos utilizar.
Para mas detalles, pueden ver en el código de ejemplo los proyectos
- DMS.Mappers (donde está la interface)
- DMS.Mappers.AutoMapper (donde está la implementación)
- DMS.Services.Impl (donde se usa la interface)
- DMS.Integration.Testing (donde se inyecta al servicio, con castle, dicha implementación)