저장소 패턴을 사용하지 않고 ORM을있는 그대로 사용 (EF)


95

저는 항상 Repository 패턴을 사용했지만 최근 프로젝트에서이 패턴과 "Unit Of Work"구현을 완벽하게 사용할 수 있는지 확인하고 싶었습니다. 땅을 파기 시작할수록 스스로에게 "정말 필요합니까?"라는 질문을하기 시작 했습니다.

이제이 모든 것은 그의 블로그에 Ayende Rahien의 게시물을 추적하여 Stackoverflow에 대한 몇 가지 의견으로 시작됩니다.

이것은 아마도 영원히 이야기 될 수 있으며 다른 응용 프로그램에 따라 다릅니다. 내가 알고 싶은 것,

  1. 이 접근 방식이 Entity Framework 프로젝트에 적합합니까?
  2. 이 접근 방식을 사용하면 비즈니스 로직이 여전히 서비스 계층 또는 확장 방법으로 이동합니까 (아래에서 설명했듯이 확장 방법은 NHib 세션을 사용하고 있음)?

확장 메서드를 사용하면 쉽게 수행 할 수 있습니다. 깨끗하고 간단하며 재사용이 가능합니다.

public static IEnumerable GetAll(
    this ISession instance, Expression<Func<T, bool>> where) where T : class
{
    return instance.QueryOver().Where(where).List();
}

이 접근 방식 Ninject을 DI로 사용 Context하여 인터페이스 를 만들고 컨트롤러에 삽입해야합니까?

답변:


103

나는 많은 길을 갔고 다른 프로젝트에서 많은 리포지토리 구현을 만들었고 ... 나는 수건을 던지고 포기했습니다. 그 이유가 여기에 있습니다.

예외에 대한 코딩

데이터베이스가 한 기술에서 다른 기술로 변경 될 확률 1 %에 대해 코딩합니까? 비즈니스의 미래 상태에 대해 생각하고 있고 예라고 대답하면 a) 다른 DB 기술로 마이그레이션 할 수있는 많은 돈이 있어야합니다. 또는 b) 재미를 위해 DB 기술을 선택하는 것입니다. ) 사용하기로 결정한 첫 번째 기술에 끔찍한 문제가 발생했습니다.

풍부한 LINQ 구문을 버리는 이유는 무엇입니까?

LINQ 및 EF는 개체 그래프를 읽고 순회하기 위해 깔끔한 작업을 수행 할 수 있도록 개발되었습니다. 동일한 유연성을 제공 할 수있는 저장소를 만들고 유지 관리하는 것은 엄청난 작업입니다. 내 경험상 저장소를 만들 때마다 쿼리의 성능을 높이거나 데이터베이스에 대한 적중 수를 줄이기 위해 항상 비즈니스 논리가 저장소 계층으로 유출되었습니다.

작성해야하는 쿼리의 모든 순열에 대한 메서드를 만들고 싶지 않습니다. 저장 프로 시저를 작성하는 것이 좋습니다. 나는 싶지 않아 GetOrder, GetOrderWithOrderItem, GetOrderWithOrderItemWithOrderActivity, GetOrderByUserId, 등 ... 난 그냥 주요 기업 트래버스를 얻기 위해 원하는대로 객체 그래프를 포함 나는 그렇게하십시오.

저장소의 대부분의 예는 헛소리입니다.

블로그 나 어떤 것을 개발하지 않는 한, 리포지토리 패턴을 둘러싼 인터넷에서 찾은 예제의 90 %만큼 간단하지 않을 것입니다. 나는 이것을 충분히 강조 할 수 없다! 이것은 알아 내기 위해 진흙 속을 기어 가야하는 것입니다. 당신이 만든 완벽하게 생각한 저장소 / 솔루션을 깨뜨리는 쿼리가 항상있을 것이고, 당신이 두 번째로 스스로를 추측하고 기술적 부채 / 침식이 시작될 때까지는 아닙니다.

날 단위 테스트하지 마

하지만 리포지토리가없는 경우 단위 테스트는 어떻습니까? 어떻게 조롱합니까? 간단하지 않습니다. 두 각도에서 살펴 보겠습니다.

리포지토리 없음- 또는 다른 트릭을 DbContext사용하여 모의 할 수 IDbContext있지만 쿼리가 런타임에 결정되기 때문에 실제로는 LINQ to 개체 가 아니라 LINQ to 개체를 단위 테스트합니다 . 이제 이것을 다루는 것은 통합 테스트에 달려 있습니다.

