IMO, tanto a Repository
abstração quanto a UnitOfWork
abstração têm um lugar muito valioso em qualquer desenvolvimento significativo. As pessoas discutirão sobre os detalhes de implementação, mas assim como existem muitas maneiras de esfolar um gato, existem muitas maneiras de implementar uma abstração.
Sua pergunta é especificamente para usar ou não usar e por quê.
Como você sem dúvida percebeu, você já tem esses dois padrões integrados ao Entity Framework, DbContext
é o UnitOfWork
e DbSet
é o Repository
. Geralmente, você não precisa fazer o teste de unidade dos próprios UnitOfWork
ou Repository
, pois eles estão simplesmente facilitando entre suas classes e as implementações de acesso a dados subjacentes. O que você vai precisar fazer, repetidamente, é simular essas duas abstrações ao testar a lógica de seus serviços.
Você pode simular, falsificar ou o que quer que seja com bibliotecas externas adicionando camadas de dependências de código (que você não controla) entre a lógica que está fazendo o teste e a lógica que está sendo testada.
Portanto, um ponto secundário é que ter sua própria abstração UnitOfWork
e Repository
lhe dá o máximo de controle e flexibilidade ao simular seus testes de unidade.
Tudo bem, mas para mim, o verdadeiro poder dessas abstrações é que elas fornecem uma maneira simples de aplicar técnicas de Programação Orientada a Aspectos e aderir aos princípios SOLID .
Então você tem o seu IRepository
:
public interface IRepository<T>
where T : class
{
T Add(T entity);
void Delete(T entity);
IQueryable<T> AsQueryable();
}
E sua implementação:
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 fora do comum até agora, mas agora queremos adicionar um pouco de registro - fácil com um decorador de registro .
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();
}
}
Tudo pronto e sem alterações em nosso código existente . Existem inúmeras outras questões transversais que podemos adicionar, como tratamento de exceções, armazenamento em cache de dados, validação de dados ou qualquer outra coisa e, em todo o nosso processo de design e construção, a coisa mais valiosa que temos que nos permite adicionar recursos simples sem alterar nenhum de nosso código existente é a nossa IRepository
abstração .
Agora, muitas vezes eu vi esta pergunta no StackOverflow - “como você faz o Entity Framework funcionar em um ambiente multilocatário?”.
https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant
Se você tem uma Repository
abstração, a resposta é “é fácil adicionar um 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);
}
}
IMO, você deve sempre colocar uma abstração simples sobre qualquer componente de terceiros que será referenciado em mais de um punhado de lugares. Sob essa perspectiva, um ORM é o candidato perfeito, pois é referenciado em grande parte do nosso código.
A resposta que normalmente vem à mente quando alguém diz "por que eu deveria ter uma abstração (por exemplo Repository
) sobre esta ou aquela biblioteca de terceiros" é "por que você não faria?"
Os decoradores PS são extremamente simples de aplicar usando um recipiente 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>)));
}
}
}