jueves, 26 de enero de 2012

Repositorio vs DAO

Una de las partes más importantes de una aplicación, tanto en Java como en .Net, es el acceso a datos. Las dos estrategias más famosas son el DAO y el Repositorio, aunque algunos los confunden, son estrategias diferentes que dan solución al mismo problema.

Patrón DAO
DAO son las siglas de Data Access Object, pero para entenderlo mejor vayamos a las fuentes J2EE Core Paterns:
"Use a Data Access Object (DAO) to abstract and encapsulate all access to the data source. The DAO manages the connection with the data source to obtain and store data. (...)
The DAO implements the access mechanism required to work with the data source.(...) 
The DAO completely hides the data source implementation details from its clients."

En román paladino: el DAO es el responsable de componer los datos de sistemas externos, en caso de una base de datos, será el responsable de componer y ejecutar las consultas SQL necesarias para hacer las operaciones. Por ejemplo:
    interface IFacturaDAO
    {
        Factura Get(int id);
        void Save(Factura factura);
        void Delete(Factura factura);

        ICollection<Factura> getFacturasSinCobrar();
    }
Más o menos este es el ejemplo clásico, un interfaz que nos permite realizar como mínimo las operaciones de un CRUD.

Patrón Repositorio
Si para el patrón DAO consultamos J2EE Core Patterns, para la definición del repositorio debemos acudir a Martin Fowler:
"Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects"

Aunque la definción de los colaboradores de Fowler (Edward Hieatt y Rob Mee) es breve y clara, creo que deja muchos interrogantes abiertos. Únicamente indica que se debe implementar un interfaz similar al de una colección y que por debajo ya se encargará acceder a los datos.
    interface IFacturaRepositoy : ICollection<Factura>
    {
        ICollection<Factura> GetFacturasSinCobrar();
    }

En este ejemplo, además del interfaz de ICollection, el respositorio de facturas implementa una consulta que recupera las facturas sin cobrar. Pero al implementar ICollection hay que sopesar si se debe implementar el método Clear(), yo personalmente no lo haría ;)

Las ¿consultas son reglas de negocio? ¿o es parte del acceso a datos?
Si os fijáis en los ejemplos anteriores, tanto el DAO como el repositorio de factura exponen Este es un debate muy importante que condiciona el desarrollo de la capa de acceso a datos y la implementación de las reglas de negocio.
Utilices DAOs o Repositorios, si crees que las consultas forman parte de las reglas de negocio y quieres mantener la independencia entre negocio y acceso a datos, ¿como puedo escribir las querys, sin usar SQL en la capa de negocio? La solución nos la ofrece otra vez Fowler con el patrón Query Object: "An object that represents a database query"
Si crees que las consultas son solo consultas o que programar una implementación de query object es un esfuerzo innecesario, siempre puedes escribir las consultas dentro de tu DAO (incluso dentro del repositorio, estirando un poco la interpretación del patrón).
Algunos de vosotros pensareis, este Query Object se parece mucho a un criteria de Hibernate. Sí, tenéis razón, se podría decir que el api de criteria es una implementación de Query Object. ¿Y por qué no usarlo en la capa de negocio? Porque si usas criteria fuera de la capa de acceso a datos, estas añadiendo una referencia a Hibernate (a un mecanismo de acceso a datos) en tu capa de negocio. Pero de esto hablaremos más tarde en este post (la tercera vía)
En .Net Linq nos permite hacer implementaciones limpias del patrón repositorio. Linq nos permite hacer consultas sobre colecciones IQueriables y está soportado por el framework. Pero ¿exponer IQueriable fuera de nuestro acceso a datos nos permite mantener a nuestras entidades ignorantes de los mecanismos de persistencia? Sí y no.
En teoría sí ya que Linq forma parte del framework, pero en la realidad es que, de esta forma, se limita el número de opciones a disposición del programador: NHibernate, Entity Framework, o algun otro sistema que soporte Linq. Porque el coste de implementar IQueriable en un sistema que utiliza Ado.Net es altísimo y no creo que merezca la pena.
En definitiva se podría decir que se aplican las reglas de negocio en dos momentos de la aplicación:
  1. al aplicar condiciones para recuperar los datos (recuperar las facturas no pagadas) 
  2. Antes de persistir los cambios en la base de datos.