리포지토리 사용-이제 리포지토리를 모의하고 그 사이의 계층을 단위 테스트 할 수 있습니다. 좋아요? 글쎄요 ... 위의 경우 쿼리의 성능을 높이고 / 또는 데이터베이스에 대한 적중을 줄이기 위해 리포지토리 계층에 로직을 누출해야하는 경우, 단위 테스트에서 어떻게 처리 할 수 ​​있습니까? 이제 repo 레이어에 있으며 제대로 테스트하고 싶지 IQueryable<T>않습니까? 또한 솔직히 말해서 단위 테스트는 20 줄 .Where()절이 있는 쿼리를 다루지 않을 것입니다..Include()쿼리가 런타임에 생성되기 때문에 어쨌든 다른 모든 작업을 수행하기 위해 데이터베이스에 다시 연결됩니다. 또한 상위 계층 지속성을 무시하기 위해 저장소를 만들었으므로 이제 데이터베이스 기술을 변경하려는 경우 단위 테스트가 런타임에 동일한 결과를 보장하지 못해 통합 테스트로 돌아갑니다. 그래서 저장소 전체가 이상해 보입니다 ..

2 센트

일반 저장 프로 시저 (대량 삽입, 대량 삭제, CTE 등)보다 EF를 사용할 때 이미 많은 기능과 구문이 손실되었지만 C #으로 코딩도하므로 바이너리를 입력 할 필요가 없습니다. 우리는 EF를 사용하므로 다른 공급자를 사용하고 여러 가지 중에서 좋은 관련 방식으로 개체 그래프를 사용할 수 있습니다. 특정 추상화는 유용하지만 일부는 그렇지 않습니다.


17
단위 테스트를 위해 리포지토리를 만들지 않습니다. 비즈니스 로직 을 단위 테스트 할 수 있도록 리포지토리를 만듭니다 . 쿼리가 작동하는지 확인하는 방법은 비즈니스가 아닌 논리 만 포함하므로 저장소에 대한 통합 테스트를 작성하는 것이 훨씬 쉽습니다.
jgauffin

16
Coding for the exception: 저장소를 사용하면 데이터베이스 엔진을 전환 할 수 없습니다. 비즈니스를 끈기와 분리하는 것입니다.
jgauffin

2
이것들은 모두 그 뒤에 많은 진실을 가진 매우 유효한 포인트입니다. 그러나 부족한 것은 LINQ가 일관된 위치로 제한되는 대신 응용 프로그램에 흩어져 있다는 사실이 코드 숨김 페이지에서 SQL 호출에 해당하는 EF를 생성한다는 것입니다. 모든 LINQ 쿼리는 응용 프로그램의 잠재적 인 유지 관리 지점이며, 더 많을수록 (그리고 더 널리 퍼질수록) 유지 관리 비용과 위험이 높아집니다. 엔터티에 '삭제됨'플래그를 추가하고 엔터티가 쿼리되는 대형 애플리케이션의 모든 위치를 찾아야한다고 상상해보십시오. 각 위치를 수정해야합니다.
DVK

2
나는 이것이 근시안적이고 지쳐 있다고 생각합니다. 리포지토리에 로직을 유출하는 이유는 무엇입니까? 그랬다면 왜 중요할까요? 데이터 구현입니다. 우리가하는 일은 리포지토리 뒤에 숨겨 나머지 코드에서 LINQ를 차단하는 것입니다. 당신은 그것을 시험하지 않는다고 말하지만 그것을 시험 할 수 없다는 것을 반대하는 주장으로 사용합니다. 따라서 repo를 만들고 IQueryable을 노출하지 말고 테스트하지 마십시오. 최소한 데이터 구현과 분리하여 다른 모든 것을 테스트 할 수 있습니다. 그리고 db 변경의 1 % 확률은 여전히 ​​$ 현명하게 큰 것입니다.
Sinaesthetic

5
이 답변에 +1. Entity Framework Core에는 리포지토리가 실제로 필요하지 않습니다. 이 DbSet는 IS 저장소DbContext는 IS 작업 단위 . ORM이 이미 우리를 위해 그렇게하고 있는데 왜 리포지토리 패턴을 구현 하는가! 테스트를 위해 공급자를 InMemory. 그리고 테스트를 수행하십시오! MSDN에 잘 설명되어 있습니다.
Mohammed Noureldin

