IMO는 Repository
추상화와 추상화 모두 UnitOfWork
의미있는 개발에서 매우 가치있는 위치를 차지합니다. 사람들은 구현 세부 사항에 대해 논쟁 할 것입니다.하지만 고양이 피부를 만드는 방법이 많 듯이 추상화를 구현하는 방법도 많습니다.
귀하의 질문은 구체적으로 사용 여부와 그 이유입니다.
당신은 의심의 여지가 실현되지 것처럼 당신은 이미, 엔티티 프레임 워크에 내장 된 두 가지 패턴이 DbContext
입니다 UnitOfWork
및 DbSet
입니다 Repository
. 당신은 일반적으로 단위 테스트에 필요하지 않습니다 UnitOfWork
또는 Repository
그들은 단순히 수업 및 기본 데이터 액세스 구현에 용이하게되어 스스로. 서비스의 논리를 단위 테스트 할 때이 두 가지 추상화를 모방하는 것이 반복해서 수행해야하는 작업입니다.
테스트를 수행하는 로직과 테스트중인 로직 사이 에 코드 종속성 레이어 (제어하지 않는) 를 추가하는 외부 라이브러리를 사용하여 모의, 가짜 또는 무엇이든 할 수 있습니다 .
작은 포인트 그래서 있다는 것입니다 에 대한 자신의 추상화를 가진 UnitOfWork
하고하는 Repository
단위 테스트를 조롱 할 때 최대의 제어와 유연성을 제공합니다.
모두 훌륭하지만, 이러한 추상화의 진정한 힘은 Aspect Oriented Programming 기술 을 적용 하고 SOLID 원칙을 고수 하는 간단한 방법을 제공한다는 것 입니다.
그래서 당신은 당신의 IRepository
:
public interface IRepository<T>
where T : class
{
T Add(T entity);
void Delete(T entity);
IQueryable<T> AsQueryable();
}
그리고 그 구현 :
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();
}
}
지금까지는 평범하지 않지만 이제 로깅 데코레이터를 사용하여 로깅을 쉽게 추가하고 싶습니다 .
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();
}
}
모두 완료되었으며 기존 코드는 변경되지 않았습니다 . 예외 처리, 데이터 캐싱, 데이터 유효성 검사 등과 같이 추가 할 수있는 다른 교차 절단 문제가 많이 있으며, 설계 및 빌드 프로세스 전반에 걸쳐 기존 코드를 변경하지 않고도 간단한 기능을 추가 할 수있는 가장 가치있는 것입니다. 우리의 IRepository
추상화 입니다.
이제 저는 StackOverflow에서 "멀티 테넌트 환경에서 Entity Framework가 작동하도록 만드는 방법은 무엇입니까?"라는 질문을 여러 번 보았습니다.
https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant
Repository
추상화 가 있다면 대답은 "데코레이터를 추가하는 것은 쉽습니다"입니다.
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는 항상 몇 군데 이상에서 참조되는 타사 구성 요소에 대해 간단한 추상화를 배치해야합니다. 이러한 관점에서 ORM은 우리 코드의 많은 부분에서 참조되는 완벽한 후보입니다.
누군가 "왜이 Repository
라이브러리 나 타사 라이브러리에 대해 추상화 (예 :)를 가져야합니까?"라고 말할 때 일반적으로 떠오르는 대답 은 "왜 그렇지 않습니까?"입니다.
PS 데코레이터는 SimpleInjector 와 같은 IoC 컨테이너를 사용하여 적용하는 것이 매우 간단합니다 .
[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>)));
}
}
}