Data Transfer Object (DTOs)

 

¿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 DTOsScope de las BEs y los 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

Scope de las BEs sin DTOs

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.

Nuestras BEs (ver dominio completo)

Nuestro DTO

InvoiceDiagram InvoiceDTODiagram

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:

IMapperBEtoDTO

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)

2 comentarios:

  1. Yo una vez me las ingenie para publicar mi BE como DTO, con un par de interfaces y un poco de cuidado, logre incluso hacer el lazy load desde el cliente... A manopla obviamente!

    Saludos
    Leandro
    PD: Es una pesima decisión de diseño... Pero cuando anda se trabaja muy poco!!!

    ResponderBorrar
  2. que tal muy buen post, tienes algun ejemplo de esto? me gustaria analizarlo bien

    ResponderBorrar