49

저장소 패턴은 추상화 입니다. 그 목적은 복잡성을 줄이고 나머지 코드를 무지하게 만드는 것입니다. 보너스 로 통합 테스트 대신 단위 테스트 를 작성할 수 있습니다 .

문제는 많은 개발자가 패턴 목적을 이해하지 못하고 호출자에게 지속성 특정 정보를 유출하는 저장소를 생성하지 못한다는 것입니다 (일반적으로 IQueryable<T>. 그렇게함으로써 그들은 OR / M을 직접 사용하는 것보다 혜택을받지 못합니다.

다른 답변을 해결하기 위해 업데이트

예외에 대한 코딩

저장소를 사용하는 것은 지속성 기술을 전환 할 수있는 것이 아닙니다 (예 : 데이터베이스 변경 또는 웹 서비스 사용 등). 복잡성과 결합을 줄이기 위해 비즈니스 로직을 지속성과 분리하는 것입니다.

단위 테스트와 통합 테스트

리포지토리에 대한 단위 테스트를 작성하지 않습니다. 기간.

그러나 리포지토리 (또는 지속성과 비즈니스 사이의 다른 추상화 계층)를 도입하여 비즈니스 논리에 대한 단위 테스트를 작성할 수 있습니다. 즉, 잘못 구성된 데이터베이스로 인해 테스트 실패에 대해 걱정할 필요가 없습니다.

쿼리에 관해서. LINQ를 사용하는 경우 리포지토리와 마찬가지로 쿼리가 작동하는지 확인해야합니다. 통합 테스트를 사용하여 수행됩니다.

차이점은 비즈니스를 LINQ 문과 혼합하지 않은 경우 실패한 것이 지속성 코드이고 다른 것이 아니라는 것을 100 % 확신 할 수 있다는 것입니다.

테스트를 분석하면 문제가 혼합되지 않은 경우 (예 : LINQ + 비즈니스 로직) 훨씬 더 깔끔하다는 것을 알 수 있습니다.

저장소 예

대부분의 예는 헛소리입니다. 그것은 매우 사실입니다. 그러나 디자인 패턴을 검색하면 많은 엉뚱한 예를 찾을 수 있습니다. 그것은 패턴 사용을 피할 이유가 아닙니다.

올바른 저장소 구현을 구축하는 것은 매우 쉽습니다. 사실, 당신은 단 하나의 규칙 만 따라야합니다 :

필요할 때까지 저장소 클래스에 아무것도 추가하지 마십시오.

코더의 많은 일반적인 저장소를 만들 게으른 시도하고 그들이 그 방법의 많은 기본 클래스를 사용할 있어야합니다. 야 그니. 리포지토리 클래스를 한 번 작성하고 응용 프로그램이 유지되는 동안 보관합니다 (년이 될 수 있음). 왜 게 으르면서 망쳐 버려. 기본 클래스 상속없이 깨끗하게 유지하십시오. 읽기 및 유지 관리가 훨씬 쉬워집니다.

(위의 진술은 지침이며 법률이 아닙니다. 기본 클래스는 동기를 부여 할 수 있습니다. 추가하기 전에 생각하여 올바른 이유에 추가하십시오)

오래된 물건

결론:

비즈니스 코드에 LINQ 문이 있거나 단위 테스트에 관심이 없다면 Entity Framework를 직접 사용하지 않을 이유가 없습니다.

최신 정보

저장소 패턴과 "추상화"가 실제로 의미하는 바에 대해 블로그를 작성했습니다. http://blog.gauffin.org/2013/01/repository-pattern-done-right/

업데이트 2

필드가 20 개 이상인 단일 항목 유형의 경우 모든 순열 조합을 지원하는 쿼리 방법을 어떻게 디자인할까요? 이름으로 만 검색을 제한하고 싶지는 않습니다. 탐색 속성으로 검색하는 것은 어떻습니까? 특정 가격 코드가있는 모든 주문 목록, 3 단계 탐색 속성 검색. IQueryable발명 된 모든 이유 는 데이터베이스에 대한 검색 조합을 구성 할 수 있기 때문입니다. 이론상 모든 것이 훌륭해 보이지만 사용자의 요구가 이론보다 우위에 있습니다.

다시 : 필드가 20 개 이상인 엔터티가 잘못 모델링되었습니다. 그것은 신의 존재입니다. 그것을 파괴.

나는 그것이 IQueryablequering을 위해 만들어진 것이 아니라고 주장하는 것이 아닙니다. 리포지토리 패턴과 같은 추상화 레이어 에는 누수가 있기 때문에 적합하지 않다는 것입니다 . 100 % 완전한 LINQ To Sql 공급자 (예 : EF)는 없습니다.

이들은 모두 eager / lazy 로딩을 사용하는 방법이나 SQL "IN"문을 수행하는 방법과 같은 구현 특정 사항을 가지고 있습니다. IQueryable저장소에 노출 되면 사용자는 이러한 모든 것을 알 수 있습니다. 따라서 데이터 소스를 추상화하려는 전체 시도는 완전한 실패입니다. OR / M을 직접 사용하는 것보다 이점을 얻지 않고 복잡성 만 추가하면됩니다.

리포지토리 패턴을 올바르게 구현하거나 아예 사용하지 마십시오.

(만약 큰 엔터티를 처리하고 싶다면 Repository 패턴을 Specification 패턴 과 결합 할 수 있습니다 . 이는 또한 테스트 가능한 완전한 추상화를 제공합니다.)


6
IQueryable을 노출하지 않으면 검색이 제한되고 사람들은 결국 다른 유형의 쿼리에 대해 더 많은 Get 메서드를 생성하게되고 결국 저장소가 더 복잡해집니다.
아카 쉬 카바

3
핵심 문제를 전혀 다루지 않았습니다. 저장소를 통해 IQueryable을 노출하는 것은 완전한 추상화가 아닙니다.
jgauffin 2013

1
실행되는 데 필요한 모든 인프라를 포함하는 쿼리 객체를 갖는 것이 그 자체로 imo로가는 길입니다. 검색어 인 필드를 제공하면 결과 목록이 반환됩니다. QO 내부에서 원하는 것은 무엇이든 할 수 있습니다. 그리고 그것은 인터페이스이기 때문에 쉽게 테스트 할 수 있습니다. 위의 내 게시물을 참조하십시오. 최고입니다.
h.alex

2
개인적으로 IQueryable <T> 인터페이스를 Repository 클래스의 멤버 중 하나에서 기본 집합을 노출하는 것보다 구현하는 것이 합리적이라고 생각합니다.
dark_perfect 2013-09-02

3
@yat : 집계 루트 당 하나의 저장소. 그러나 imho 그것은 집계 루트 및 집계 테이블이 아니라 집계 루트 및 집계 입니다. 실제 스토리지는 하나의 테이블 또는 많은 테이블을 사용할 수 있습니다. 즉, 각 집계와 테이블 간의 일대일 매핑이 아닐 수 있습니다. 리포지토리를 사용하여 복잡성을 줄이고 기본 스토리지의 종속성을 제거합니다.
jgauffin

27

IMO는 Repository추상화와 추상화 모두 UnitOfWork의미있는 개발에서 매우 가치있는 위치를 차지합니다. 사람들은 구현 세부 사항에 대해 논쟁 할 것입니다.하지만 고양이 피부를 만드는 방법이 많 듯이 추상화를 구현하는 방법도 많습니다.

귀하의 질문은 구체적으로 사용 여부와 그 이유입니다.

당신은 의심의 여지가 실현되지 것처럼 당신은 이미, 엔티티 프레임 워크에 내장 된 두 가지 패턴이 DbContext입니다 UnitOfWorkDbSet입니다 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>)));
        }
    }
}

