29 julio 2011

Repository y UnitOfWork con EF 4.1

Hola a tod@s,
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.
y a favor de usar la abstracción que proporciona el ORM:
  • 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.
Después de leer los argumentos de las dos posturas y ver que, como casi siempre, hay argumentos validos a favor de la abstracción y también en contra me di cuenta que lo primero que tenia que hacer era una prueba de concepto con cada una de las implementaciones y esto es lo que pretendo mostrar en este post: ver los pasos a seguir para aplicar los patrones repositorio y unidad de trabajo con EF partiendo de una implementación “directa”, sin ninguna abstracción y después ver los pasos a realizar para abstraer los repositorios. Este ejemplo nos permitirá entender cada una de las dos opciones y podremos avaluar “con un poco de conocimiento de causa” pros y contras de cada una de ellas.

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:
public class MyUnitOfWork : DbContext
{
private IDbSet<FooEntity> FooEntities;
}
Con esta aproximación hay que tener en cuenta que:
  • 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:
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);
}    
}
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 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);
}
}
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:
  • 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.
Una vez creados los repositorios procedemos a crear la abstracción de la unidad de trabajo mediante una interfaz IMyUnitOfWork que exponga nuestros repositorios en vez de exponer IDbSet<TEntity> y su correspondiente implementación:
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();
}
}
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.
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:

6 comentarios:

Joaquín Jiménez Godoy dijo...

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.

xampi dijo...

@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.

Unknown dijo...

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...

xampi dijo...

@Daniel muchas gracias!

Anónimo dijo...

Hola, muy buen post!.
Un saludo.

Anónimo dijo...

muy buen artículo!