현대 ORM (EF, nHibernate)에 리포지토리 패턴이 과도하게 사용된다면 더 나은 추상화는 무엇입니까?


12

최근에는 작업 단위 기능과 함께 저장소와 같은 기능을 통합하기 때문에 강력한 Entity Framework와 같은 강력한 ORM과 함께 저장소 패턴을 사용하는 것에 대한 많은 주장을 읽었습니다.

단위 테스트와 같은 상황에서 패턴을 사용하지 않는 것에 대한 또 다른 주장은 더 일반적인 구현은 IQueryable을 활용하기 때문에 저장소 패턴이 누출되는 것입니다.

저장소 패턴을 사용하는 것에 대한 논쟁은 나에게 의미가 있지만 제안 된 대체 추상화 방법은 종종 더 혼란스럽고 문제만큼 과도하게 보입니다.

Jimmy Bogards 솔루션은 추상화를 날려 버리고 자신의 아키텍처를 소개하는 것 같습니다. https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

리포지토리의 다른 예는 불필요하게 ....하지만 내 아키텍처를 사용하십시오! http://blog.gauffin.org/2012/10/22/griffin-decoupled-the-queries/

또 다른 ... http://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework

더 복잡하지 않은 "과도하게 복잡한"리포지토리 패턴 접근 방식에 대한 명확한 대체 또는 대안을 찾지 못했습니다.


4
구체적으로 달성하려는 것은 무엇입니까? 추상화에는 목적이 있어야합니다. CRUD 앱을 작성하는 경우 ORM은 추상적 일 것입니다.
JacquesB

@JacquesB 나는 강력한 도메인 모델에서 객체 관계 임피던스 문제를 피하려고 노력하고 있지만 mvc 구현에서 내 뷰 모델과는 거리가 있습니다.
AnotherDeveloper

리드 Cospey 여기 IQuerable에 대해 말을 긍정적 인 것들을 많이 있습니다 stackoverflow.com/questions/1578778/using-iqueryable-with-linq 이 더 나은 전송 계층에서의 의미합니다. 추상화에 관해서는 EntityType을 주입해야하지만 여전히 일반적인 메소드를 유지하려고 할 때 Generic Repository 패턴이 잘 작동한다는 것을 알았습니다. 반면에, 나는 스스로 MSDN LINQ 포럼에서 EF가 메모리에 있기 때문에 저장소 패턴이라고 주장했습니다. 한 프로젝트는 수많은 Where 절을 메서드 호출로 사용하여 잘 작동했습니다.
John Peters

그러나 내가 보았지만 거부 한 영역은 Expression Tree 비즈니스였습니다. 일부는 정말 좋아하지만 유용하다고 생각하기 전에 열심히 공부해야합니다.
John Peters

2
컨트롤러에서 SQL 호출로 돌아 가야합니다
bobek

답변:


12

리포지토리와 일반 리포지토리를 병합하고 있다고 생각합니다.

기본 저장소는 데이터 저장소를 인터페이스하고 데이터를 반환하는 메소드를 제공합니다.

IRepository {
   List<Data> GetDataById(string id);
}

IQueryable 또는 임의의 쿼리를 전달하는 다른 방법을 통해 데이터 계층을 코드로 유출하지 않으며 잘 정의되고 테스트 가능하고 주입 가능한 메소드 표면을 제공합니다.

일반 리포지토리를 사용하면 ORM처럼 쿼리를 전달할 수 있습니다

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
    //or
    IQueryable<T> Get<T>();
}

나는 기본적으로 또 다른 Generic Repository 인 ORM 위에 Generic Repository를 사용하는 것이별로 중요하지 않다는 데 동의합니다.

대답은 기본 리포지토리 패턴을 사용하여 ORM을 숨기는 것입니다


1
ORM의 ORM? 나는 웃기려고 노력했다. ORM을 추상화해야하는 이유는 무엇입니까?
johnny

1
같은 이유로 당신은 아무것도 추상화하지 않습니다. 독점 클래스로 코드를 오염시키지 않기 위해
Ewan

6

언급 한 대부분의 주장은 가지고 있지 않은 리포지토리 패턴 기능에 대한 잘못된 속성입니다.

개념적으로 DDD에 원래 정의 된 리포지토리는 검색하거나 추가 할 수있는 개체의 모음 일뿐입니다. 그 배후의 지속 메커니즘은 추상화되어 있으므로 소비자는 메모리 내 컬렉션이라는 환상을 얻습니다.

유출 추상화 ( IQueryables예 : 노출 )가있는 리포지토리 구현은 잘못된 리포지토리 구현입니다.

단순한 콜렉션 조작 (예 : 작업 단위 (UOW) 기능) 이상을 노출하는 저장소 구현은 저장소 구현이 좋지 않습니다.

데이터 액세스를위한 리포지토리의 대안이 있습니까? 예,하지만 질문에서 제기 한 문제와 관련이 없습니다.



1