11

우선, 일부 답변에서 제안했듯이 EF 자체는 저장소 패턴이므로 저장소 이름을 지정하기 위해 추가 추상화를 만들 필요가 없습니다.

단위 테스트를위한 Mockable Repository, 정말 필요합니까?

EF는 단위 테스트에서 테스트 DB와 통신하여 SQL 테스트 DB에 대해 비즈니스 논리를 직접 테스트 할 수 있습니다. 리포지토리 패턴을 모의하는 것의 이점은 전혀 없습니다. 테스트 데이터베이스에 대해 단위 테스트를 수행하는 것이 실제로 잘못된 것은 무엇입니까? 대량 작업이 불가능하므로 원시 SQL을 작성하게됩니다. 메모리의 SQLite는 실제 데이터베이스에 대한 단위 테스트를 수행하기에 완벽한 후보입니다.

불필요한 추상화

나중에 EF를 NHbibernate 등으로 쉽게 대체 할 수 있도록 저장소를 만들고 싶습니까? 훌륭한 계획처럼 들리지만 정말 비용 효율적입니까?

Linq가 단위 테스트를 죽입니까?

나는 그것이 어떻게 죽일 수 있는지에 대한 예를보고 싶습니다.

의존성 주입, IoC

와우, 이것들은 훌륭한 단어입니다. 이론적으로는 훌륭해 보이지만 때로는 훌륭한 디자인과 훌륭한 솔루션 사이의 균형을 선택해야합니다. 우리는 그 모든 것을 사용했고 결국 모든 것을 쓰레기통에 버리고 다른 접근 방식을 선택했습니다. 크기 대 속도 (코드 크기 및 개발 속도)는 실제 생활에서 매우 중요합니다. 사용자는 유연성이 필요합니다. DI 또는 IoC 측면에서 코드가 훌륭한 디자인인지 상관하지 않습니다.

