en estos días he estado trasteando un poco con Entity Framework 4.1 Code First (EF) y su integración en proyectos ASP.NET MVC3 y, como es habitual, al trastear te surgen dudas sobre cual/cuales serian los patrones y buenas prácticas que se deberían aplicar al trabajar con un motor de ORM. Dos de los patrones que están ampliamente aceptados al trabajar con ORM’s son el patrón repositorio (Repository pattern definido por Martin Fowler) y el patrón unidad de trabajo (Unit of Work también definido por Martin Fowler).
Parece claro pues que es una buena opción aplicar estos patrones si tenemos que “lidiar” con un ORM en nuestro proyecto. Normalmente todos los ORM ya proporcionan una implementación de estos patrones, por ejemplo, con EF, los IDbSets<TEntity> proporcionan los repositorios para nuestras entidades y el DbContext proporciona la unidad de trabajo.
La principal duda que surge una vez hemos decidido aplicar estos patrones es si debemos hacer una abstracción de los patrones repositorio y unidad de trabajo o, por el contrario, podemos trabajar directamente con la implementación de estos patrones que proporciona el ORM. En la comunidad hay partidarios y detractores de cada una de las dos posturas, y cada uno de ellos expone varias razones que les llevan a decantarse por su opción.
Entre los argumentos que esgrimen los partidarios de crear una abstracción encontramos:
- Se pueden definir operaciones de consulta específicas para cada agregado.
- Tener un repositorio propio nos permite dotar a los agregados de las funcionalidades que necesitan y solo de estas, cada agregado se puede personalizar para ofrecer solo aquello que debe.
- Permite centralizar más fácilmente en el repositorio opciones de monitorización, logging y también opciones particulares que queremos que se apliquen siempre que se trabaje con el agregado que representa el repositorio.
- Los consumidores de los repositorios dependen del ORM concreto (EF en nuestro caso) y hay una dependencia entre estos consumidores (normalmente la capa de servicios) con la capa de acceso a datos.
- Es más fácil hacer unit testing ya que podemos hacer mocks o stubs de la interfaz que cumple cada uno de nuestros repositorios.
- El más argumento clave que esgrimen los partidarios de usar la abstracción que proporciona el ORM es la simplicidad: ¿para que vamos a reinventar la rueda? Si ya tenemos un repositorio proporcionado por el ORM ¿para que necesitamos implementar el nuestro?
- Implementar una abstracción de patron repositorio tenia sentido cuando Fowler lo definió por que los ORM no lo proporcionaban, hoy en día carece de sentido puesto que la mayoría de ORM ya proporcionan esta abstracción “de serie”.
- Cada operación de consulta de debe definir e implementar en los agregados, ¿para qué? esto ya lo tenemos implementado…
- Se ve como un intento de proteger a los usuarios de los repositorios al “caparles” funcionalidad.
- En el caso particular de EF los repositorios implementan la interfaz IDbSet<TEntity> con lo cual podemos hacer unit testing igual que si usáramos nuestra abstracción.
Opción 1: usando los repositorios de EF
La primera alternativa consiste en usar directamente la implementación del patrón repositorio que proporciona EF.En esta aproximación solo debemos crear una clase que hereda del contexto de EF (DbContext) y que expone los repositorios IDbSet<TEntity> con los que va a trabajar:
Con esta aproximación hay que tener en cuenta que:public class MyUnitOfWork : DbContext{private IDbSet<FooEntity> FooEntities;
}
- Todo es muy sencillo, solo debes crear la clase que hereda de DbContext y añadir un atributo público IDbSet>TEntity> para cada repositorio.
- Nuestro servicio tiene una dependencia a EF, desde el proyecto donde se usen los repositorios y la unidad de trabajo tenemos que añadir una referencia a la dll de EF.
- Nosotros no creamos los repositorios implementado la interfaz IDbSet<TEntity>, usamos la implementación DbSet<TEntity> que proporciona EF.
- Los repositorios implementan la interfaz IDbSet<TEntity> con lo cual podemos hacer unit test de los servicios usando mocks o stubs de estos repositorios.
- La unidad de trabajo tiene los repositorios como atributos, para acceder a un repositorio debemos crear una unidad de trabajo y acceder a este.
- Es una buena opción para hacer testing usando mocks o stubs hacer que la clase que representa la unidad de trabajo y nos da acceso a los repositorios hereda de DbContext implemente una interfaz con las operaciones operaciones propias de la unidad de trabajo y los getters a los repositorios.
- Como los repositorios dependen de EF no es tan importante hacer que nuestra unidad de trabajo implemente una interfaz para eliminar esta dependencia.
Opción 2: creando nuestra abstracción para los repositorios
La segunda alternativa es hacer una abstracción de la unidad de trabajo y los repositorios que nos proporciona EF para independizar nuestra capa de servicios del ORM usado en la capa de datos.Este ejemplo no es un ejemplo funcional, solo pretende mostrar como se harían estas abstracciones. Solo se muestra la operación Add del repositorio y la operación Commit de la unidad de trabajo.
Primero creamos un repositorio para cada agregado que tengamos en nuestra aplicación. Estos agregados implementaran una interfaz con las operaciones típicas (Add, Remove, Attach, etc…) y la implementación de cada una de estas operaciones se delegará al correspondiente DbSet<TEntity>. En el código solo se muestra la operación Add para un agregado FooEntity:
Bueno, al menos esto es lo que salió en el primer ciclo de Red, Green, Refactor. Fue al crear el segundo repositorio cuando, en la fase de refactoring, nos dimos cuenta que se podía crear un repositorio genérico del que heredaran todos nuestros repositorios (DRY):public interface IFooRepository{void Add(FooEntity entity);
}public class FooEntitiesRepository : IFooRepository{private IDbSet<FooEntity> baseRepository;
public FooEntitiesRepository(IDbSet<FooEntity> baseRepository)
{this.baseRepository = baseRepository;
}public void Add(FooEntity entity){baseRepository.Add(entity);}}
a partir de aquí surge otra de los debates interesantes (que queda fuera del ámbito de este post): ¿como realizar las consultas a estos repositorios? Aquí tenemos varias opciones y cada una tiene sus pros y contras:public interface IRepository<TEntity> where TEntity : class{TEntity Add(TEntity item);}public class Repository<TEntity> : IRepository<TEntity> where TEntity : class{private IDbSet<TEntity> baseRepository;
public Repository(IDbSet<TEntity> baseRepository)
{this.baseRepository = baseRepository;
}public TEntity Add(TEntity item)
{return this.baseRepository.Add(item);}}
- Devolver IQueryable<T> y que sea el cliente el que especifique las consultas a realizar directamente con LINQ
- Proporcionen métodos de consulta explícitos
- Implementar el patrón de especificación de consultas Specification pattern.
Como veis, la implementación de esta abstracción delega en el DbContext de EF las operaciones relacionadas con las tareas propias de la unidad de trabajo (Commit, Rollback, etc…). Como ya he comentado antes, solo se muestra la operación Commit de la unidad de trabajo y un único repositorio de entidades de tipo FooEntity.public interface IMyUnitOfWork{IRepository<FooEntity> FooEntities { get; }
int Commit();
}public class MyUnitOfWork : DbContext, IMyUnitOfWork{private IRepository<FooEntity> fooEntities;
public IRepository<FooEntity> FooEntities
{get
{if (fooEntities == null)fooEntities = new Repository<FooEntity>(this.Set(typeof(FooEntity)) as IDbSet<FooEntity>);return fooEntities;
}}public int Commit(){return base.SaveChanges();}}
Con esta aproximación hay que tener en cuenta que:
- Bastante mas complejo inicialmente, hay que hacer la implementación genérica del repositorio.
- Posiblemente estas creando una abstracción de algo que ya tenemos, recordad que los DbSets<TEntity> son una implementación del patrón repositorio.
- Nuestro servicio NO tiene una dependencia de EF, el servicio usara las interfaces definidas para los repositorios y la unidad de trabajo y estas deberían estar colocadas en un proyecto diferente al que contenga las implementaciones dependientes del ORM.
- Nuestros creamos nuestros propios repositorios heredando de Repository<TEntity> que es la abstracción que hemos creado para implementar el patrón repositorio. Esta implementación es dependiente del contexto de EF.
- Los repositorios implementan su propia interfaz (para proporcionar consultas explicitas por ejemplo) y nuestra interfaz genérica IRepository
- La implementación de nuestra unidad de trabajo contiene los DbSet<TEntity> propios
- Igual que con la primera aproximación, la unidad de trabajo tiene los repositorios como atributos, para acceder a un repositorio debemos crear una unidad de trabajo y acceder a este.
Conclusión
En este post hemos visto dos opciones a la hora de acceder a nuestro ORM mediante los patrones repositorio y unidad de trabajo, cada opción con sus ventajas e inconvenientes. Personalmente, escribir este post me ha servido para clarificar estas dos de las alternativas y me ha permitido comprender un poco más los argumentos a favor y en contra de cada una de ellas. Escoger una opción u otra dependerá, en gran medida, del tipo de aplicación a desarrollar, lo que espero que después de leer este post os sea un poco mas fácil decantaros hacia una opción u otra.Saludos, Josep Maria
Nota: los siguientes enlaces me han servido de referencia para escribir este post:
- IDbSet, IObjectSet como repositorios o tus propias abstracciones, interesantísimo post de Unai Zorrila sobre las motivaciones que nos pueden llevar a considerar una buena opción crear nuestras propias abstracciones de los repositorios.
- Microsoft Spain - Domain Oriented N-Layered .NET 4.0 Sample App, aplicación de ejemplo con arquitectura DDD donde se opta por hacer una abstracción de los repositorios y la unidad de trabajo. Esta arquitectura se decanta por una solución un tanto diferente a la que hemos mostrado en el post, en ella la unidad de trabajo es accesible a través de los repositorios mientras que en la aproximación que hemos visto es la unidad de trabajo la que nos da acceso a los repositorios.
- Respository is the new Singleton, post de Ayende Rahien crítico con el uso de abstracciones del patrón repositorio, según el autor es absurdo hacer esta abstracción cuando los ORM actuales ya la proporcionan. Si el post es interesante los comentarios no tienen desperdicio!
- Architecting in the pit of doom: The evils of the repository abstraction layer, otro post crítico de Ayende con la complejidad y lo problemas que pueden introducir los patrones repositorio y specification.
6 comentarios:
Hola Josep Maria,
he leido con atención tu post y hay muchas cosas que no he podido seguir, la causa, seguro, es que solo he finalizado el primer curso de ingenieria de software. Así que me marcaré el post en mis favoritos y regresaré a él en un par de años.
Joaquin.
@Joaquín Jimenez En un par de años esto habrá cambiado tanto que parecerá que el post hable de máquinas de escribir :-) Muchas gracias por el comentario.
Me gusto mucho tu Post, actualmente estoy desarrollando un proyecto usando Asp.Net MVC3 y EF 4.1 con el enfoque Code First, yo opte por la abstracción de los repositorios. Aunque no implemente aún el patron UofW, los problemas que tengo hoy son sobre todo con el mapping de mi clases POCO. Las convenciones del EF no siempre producen el resultado que uno espera en el esquema de la Base de Datos. Saludos...
@Daniel muchas gracias!
Hola, muy buen post!.
Un saludo.
muy buen artículo!
Publicar un comentario