ORM 로직 캡슐화를위한 리포지토리 패턴의 대안?


24

방금 ORM을 전환해야했는데 쿼리 논리가 어디에서나 유출되기 때문에 비교적 어려운 작업이었습니다. 새로운 응용 프로그램을 개발해야한다면 개인적으로 선호하는 것은 ORM을 사용하여 모든 쿼리 논리를 캡슐화하여 변경을 위해 미래를 보장하는 것입니다. 리포지토리 패턴은 코딩 및 유지 관리가 매우 까다로워서 문제를 해결할 다른 패턴이 있는지 궁금합니다.

실제로 필요하기 전에 추가 복잡성을 추가하지 않고 민첩한 등의 게시물을 예견 할 수는 있지만 기존 패턴 만 유사한 문제를 더 간단한 방법으로 해결하는 데 관심이 있습니다.

내 첫 번째 생각은 확장 유형 메소드를 통해 특정 유형 저장소 클래스에 필요한 메소드를 추가하는 일반 유형 저장소를 갖는 것이었지만 정적 메소드를 테스트하는 것은 끔찍한 고통입니다. IE :

public static class PersonExtensions
{
    public static IEnumerable<Person> GetRetiredPeople(this IRepository<Person> personRep)
    {
        // logic
    }
}

5
코드 작성 및 유지 관리에 어려움이있는 리포지토리 패턴은 정확히 무엇입니까?
Matthew Flynn

1
대안이 있습니다. 그중 하나는 Query Object 패턴을 사용하는 것인데, 비록 사용하지는 않았지만 좋은 접근법처럼 보입니다. 리포지토리 IMHO는 올바르게 사용되었지만 모두가 아니라 모든 것을
끝내면

답변:


14

우선 : 일반 저장소는 완전한 구현이 아닌 기본 클래스로 간주해야합니다. 일반적인 CRUD 방법을 마련하는 데 도움이됩니다. 그러나 여전히 쿼리 메소드를 구현해야합니다.

public class UserRepository : EntityFrameworkRepository<User>, IUserRepository
{
    public UserRepository(DbContext context){}

    public ICollection<Person> FindRetired()
    {
        return context.Persons.Where(p => p.Status == "Retired").ToList();
    }
}

둘째:

돌아 오지 마십시오 IQueryable. 새는 추상화입니다. 해당 인터페이스를 직접 구현하거나 기본 DB 제공자에 대한 지식없이 사용하십시오. 예를 들어, 모든 DB 공급자는 엔티티를 간절히로드하기위한 자체 API를 가지고 있습니다. 그렇기 때문에 누설이 많은 추상화입니다.

대안

대안으로 쿼리를 사용할 수 있습니다 (예 : 명령 / 쿼리 분리 패턴에 정의 된대로). CQS는 Wikipedia에 설명되어 있습니다.

기본적으로 호출하는 쿼리 클래스를 작성하십시오.

public class GetMyMessages
{
    public GetMyMessages(IDbConnection connection)
    {
    }


    public Message[] Execute(DateTime minDate)
    {
        //query
    }
}

쿼리의 장점은 코드를 어지럽히 지 않고 다른 쿼리에 대해 다른 데이터 액세스 방법을 사용할 수 있다는 것입니다. 예를 들어 하나의 웹 서비스를 사용할 수 있으며 다른 하나는 최대 절전 모드이고 다른 하나는 ADO.NET입니다.

.NET 구현에 관심이 있다면 내 기사를 읽으십시오 : http://blog.gauffin.org/2012/10/griffin-decoupled-the-queries/


대안 패턴이 아닌가? 리포지토리 패턴의 메소드가 이제 전체 클래스로 간주되는 큰 프로젝트에서 더 많은 혼란을 만들 것을 제안 했습니까?
단테

3
작은 클래스를 갖는 것에 대한 정의가 복잡하다면, 그렇습니다. 이 경우 SOLID 원칙을 적용하면 항상 더 많은 (작은) 수업이 생성되므로 SOLID 원칙을 따르지 않아야합니다.
jgauffin 2016 년

2
더 작은 클래스가 더 좋지만, 필요한 기능이 있는지 여부를 확인하기 위해 (도메인) 리포지토리의 인텔리전스를 탐색하는 것보다 기존 쿼리 클래스를 탐색하는 것이 더 번거롭지 않습니다.
단테

4
프로젝트 구조가 더욱 중요해집니다. 모든 쿼리를 네임 스페이스에 넣으면 YourProject.YourDomainModelName.Queries쉽게 탐색 할 수 있습니다.
jgauffin 2016 년

CQS를 사용하여 좋은 방법으로 찾기 어려운 것은 일반적인 쿼리입니다. 예를 들어, 사용자에게 하나의 쿼리는 관련된 모든 학생 (diff grades, own children 등)을 가져옵니다. 이제 다른 쿼리는 사용자의 자식을 가져 와서 일부 작업을 수행해야하므로 쿼리 2는 쿼리 1의 공통 논리를 활용할 수 있지만 간단한 방법은 없습니다. 이것을 쉽게 할 수있는 CQS 구현을 찾을 수 없었습니다. 리포지토리는 이와 관련하여 많은 도움이됩니다. 어느 패턴의 팬이 아니라 내 테이크를 공유합니다.
Mr.

8