Visual Studio를 빌드하지 않는 한

많은 사람들이 개발할 Visual Studio 또는 Eclipse와 같은 복잡한 프로그램을 빌드하고 고도로 사용자 정의 할 수 있어야하는 경우 이러한 모든 훌륭한 디자인이 필요합니다. 모든 훌륭한 개발 패턴은 수년간의 개발 끝에 등장했으며 이러한 IDE는이 모든 훌륭한 디자인 패턴이 매우 중요한 곳에서 진화했습니다. 그러나 간단한 웹 기반 급여 또는 간단한 비즈니스 앱을 수행하는 경우 100 만 명의 사용자에게만 배포되는 백만 사용자를 위해 빌드하는 데 시간을 소비하는 대신 시간에 따라 개발을 진행하는 것이 좋습니다.

필터링 된보기로서의 저장소-ISecureRepository

반면에 저장소는 현재 사용자 / 역할을 기반으로 필요한 필러를 적용하여 데이터에 대한 액세스를 보호하는 EF의 필터링 된보기 여야합니다.

그러나 이렇게하면 유지 관리해야 할 거대한 코드 기반으로 끝나기 때문에 저장소가 훨씬 더 복잡해집니다. 사람들은 서로 다른 사용자 유형 또는 엔티티 유형 조합에 대해 서로 다른 저장소를 생성하게됩니다. 뿐만 아니라 우리는 많은 DTO로 끝납니다.

다음 답변은 전체 클래스 및 메서드 집합을 생성하지 않고 Filtered Repository를 구현 한 예입니다. 질문에 직접 답할 수는 없지만 도출하는 데 유용 할 수 있습니다.

면책 조항 : 저는 Entity REST SDK의 작성자입니다.

http://entityrestsdk.codeplex.com

위의 사항을 염두에두고 CRUD 작업을위한 필터를 보유하는 SecurityContext를 기반으로 필터링 된 뷰의 저장소를 생성하는 SDK를 개발했습니다. 그리고 두 종류의 규칙 만이 복잡한 작업을 단순화합니다. 첫 번째는 엔티티에 대한 액세스이고 다른 하나는 속성에 대한 읽기 / 쓰기 규칙입니다.

장점은 다른 사용자 유형에 대해 비즈니스 로직 또는 리포지토리를 다시 작성하지 않고 단순히 액세스를 차단하거나 부여 할 수 있다는 것입니다.

public class DefaultSecurityContext : BaseSecurityContext {

  public static DefaultSecurityContext Instance = new DefaultSecurityContext();

  // UserID for currently logged in User
  public static long UserID{
       get{
             return long.Parse( HttpContext.Current.User.Identity.Name );
       }
  }

  public DefaultSecurityContext(){
  }

  protected override void OnCreate(){

        // User can access his own Account only
        var acc = CreateRules<Account>();

        acc.SetRead( y => x=> x.AccountID == UserID ) ;
        acc.SetWrite( y => x=> x.AccountID == UserID );

        // User can only modify AccountName and EmailAddress fields
        acc.SetProperties( SecurityRules.ReadWrite, 
              x => x.AccountName,
              x => x.EmailAddress);

        // User can read AccountType field
        acc.SetProperties<Account>( SecurityRules.Read, 
              x => x.AccountType);

        // User can access his own Orders only
        var order = CreateRules<Order>();
        order.SetRead( y => x => x.CustomerID == UserID );

        // User can modify Order only if OrderStatus is not complete
        order.SetWrite( y => x => x.CustomerID == UserID 
            && x.OrderStatus != "Complete" );

        // User can only modify OrderNotes and OrderStatus
        order.SetProperties( SecurityRules.ReadWrite, 
              x => x.OrderNotes,
              x => x.OrderStatus );

        // User can not delete orders
        order.SetDelete(order.NotSupportedRule);
  }
}