저에게 ORM 또는 다른 DB 지속성 계층과 결합 된 리포지토리에는 다음과 같은 단점이 있습니다.

  1. 작업 단위를 커버. UoW는 프로그래머가 코딩해야하며 백그라운드에서 일종의 마술로 구현되는 경우는 거의 없습니다. 사용자는 UoW 경계 및 커밋 지점을 정의하지 않고 단순히 쿼리 및 수정을 수행합니다. 때로는 각 리포지토리 액세스 방법에서 UoW를 마이크로 UoW (예 : NHibernate 세션)로 줄여서 포기합니다.
  2. 지속성 무지 (Current Pergistence Ignorance)를 덮거나 최악의 경우 : "Load ()", "Get ()", "Save ()"또는 "Update ()"와 같은 메서드는 개별 개체를 보내는 것처럼 즉각적인 단일 개체 작업을 제안합니다 SQL / DML 또는 파일로 작업하는 것처럼. 실제로, 예를 들어, 이러한 잘못된 이름을 가진 NHibernate 메소드는 일반적으로 개별 액세스를하지 않지만 지연로드 또는 삽입 / 업데이트 배치 (지속성 무지)를 위해 대기합니다. 때때로 프로그래머는 왜 즉각적인 DB 작업을 수행하지 않고 지속성 무지를 강제로 중단하여 성능을 저하시키고 실제로 시스템을 악화시키기 위해 많은 노력을 기울입니다.
  3. 통제되지 않은 성장. 간단한 저장소는 특정 요구에 맞게 점점 더 많은 방법을 축적 할 수 있습니다.

같은 :

public interface ICarsRepository  /* initial */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); // bad, should be for multiple IDs.
    void SaveCar(ICar carToSave); // bad, no individual saves, use UoW commit!
}

public interface ICarsRepository  /* a few years later */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); 
    IList<ICar> GetBlueCars();
    IList<ICar> GetRedYellowGreenCars();
    IList<ICar> GetCarsByColor(Color colorOfCars); // a bit better
    IList<ICar> GetCarsByColor(IEnumerable<Color> colorsOfCars); // better!
    IList<ICar> GetCarsWithPowerBetween(int hpFrom, int hpTo);
    IList<ICar> GetCarsWithPowerKwBetween(int kwFrom, int kwTo);
    IList<ICar> GetCarsBuiltBetween(int yearFrom, int yearTo);
    IList<ICar> GetCarsBuiltBetween(DateTime from, DateTime to); // some also need month and day
    IList<ICar> GetHybridCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetElectricCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetCarsFromManufacturer(IManufacturer carManufacturer); 
    bool HasCarMeanwhileBeenChangedBySomebodyElseInDb(ICar car); // persistence ignorance broken
    void SaveCar(ICar carToSave);
}

4. 신의 위험 개체 : 모든 모델 또는 데이터 액세스 계층을 포함하는 하나의 신 클래스를 만들려고 할 수 있습니다. 저장소 클래스는 Car 메소드뿐만 아니라 모든 엔티티에 대한 메소드를 포함합니다.

내 의견으로는, 많은 단일 목적 방법의 큰 혼란을 피하기 위해 적어도 쿼리 기회를 제공하는 것이 좋습니다. 그것이 LINQ이든, 자체 쿼리 언어이거나 ORM에서 직접 가져온 것 (OK, kind of coupling problem ...)이든 상관 없습니다.


4
리포지토리 패턴을 사용하지 않는 경우 이러한 모든 방법은 어디에 있습니까?
johnny


1

저장소 인터페이스의 목적에 대한 데이터베이스를 멀리 조롱하는 경우, 유닛 테스트 (= 분리에서 테스트) 최고의 추상화 조롱하기 쉬운 무언가이다.

IQueryable 결과를 기반으로하는 리포지토리 인터페이스를 조롱하는 것은 어렵습니다.

단위 테스트 관점에서

IRepository {
   List<Data> GetDataById(string id);
}

쉽게 조롱 할 수 있습니다

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
}

모의가 쿼리 매개 변수의 내용을 무시하는 경우에만 쉽게 조롱 할 수 있습니다.

IGenericRepository<T> {
    IQueryable<T> Get<T>(some_parameters);
}

쉽게 조롱 할 수 없다


0

쿼리에 람다 함수를 사용하면 리포지토리 패턴이 과도하다고 생각하지 않습니다. 특히 ORM을 추상화해야 할 때 (제 생각에는 항상 있어야합니다) 저장소 자체의 구현 세부 정보는 신경 쓰지 않습니다.

예를 들면 다음과 같습니다.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        UserRepository ur = new UserRepository();
        var userWithA = ur.GetBy(u => u.Name.StartsWith("A"));

        Console.WriteLine(userWithA.Name);


        ur.GetAllBy(u => u.Name.StartsWith("M"))
          .ForEach(u => Console.WriteLine(u.Name));


        ur.GetAllBy(u => u.Age > 13)
          .ForEach(u => Console.WriteLine(u.Name));
    }
}

public class UserRepository 
{
    List<User> users = new List<User> { 
        new User{Name="Joe", Age=10},
            new User{Name="Allen", Age=12},
            new User{Name="Martin", Age=14},
            new User{Name="Mary", Age=15},
            new User{Name="Ashton", Age=29}
    };

    public User GetBy(Predicate<User> userPredicate)
    {
        return users.Find(userPredicate);
    }

    public List<User> GetAllBy(Predicate<User> userPredicate)
    {
        return users.FindAll(userPredicate);
    }
}

public class User
{
    public string Name { get; set; }

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