Con la palabra "Antes" ya queda claro que es en la capa de negocio donde se deben aplicar las validaciones, cambios de estado, etc. la duda está a la hora de recuperar los datos ¿dentro o fuera del acceso a datos?

Tercera vía: los ORM ya nos abstraen de la base de datos.
La gran ventaja de los patrones DAO y repositorio es que abstraen al resto del código de la base de datos, mediante estos mecanismos, si cambia la base de datos no tenemos que tocar la implementación de las reglas de negocio.
Al utilizar ORMs como Hibernate o Entity Framework la abstracción con la base de datos está asegurada. Si ya tenemos una capa de abstracción ¿por qué añadir otra? evitar perder funcionalidad que ofrece el ORM directamente. De esta forma el código es más seco (DRY), incluso se podría decir que cumple el principio de responsabilidad única, ya que el encargado de guardar el objeto es el ORM. Por último, al no tener DAOs ya no tienes la duda de ¿la consulta es una regla de negocio o forma parte del acceso a datos? al usar el ORM directamente también se usan directamente sus mecanismos de consulta.


Reflexiones finales
En una aplicación empresarial, donde es importante tener organizado el código, donde muchas cosas pueden cambiar a petición del cliente y además el equipo estará formado por varias personas, intentaría mantener la capa de acceso a datos completamente aislada, es decir que usaría DAO o Repositorio.

En .Net dudaría si implementar repositorios exponiendo IQueriables ya que muchos controles del framework, paginan y ordenan estas colecciones.
En Java utilizaría DAOs sin lugar a dudas, no implementaría Query Objects, dejando las consultas en el DAO.
Si estas en una empresa con personas que programan en Java y .Net un DAO con hibernate creo que es la mejor solución.

El uso directo de un ORM lo dejaría en proyectos pequeños o medianos, con una persona desarrollando y que requieran cambios rápidos. Aunque eso es un riesgo en un entorno empresarial, ya que desaparece esa persona, y lo que para unos es orden, para otros es caos.

¿Y tu qué opinas? ¿cómo plantearías la estrategia de acceso a datos de una aplicación?

3 comentarios:

  1. Interesante, me hallo también en este momento revisando y analizando arquitecturas lo más desacopladas posibles en las que la capa de datos sea lo más agnóstica posible del motor de base de datos. Uno de los grandes problemas que hasta ahora había tenido (siempre usando DAO's y factorías) era la generación de los DTO's / VO's que serán devueltos y recibidos por los diferentes DAO's, usando hasta ahora una clase que mapeaba estos DTO's con entidades de mi ORM (ya sea EF, NHibernate o lo que sea).

    Hoy precisamente he descubierto varias alternativas como AutoMapper que permiten realizar este tipo de mapeos de forma muy sencilla... ¿tienes experiencia al respecto? ¿qué modelo aconsejas en este sentido? Entiendo que también te basas en este tipo de objetos en tus capas de negocio y vista...

    ResponderEliminar
  2. Hola Pumb@,
    con un ORM no creo que sea necesario tener TOs entre la capa de negocio y la de acceso a datos, ya que es el propio ORM el que realiza el mapeo.
    Donde puede ser necesario el uso de TOs(segun la aquitectura que utilices, o lo purista que quieras ser) es entre la capa vista y la de negocio.
    He jugado un poco con automapper y es muy potente, PERO OJO, hagas el mapeo de tus entidades a mano o usando automapper ¡¡¡ten cuidado con la carga de colecciones asociadas!!! podrías acabar haciendo selects de más y perjudicando el rendimiento de la aplicación.

    ResponderEliminar
  3. Exacto, mis TO's los utilizo para transportar la información a negocio y vista de forma bidireccional, lo que no quiero es tener entidades del ORM paseando por todas las capas, éstas quiero que sólo estén en la capa de datos.

    Lo de los N+1 es un problema muy recurrente en todos los ORM, pero bueno siempre se puede tunear.

    Gracias, buen artículo :-)

    ResponderEliminar