이러한 LINQ 규칙은 모든 작업에 대해 SaveChanges 메서드의 데이터베이스에 대해 평가되며 이러한 규칙은 데이터베이스 앞에서 방화벽 역할을합니다.


3
DB에 대한 단위 테스트는 테스트에 대한 추가 외부 요구 사항이 있음을 의미합니다. 해당 DB가 다운되거나 데이터가 지워지거나 해당 DB에 어떤 일이 발생하면 테스트가 실패합니다. 이것은 바람직하지 않습니다. IQueryable을 노출하는 리포지토리는 설정하는 데 약 2 분이 걸립니다. 여기에 시간 낭비가 없습니다. DI는 왜 당신에게 오랜 시간이 걸렸습니까? 이 모든 작업에는 몇 분이 걸립니다. 이 모든 것이 내 서비스 계층에서 복잡한 쿼리를 단위 테스트하는 데 효과적이라고 말할 것입니다. 연결할 데이터베이스가 필요하지 않아서 좋았습니다. nuget에서 조롱 프레임 워크를 가져 오는 데 약 1 분이 걸렸습니다. 이 물건은 시간이 걸리지 않습니다.
user441521 2014-06-26

@ user441521 IQueryable이있는 저장소를 설정하는 데 2 ​​분이 걸리나요? 당신이 살고있는 세계, 우리 라이브 사이트의 모든 asp.net 요청은 밀리 초 이내에 제공됩니다. 조롱과 위조 등은 코드를 더 복잡하게 만들고 총 시간 낭비입니다. 단위 테스트는 단위가 비즈니스 논리 단위로 정의되지 않은 경우 쓸모가 없습니다.
Akash Kava

7

어떤 방법이 옳은지에 대한 많은 논쟁이 있기 때문에 둘 다 수용 가능하다고 생각하므로 내가 가장 좋아하는 것을 사용합니다 (저장소가 아닌 UoW).

EF에서 UoW는 DbContext를 통해 구현되고 DbSet은 리포지토리입니다.

데이터 레이어로 작업하는 방법은 DbContext 개체에서 직접 작업하고 복잡한 쿼리의 경우 재사용 할 수있는 쿼리에 대한 확장 메서드를 만들 것입니다.

Ayende는 CUD 작업을 추상화하는 것이 얼마나 나쁜지에 대한 게시물도 가지고 있다고 생각합니다.

나는 항상 인터페이스를 만들고 내 컨텍스트를 상속 받으므로 DI에 IoC 컨테이너를 사용할 수 있습니다.


확장 방법은 얼마나 광범위합니까? 내 확장에서 다른 엔티티의 상태를 가져와야한다고 가정 해 보겠습니다. 이것이 바로 지금 저의 가장 큰 관심사입니다. 확장 방법의 몇 가지 예를 보여 주시겠습니까?
Dejan.S

