En mi opinión, tanto la Repository
abstracción como la UnitOfWork
abstracción tienen un lugar muy valioso en cualquier desarrollo significativo. La gente discutirá sobre los detalles de implementación, pero así como hay muchas formas de desollar un gato, hay muchas formas de implementar una abstracción.
Su pregunta es específicamente para usar o no usar y por qué.
Como sin duda se habrá dado cuenta de que ya tiene ambos patrones integrados en Entity Framework, DbContext
es el UnitOfWork
y DbSet
es el Repository
. Por lo general no necesita prueba de la unidad del UnitOfWork
o de Repository
ellos mismos, ya que son simplemente facilitando entre las clases y las implementaciones de acceso a datos subyacentes. Lo que tendrá que hacer una y otra vez es burlarse de estas dos abstracciones cuando pruebe unitariamente la lógica de sus servicios.
Puede simular, falsificar o lo que sea con bibliotecas externas que agregan capas de dependencias de código (que no controla) entre la lógica que realiza la prueba y la lógica que se prueba.
Entonces, un punto menor es que tener su propia abstracción para UnitOfWork
y Repository
le brinda el máximo control y flexibilidad al burlarse de sus pruebas unitarias.
Todo muy bien, pero para mí, el poder real de estas abstracciones es que proporcionan una forma sencilla de aplicar técnicas de Programación Orientada a Aspectos y adherirse a los principios SOLID .
Entonces tienes tu IRepository
:
public interface IRepository<T>
where T : class
{
T Add(T entity);
void Delete(T entity);
IQueryable<T> AsQueryable();
}
Y su implementación:
public class Repository<T> : IRepository<T>
where T : class
{
private readonly IDbSet<T> _dbSet;
public Repository(PPContext context)
{
_dbSet = context.Set<T>();
}
public T Add(T entity)
{
return _dbSet.Add(entity);
}
public void Delete(T entity)
{
_dbSet.Remove(entity);
}
public IQueryable<T> AsQueryable()
{
return _dbSet.AsQueryable();
}
}
Nada fuera de lo común hasta ahora, pero ahora queremos agregar algunos registros, fácil con un Decorador de registros .
public class RepositoryLoggerDecorator<T> : IRepository<T>
where T : class
{
Logger logger = LogManager.GetCurrentClassLogger();
private readonly IRepository<T> _decorated;
public RepositoryLoggerDecorator(IRepository<T> decorated)
{
_decorated = decorated;
}
public T Add(T entity)
{
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
T added = _decorated.Add(entity);
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
return added;
}
public void Delete(T entity)
{
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
_decorated.Delete(entity);
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
}
public IQueryable<T> AsQueryable()
{
return _decorated.AsQueryable();
}
}
Todo hecho y sin cambios en nuestro código existente . Hay muchas otras preocupaciones transversales que podemos agregar, como el manejo de excepciones, el almacenamiento en caché de datos, la validación de datos o lo que sea, y a lo largo de nuestro proceso de diseño y construcción, lo más valioso que tenemos nos permite agregar características simples sin cambiar ninguno de nuestro código existente. es nuestra IRepository
abstracción .
Ahora, muchas veces he visto esta pregunta en StackOverflow: "¿cómo se hace que Entity Framework funcione en un entorno de múltiples inquilinos?".
https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant
Si tiene una Repository
abstracción, la respuesta es "es fácil agregar un decorador"
public class RepositoryTennantFilterDecorator<T> : IRepository<T>
where T : class
{
//public for Unit Test example
public readonly IRepository<T> _decorated;
public RepositoryTennantFilterDecorator(IRepository<T> decorated)
{
_decorated = decorated;
}
public T Add(T entity)
{
return _decorated.Add(entity);
}
public void Delete(T entity)
{
_decorated.Delete(entity);
}
public IQueryable<T> AsQueryable()
{
return _decorated.AsQueryable().Where(o => true);
}
}
En mi opinión, siempre debe colocar una simple abstracción sobre cualquier componente de terceros al que se hará referencia en más de un puñado de lugares. Desde esta perspectiva, un ORM es el candidato perfecto, ya que se hace referencia en gran parte de nuestro código.
La respuesta que normalmente me viene a la mente cuando alguien dice "¿por qué debería tener una abstracción (por ejemplo Repository
) sobre esta o aquella biblioteca de terceros" es "por qué no lo harías tú?"
Los decoradores PS son extremadamente sencillos de aplicar utilizando un contenedor IoC, como SimpleInjector .
[TestFixture]
public class IRepositoryTesting
{
[Test]
public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
{
Container container = new Container();
container.RegisterLifetimeScope<PPContext>();
container.RegisterOpenGeneric(
typeof(IRepository<>),
typeof(Repository<>));
container.RegisterDecorator(
typeof(IRepository<>),
typeof(RepositoryLoggerDecorator<>));
container.RegisterDecorator(
typeof(IRepository<>),
typeof(RepositoryTennantFilterDecorator<>));
container.Verify();
using (container.BeginLifetimeScope())
{
var result = container.GetInstance<IRepository<Image>>();
Assert.That(
result,
Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
Assert.That(
(result as RepositoryTennantFilterDecorator<Image>)._decorated,
Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
}
}
}