먼저 ORM이 이미 데이터베이스를 충분히 추상화했다고 생각합니다. 일부 ORM은 모든 공통 관계형 데이터베이스에 바인딩을 제공합니다 (NHibernate는 MSSQL, Oracle, MySQL, Postgress 등에 바인딩을 가짐). 따라서 새로운 추상화를 구축하는 것이 수익성이없는 것 같습니다. 또한이 ORM을 "추상화"해야한다는 주장은 의미가 없습니다.

이 추상화를 계속 만들고 싶다면 리포지토리 패턴에 반대합니다. 순수한 SQL 시대에는 좋았지 만 현대 ORM에 직면하면 상당히 번거 롭습니다. 주로 CRUD 작업 및 쿼리와 같은 대부분의 ORM 기능을 다시 구현하기 때문입니다.

이러한 추상화를 작성하려면이 규칙 / 패턴을 사용하십시오.

  • CQRS
  • 기존 ORM 기능을 최대한 사용하십시오.
  • 복잡한 논리 및 쿼리 만 캡슐화
  • ORM의 "배관"을 아키텍처 내부에 숨기십시오.

구체적인 구현에서는 래핑없이 ORM의 CRUD 작업을 직접 사용합니다. 또한 코드에서 직접 간단한 쿼리를 수행합니다. 그러나 복잡한 쿼리는 자체 개체로 캡슐화됩니다. ORM을 "숨기기"위해 데이터 컨텍스트를 서비스 / UI 오브젝트에 투명하게 삽입하려고 시도하고 오브젝트 조회를 위해 동일한 작업을 수행하려고합니다.

마지막으로 말하고 싶은 것은 많은 사람들이 ORM을 사용하는 방법과 그로부터 최상의 "이익"을 얻는 방법을 모르고 ORM을 사용한다는 것입니다. 리포지토리를 추천하는 사람들은 대개 이런 종류입니다.

권장 독서로, 나는 Ayende의 블로그 , 특히이 기사를 말할 것 입니다.


+1 "내가 마지막으로 말하고 싶은 것은, 많은 사람들이 ORM을 사용하는 방법과
그로부터

1

누출 구현의 문제점은 많은 다른 필터 조건이 필요하다는 것입니다.

저장소 메소드 FindByExample을 구현하고 다음과 같이 사용하면이 API를 좁힐 수 있습니다.

// find all retired persons
Person filter = new Person {Status=PersonStatus.Retired};
IEnumerable<Person> found = personRepository.FindByExample(filter);


// find all persons who have a dog named "sam"
Person filter = new Person();
filter.AddPet(new Dog{Name="sam"});
IEnumerable<Person> found = personRepository.FindByExample(filter);

0

확장이 IRepository 대신 IQueryable에서 작동하도록 고려하십시오.

public static class PersonExtensions
{
    public static IQueryable<Person> AreRetired(this IQueryable<Person> people)
    {
        return people.Where(p => p.Status == "Retired");
    }
}

단위 테스트 :

List<Person> people = new List<Person>();
people.Add(new Person() { Name = "Bob", Status = "Retired" });
people.Add(new Person() { Name = "Sam", Status = "Working" });

var retiredPeople = people.AsQueryable().AreRetired();
// assert that Bob is in the list
// assert that Sam is not in the list

테스트에는 파이낸싱 조롱이 필요하지 않습니다. 필요에 따라 이러한 확장 방법을 결합하여보다 복잡한 쿼리를 만들 수도 있습니다.


5
-1 a) IQueryable는 나쁜 어머니이거나 오히려 GOD 인터페이스입니다. 이를 구현하는 데는 LINQ to Sql 제공 업체가 거의 없습니다 (있는 경우). b) 확장 방법은 확장 (기본 OOP 원칙 중 하나)을 불가능하게합니다.
jgauffin

0

NHibernate에 대한 매우 깔끔한 쿼리 객체 패턴을 여기에 썼습니다 : https://github.com/shaynevanasperen/NHibernate.Sessions.Operations

다음과 같은 인터페이스를 사용하여 작동합니다.

public interface IDatabases
{
    ISessionManager SessionManager { get; }

    T Query<T>(IDatabaseQuery<T> query);
    T Query<T>(ICachedDatabaseQuery<T> query);

    void Command(IDatabaseCommand command);
    T Command<T>(IDatabaseCommand<T> command);
}

다음과 같은 POCO 엔티티 클래스가 주어집니다.

class Database1Poco
{
    public int Property1 { get; set; }
    public string Property2 { get; set; }
}

다음과 같은 쿼리 개체를 작성할 수 있습니다.

class Database1PocoByProperty1 : DatabaseQuery<Database1Poco>
{
    public override Database1Poco Execute(ISessionManager sessionManager)
    {
        return sessionManager.Session.Query<Database1Poco>().SingleOrDefault(x => x.Property1 == Property1);
    }

    public int Property1 { get; set; }
}

그런 다음 다음과 같이 사용하십시오.

var database1Poco = _databases.Query(new Database1PocoByProperty1 { Property1 = 1 });

1
이 링크가 질문에 대한 답변을 제공 할 수 있지만 여기에 답변의 필수 부분을 포함시키고 참조 용 링크를 제공하는 것이 좋습니다. 링크 된 페이지가 변경되면 링크 전용 답변이 유효하지 않을 수 있습니다.
Dan Pichelman

사과합니다, 서두르고있었습니다. 예제 코드를 표시하도록 답변이 업데이트되었습니다.
Shayne

1
더 많은 콘텐츠로 게시물을 개선하기 위해 +1
Songo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.