ayende.com/blog/153473/...ayende.com/blog/153569/... . (이들은 s # arp lite라고 불리는 아키텍처 (Framework?)에 대한 리뷰입니다. 대부분 좋지만 그는 저장소와 CUD 추상화에 동의하지 않습니다).
Josh

NHibernate 기반. EF를 사용한 예가 없습니까? 그리고 또 다른 엔터티를 호출해야 할 때 정적 확장 메서드에서 가장 좋은 방법은 무엇입니까?
Dejan.S 2013 년

3
데이터베이스에 저장되지 않은 데이터로 도메인 개체의 속성을 수화해야 할 때까지는이 모든 것이 좋습니다. 또는 부풀어 오른 ORM보다 성능이 더 뛰어난 기술을 선택해야합니다. 죄송합니다! ORM은 단순히 저장소를 대체하는 것이 아니라 하나의 구현 세부 사항입니다.
cdaq 2013 년

2

EF에 가장 많이 적용되는 것은 리포지토리 패턴이 아닙니다. 이것은 Facade 패턴입니다 (EF 메서드에 대한 호출을 더 간단하고 사용하기 쉬운 버전으로 요약).

EF는 리포지토리 패턴 (및 작업 단위 패턴)을 적용하는 것입니다. 즉, EF는 데이터 액세스 계층을 추상화하여 사용자가 SQLServer를 처리하고 있다는 것을 알지 못하도록하는 것입니다.

그리고 EF에 대한 대부분의 "리포지토리"는 동일한 서명을 갖는 지점까지도 EF의 단일 메서드에 매우 간단하게 매핑하기 때문에 좋은 외관도 아닙니다.

따라서이 소위 "리포지토리"패턴을 EF에 적용하는 두 가지 이유는보다 쉬운 테스트를 허용하고 이에 대한 "미리 준비된"호출의 하위 집합을 설정하기위한 것입니다. 그 자체로는 나쁘지 않지만 분명히 저장소는 아닙니다.


1

Linq는 요즘 'Repository'입니다.

ISession + Linq는 이미 저장소이며 GetXByY메소드 나 QueryData(Query q)일반화 가 필요하지 않습니다 . DAL 사용에 약간의 편집증이 있기 때문에 여전히 저장소 인터페이스를 선호합니다. (유지 보수성의 관점에서 우리는 또한 특정 데이터 액세스 인터페이스에 대한 일부 외관을 가지고 있어야합니다).

여기에 우리가 사용하는 저장소가 있습니다. nhibernate의 직접적인 사용에서 우리를 분리하지만 linq 인터페이스를 제공합니다 (예외적 인 경우 ISession 액세스로, 결국 리팩터링 될 수 있음).

class Repo
{
    ISession _session; //via ioc
    IQueryable<T> Query()
    {
        return _session.Query<T>();
    }
}

서비스 계층을 위해 무엇을합니까?
Dejan.S

컨트롤러는 리포지토리에서 읽기 전용 데이터를 쿼리하는데 왜 추가 레이어를 추가합니까? 다른 가능성은 점점 더 서비스 수준 저장소 인 "ContentService"를 사용하는 것입니다 : GetXByY 등. 수정 작업의 경우 애플리케이션 서비스는 사용 사례에 대한 추상화
일뿐입니다

저는 비즈니스 로직을위한 서비스 레이어를 사용하는 데 익숙합니다. ContentService로 당신을 따르는 지 잘 모르겠습니다. 자세히 설명 해주세요. 헬퍼 클래스를 "서비스 계층"으로하는 것이 나쁜 습관일까요?
Dejan.S

'서비스 레이어'는 '애플리케이션 서비스'를 의미합니다. 그들은 저장소와 도메인 레이어의 다른 공용 부분을 사용할 수 있습니다. "서비스 계층"은 나쁜 습관은 아니지만 List <X> 결과를 ​​제공하기 위해 XService 클래스를 만드는 것은 피하고 싶습니다. 댓글 입력란이 너무 짧아 서비스를 자세히 설명 할 수 없습니다. 죄송합니다.
mikalai

장바구니 계산을 가정하고 계산을 위해 애플리케이션 설정 매개 변수와 특정 고객 매개 변수를 가져와야하는데, 이는 애플리케이션의 여러 위치에서 재사용됩니다. 그 상황을 어떻게 처리합니까? 도우미 클래스 또는 응용 프로그램 서비스?
Dejan.S 2013 년

1

저장소 나 영속 계층을 멀리 추상화에 대해 대부분이다이 시간에 (또는 단 하나를 선택 그것을 호출).

나는 그것을 쿼리 객체와 결합하여 사용하므로 애플리케이션의 특정 기술과 결합하지 않습니다. 또한 테스트를 많이 용이하게합니다.

그래서 나는

public interface IRepository : IDisposable
{
    void Save<TEntity>(TEntity entity);
    void SaveList<TEntity>(IEnumerable<TEntity> entities);

    void Delete<TEntity>(TEntity entity);
    void DeleteList<TEntity>(IEnumerable<TEntity> entities);

    IList<TEntity> GetAll<TEntity>() where TEntity : class;
    int GetCount<TEntity>() where TEntity : class;

    void StartConversation();
    void EndConversation();

    //if query objects can be self sustaining (i.e. not need additional configuration - think session), there is no need to include this method in the repository.
    TResult ExecuteQuery<TResult>(IQueryObject<TResult> query);
}

콜백이있는 비동기 메서드를 대리인으로 추가 할 수 있습니다. 리포지토리는 일반적으로 구현하기 쉽기 때문에 앱에서 앱으로 구현 라인을 건드릴 수 없습니다. 음, 적어도 NH를 사용할 때는 사실입니다. EF에서도했지만 EF를 싫어하게 만들었습니다. 4. 대화는 거래의 시작입니다. 몇몇 클래스가 저장소 인스턴스를 공유하는 경우 매우 좋습니다. 또한 NH의 경우 내 구현의 하나의 저장소는 첫 번째 요청에서 열리는 하나의 세션과 같습니다.

그런 다음 쿼리 개체

public interface IQueryObject<TResult>
{
    /// <summary>Provides configuration options.</summary>
    /// <remarks>
    /// If the query object is used through a repository this method might or might not be called depending on the particular implementation of a repository.
    /// If not used through a repository, it can be useful as a configuration option.
    /// </remarks>
    void Configure(object parameter);

    /// <summary>Implementation of the query.</summary>
    TResult GetResult();
}

구성의 경우 NH에서만 ISession을 전달합니다. EF에서는 다소 의미가 없습니다.

예제 쿼리는 다음과 같습니다 .. (NH)

public class GetAll<TEntity> : AbstractQueryObject<IList<TEntity>>
    where TEntity : class
{
    public override IList<TEntity> GetResult()
    {
        return this.Session.CreateCriteria<TEntity>().List<TEntity>();
    }
}

EF 쿼리를 수행하려면 세션이 아닌 추상 기반에 컨텍스트가 있어야합니다. 그러나 물론 ifc는 동일합니다.

이러한 방식으로 쿼리 자체가 캡슐화되고 쉽게 테스트 할 수 있습니다. 무엇보다도 내 코드는 인터페이스에만 의존합니다. 모든 것이 매우 깨끗합니다. 도메인 (비즈니스) 개체는 단지 테스트 할 수없는 활성 레코드 패턴을 사용할 때와 같은 책임이 혼합되지 않고 도메인 개체에 데이터 액세스 (쿼리) 코드가 혼합되어있는 경우와 같은 책임이 혼합되어 있지 않습니다. 그 자체??). 누구나 데이터 전송을 위해 POCO를 생성 할 수 있습니다.

