A algunos les gustan gordos. Otros los prefieren más pequeños. Los hay que los preferían gordos y ahora directamente ya no les gustan. Estoy hablando, claro está, de los repositorios. Aunque todos persiguen el mismo objetivo, hay diversas formas de implementarlos y hoy vamos a ver una de las más extendidas.
El repositorio genérico
Ahora está más cuestionado, pero durante mucho tiempo fue el rey de los repositorios. Surge con la esperanza de escribir la herramienta definitiva de acceso a datos combinando el uso de generics con un buen ORM. Suele tener una pinta parecida a esta:
public interface IRepository<TEntity, TId> { TEntity FindById(TId id); void Delete(TEntity entity); void Save(TEntity entity); IEnumerable<TEntity> FindAll(); IEnumerable<TEntity> FindBy(Query query); // Unas cuantas sobrecargas para buscar con paginación ... // Otras sobrecargas más para indicar la ordenación ... }
Esto es una especie de navaja suiza capaz de lanzar todo tipo de consultas a la base de datos. Para sus defensores, su principal ventaja es que permite usar la misma implementación para todas las entidades, evitando crear nuevas clases cuando aparecen nuevas entidades.
Si queremos añadir métodos específicos para alguna entidad la solución es sencilla. Sólo hace falta heredar del interface genérico y añadir los métodos que queramos:
public IUserRepository : IRepository<User, Guid> { User FindByEmail(string email); }
Como todo, esta solución también tiene sus problemas:
Expone demasiados métodos, y siempre expone los mismos métodos para todas las entidades. Sí, son justo las ventajas que le ven otros: un interface completo y flexible que se puede usar genéricamente con cualquier entidad. Esto no es bueno porque no todas las entidades son iguales ni queremos que se puedan tratar por igual.
El ejemplo que veo más claro de esto es el borrado y la creación. Hay entidades que, generalmente, no pueden ser borradas, como una factura, que una vez emitida debe quedar registrada para siempre. Otras entidades no son creadas en nuestra aplicación (simplemente existen porque llegan a la base de datos por otro camino), y no tiene sentido que el repositorio pueda añadir nuevas entidades.
En la mayoría de los casos, los métodos Find
son problemáticos. ¿Para qué querríamos buscar más de una entidad? Los dos casos más frecuentes son para mostrarlas en el interfaz de usuario o para realizar un proceso por lotes con ellas.
Si estamos usando un método Find
para llevar datos a la capa de presentación, seguramente estamos haciendo algo ineficiente. Es casi seguro que estamos cargando de la base de datos más información de la que necesitamos para luego, usando una capa de Mappers, generar los DTO
s o Model
s que mandamos a la capa de presentación. Esto abre la puerta a bastantes problemas de eficiencia si no somos cuidadosos (select n+1, distintas estrategias de fetching).
Si lo que estamos haciendo es un proceso por lotes de las entidades, cuidado. Muchas veces al tener un ORM delante no nos damos cuenta del coste de lo que estamos haciendo, y en lugar de lanzar un update Customer set blocked = 1 where ...
acabamos cargando 300 objetos Customer
con su lista de Order
s solo para marcarlos como bloqueados.
Existen variantes de este repositorio que devuelven IQueryable en lugar de IEnumerable, pero la idea es muy similar. También se puede separar el repositorio en varios interfaces permitiendo controlar un poco mejor la superficie expuesta hacia el exterior.
He usado mucho este tipo de repositorio y con el tiempo cada vez me gusta menos. La reutilización de código que se obtiene al final no merece la pena (se puede conseguir prácticamente lo mismo usando composición) y me parece que contamina el dominio añadiendo operaciones que tienen sentido sobre ciertas entidades.
Seguramente te estés preguntando qué ocurre si quieres tratar de manera genérica todas tus entidades y tener así un repositorio genérico, un controlador genérico y una vista genérica. Si tu aplicación es así, no te preocupes por cómo implementar el repositorio porque lo que estás desarrollando es una aplicación de manejo de datos (CRUD) que va a tener un dominio anémico y da igual como lo hagas: no tiene sentido aplicar DDD y por tanto menos sentido tiene ponerse quisquilloso con el tipo de repositorio.
Juanma, creo que hay que valorar muchas cosas, algunas quizás más de las que pones en tu blog.. Hace un tiempo yo publique una entrada en mi blog relacionada que no se si has podido leer…
http://geeks.ms/blogs/unai/archive/2011/07/26/idbset-iobjectset-como-repositorios-o-tus-propias-abstracciones.aspx
No conocía la entrada de tu blog, pero está muy bien.
Y sí, tienes razón, hay que valorar muchas cosas.
En este caso sólo pretendía recalcar las que se usan más frecuentemente como argumentos a favor y en contra del repositorio genérico.
Mi idea es en próximos posts tratar también otros tipos de repositorios para poder comparar.
Pingback: Tipos de repositorio: El repositorio concreto « Koalite's blog
Pingback: Tipos de repositorio: El no-repositorio « Koalite's blog
Pingback: Tipos de repositorio: Separación de responsabilidades « Koalite's blog