대체로이 접근 방식은 내가 상상할 수있는 어떤 것도 손실하지 않고 많은 코드 재사용과 단순성을 제공합니다. 어떤 아이디어?

그리고 Ayende의 훌륭한 게시물과 지속적인 헌신에 감사드립니다. 여기에 그의 아이디어 (쿼리 객체)가 아닙니다.


1
지속성 엔터티 (귀하의 POCO)는 비즈니스 / 도메인 엔터티가 아닙니다. 저장소의 목적은 비즈니스 (모든) 계층을 지속성에서 분리하는 것입니다.
MikeSW

나는 커플 링을 보지 못한다. POCO 부분에 다소 동의하지만 상관하지 않습니다. '정품'POCO를 보유하고 여전히이 접근 방식을 사용하는 것을 막을 수는 없습니다.
h.alex

1
엔티티가 멍청한 POCO 일 필요는 없습니다. 사실, 비즈니스 로직을 엔티티로 모델링하는 것은 DDD 군중이 항상하는 일입니다. 이 개발 스타일은 NH 또는 EF와 매우 잘 어울립니다.
chris

1

나에게 그것은 상대적으로 적은 요인을 가진 간단한 결정입니다. 요인은 다음과 같습니다.

  1. 저장소는 도메인 클래스 용입니다.
  2. 내 앱 중 일부에서 도메인 클래스는 내 지속성 (DAL) 클래스와 동일하지만 다른 앱에서는 그렇지 않습니다.
  3. 동일 할 때 EF는 이미 리포지토리를 제공하고 있습니다.
  4. EF는 지연로드 및 IQueryable을 제공합니다. 나는 이것들을 좋아한다.
  5. EF를 통한 리포지토리 추상화 / 'facading'/ 재 구현은 일반적으로 lazy 및 IQueryable 손실을 의미합니다.

따라서 내 앱이 # 2, 별도의 도메인 및 데이터 모델을 정당화 할 수 없다면 일반적으로 # 5는 신경 쓰지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.