저장소 패턴을 올바르게 사용하는 방법은 무엇입니까?


86

리포지토리를 어떻게 그룹화해야하는지 궁금합니다. asp.net mvc에서 본 예제와 마찬가지로 내 책에서는 기본적으로 데이터베이스 테이블 당 하나의 저장소를 사용합니다. 그러나 그것은 나중에 조롱과 물건을 위해 많은 저장소를 호출 해야하는 많은 저장소처럼 보입니다.

그래서 나는 그들을 그룹화해야한다고 생각합니다. 그러나 나는 그들을 그룹화하는 방법을 잘 모르겠습니다.

지금은 모든 등록 작업을 처리하기 위해 등록 저장소를 만들었습니다. 그러나 업데이트해야 할 테이블이 4 개 있고이를 수행하기 위해 3 개의 리포지토리가 있습니다.

예를 들어 테이블 중 하나는 라이센스 테이블입니다. 그들이 등록 할 때 나는 그들의 키를보고 데이터베이스에 존재하는지 확인합니다. 이제 등록이 아닌 다른 지점에서이 라이센스 키 또는 해당 테이블의 다른 것을 확인해야하는 경우 어떻게됩니까?

한 자리는 로그인 일 수 있습니다 (키가 만료되지 않았는지 확인).

그래서이 상황에서 무엇을할까요? 코드를 다시 작성 하시겠습니까 (break DRY)? 이 두 저장소를 함께 병합하고 다른 시점에서 메서드가 필요하지 않기를 바랍니다 (예 : userName이 사용되는지 확인하는 메서드가있을 수 있습니다. 다른 곳에서 필요할 수도 있습니다).

또한 그들을 병합하면 사이트의 두 가지 다른 부분에 대한 모든 논리가 길고 ValidateLogin (), ValdiateRegistrationForm ()과 같은 이름을 가져야한다고 생각하기 때문에 동일한 저장소로가는 2 개의 서비스 레이어가 필요합니다. , ValdiateLoginRetrievePassword () 등

아니면 리포지토리에 전화를 걸어 이상하게 들리는 이름이 있나요?

응용 프로그램의 여러 지점에서 사용할 수 있고 여전히 이해할 수 있도록 일반적인 이름을 가진 저장소를 만드는 것이 어려워 보이며 저장소에서 다른 저장소를 호출하는 것이 좋은 방법이라고 생각하지 않습니까?


11
+1. 좋은 질문입니다.
griegs

지금 당분간 저를 괴롭히는 감사합니다. 내가 책에서 본 것들이 너무 단순하다는 것을 알았 기 때문에 그들은 이러한 상황에서 무엇을 해야할지 보여주지 않습니다.
chobo2

나는 기본적으로 당신과 똑같은 일을하고 있습니다. 하나 이상의 저장소에서 사용되는 linq2sql 클래스가 있고 테이블 구조를 변경 해야하는 경우 DRY를 중단합니다. 이상적이지 않습니다. 이제 좀 더 잘 계획을 세웠 기 때문에 linq2sql 클래스를 두 번 이상 사용할 필요가 없습니다.이 클래스는 우려의 분리가 좋은 것 같지만 이것이 저에게 진짜 문제가 될 하루를 예견합니다.
griegs

나는 여기에 비슷한 질문 (동일하지는 않음)을 물었다 : stackoverflow.com/questions/910156/…
Grokys

그러나 모든 상황에 대해 계획하기는 어렵습니다. 내가 말했듯이 로그인과 등록을 인증에 병합하고 2 개의 별도 레이어를 가질 수 있습니다. 그것은 아마도 내 문제를 해결할 것입니다. 그러나 내 사이트의 프로필 페이지에서 어떤 이유로 든 키를 보여주고 싶다면 어떻게 되나요? 이제 DRY를 깨고 똑같은 것을 작성하는 것은 무엇입니까? 또는 좋은 이름으로이 3 개의 테이블을 모두 맞출 수있는 저장소를 만드십시오.
chobo2

답변:


41

리포지토리 패턴을 가지고 놀 때 내가 잘못한 한 가지는 당신처럼, 그 테이블이 리포지토리 1 : 1과 관련이 있다고 생각했습니다. 도메인 기반 설계의 일부 규칙을 적용하면 저장소 그룹화 문제가 종종 사라집니다.

리포지토리는 테이블이 아닌 집계 루트 당이어야 합니다. 즉, 엔티티가 혼자 살지 않아야하는 경우 (예 : Registrant특정 참여 가있는 경우 Registration)는 엔티티 일 뿐이며 저장소가 필요하지 않으며 집계 루트 저장소를 통해 업데이트 / 생성 / 검색되어야합니다. 속한다.

물론-대부분의 경우 저장소 수를 줄이는이 기술 (실제로는 도메인 모델을 구조화하는 기술)은 적용 할 수 없습니다. 모든 엔티티가 집계 루트 여야하므로 (도메인에 크게 의존하며, 블라인드 추측 만 제공 할 수 있습니다.) 귀하의 예에서- 엔티티의 License컨텍스트없이 확인할 수 있어야하기 때문에 집계 루트 인 것 같습니다 Registration.

그러나 그것은 우리가 저장소를 계단식으로 만드는 것을 제한하지 않습니다 ( Registration저장소는 License필요한 경우 저장소 를 참조 할 수 있습니다). 그것은 우리가 객체 License에서 직접 저장소 를 참조하도록 제한하지 않습니다 (바람직하게는 IoC를 통해) Registration.

기술이 제공하는 복잡성이나 오해를 통해 디자인을 추진하지 마십시오. ServiceX2 개의 저장소를 구성하고 싶지 않기 때문에 저장소를 그룹화하는 것은 좋은 생각이 아닙니다.

훨씬 더 나은에게 적절한 이름을 부여하는 것 - RegistrationService즉,

그러나 서비스는 일반적으로 피해야합니다. 종종 빈혈 도메인 모델로 이어지는 원인이됩니다 .

편집 :
IoC 사용을 시작하십시오. 의존성 주입의 고통을 진정으로 덜어줍니다.
작성하는 대신 :

var registrationService = new RegistrationService(new RegistrationRepository(),  
      new LicenseRepository(), new GodOnlyKnowsWhatElseThatServiceNeeds());

다음과 같이 작성할 수 있습니다.

var registrationService = IoC.Resolve<IRegistrationService>();

추신 소위 공통 서비스 로케이터 를 사용하는 것이 더 좋을 것이지만 그것은 단지 예일뿐입니다.


17
하하하 ... 서비스 로케이터 사용에 대한 조언을 드리고 있습니다. 내가 과거에 얼마나 멍청했는지 보는 것은 항상 기쁨입니다.
Arnis Lapsa

1
@Arnis 당신의 의견이 바뀐 것 같네요-지금이 질문에 어떻게 다르게 대답 할 수 있을까요?
ngm 2011

10
@ngm이 대답 한 이후로 많이 변경되었습니다. 나는 집계 루트가 트랜잭션 경계를 그려야한다는 데 여전히 동의하지만 (전체적으로 저장되어야 함) 저장소 패턴을 사용하여 지속성을 추상화하는 것에 대해 훨씬 덜 낙관적입니다. 최근-저는 직접 ORM을 사용하고 있습니다. eager / lazy loading management와 같은 것들이 너무 어색하기 때문입니다. 지속성을 추상화하는 대신 풍부한 도메인 모델 개발에 집중하는 것이 훨씬 더 유익합니다.
Arnis Lapsa

2
@ 개발자 아니오, 정확하지 않습니다. 그들은 여전히 ​​무지해야합니다. 외부에서 집계 루트를 검색하고 작업을 수행하는 메서드를 호출합니다. 내 도메인 모델에는 참조가 없으며 표준 .net 프레임 워크 만 있습니다. 이를 위해서는 충분히 똑똑한 풍부한 도메인 모델과 도구가 있어야합니다 (NHibernate가 트릭을 수행합니다).
Arnis Lapsa

1
반대로. 둘 이상의 집계를 가져 오는 것은 종종 PK 또는 인덱스를 사용할 수 있음을 의미합니다. EF가 생성하는 관계를 이용하는 것보다 훨씬 빠릅니다. 루트 집계가 작을수록 성능을 더 쉽게 얻을 수 있습니다.
jgauffin

5

이 문제를 해결하기 위해 시작한 한 가지는 실제로 N 개의 저장소를 래핑하는 서비스를 개발하는 것입니다. DI 또는 IoC 프레임 워크가이를 더 쉽게 만드는 데 도움이되기를 바랍니다.

public class ServiceImpl {
    public ServiceImpl(IRepo1 repo1, IRepo2 repo2...) { }
}

말이 돼? 또한이 저택의 서비스에 대해 말하는 것이 실제로 DDD 원칙을 준수 할 수도 있고 그렇지 않을 수도 있음을 이해합니다. 작동하는 것처럼 보이기 때문에 그렇게합니다.


1
그다지 말이되지 않습니다. 현재로서는 DI 나 IoC 프레임 워크를 사용하지 않고 있습니다.
chobo2

1
new ()를 사용하여 리포지토리를 인스턴스화 할 수 있다면 다음을 시도해 볼 수 있습니다. public ServiceImpl () : this (new Repo1, new Repo2 ...) {}를 서비스의 추가 생성자로 사용할 수 있습니다.
neouser99

의존성 주입? 나는 이미 그것을했지만 여전히 당신의 코드가 무엇인지, 그것이 무엇을 해결하는지 확실하지 않습니다.
chobo2

저는 특히 리포지토리 병합 후 진행합니다. 어떤 코드가 의미가 없습니까? DI를 사용하는 경우 답변의 코드는 DI 프레임 워크가 해당 IRepo 서비스를 주입한다는 의미에서 작동합니다. 주석 코드에서는 기본적으로 DI를 수행하는 작은 해결 방법 일뿐입니다 (기본적으로 매개 변수 없음 생성자 '주입'). ServiceImpl에 대한 종속성).
neouser99

단위 테스트를 더 잘 할 수 있도록 이미 DI를 사용하고 있습니다. 내 문제는 서비스 레이어와 리포지토리를 자세한 이름으로 만드는 것입니다. 그런 다음 다른 곳에서 사용해야 할 경우 ProfileClass와 같은 다른 클래스에서 RegRepo를 호출하는 RegistrationService 계층처럼 이상하게 호출됩니다. 그래서 나는 당신이 온전히 무엇을하고 있는지 당신의 예를 보지 못하고 있습니다. 같은 서비스 계층에 너무 많은 Repos가 있기 시작하면 비즈니스 로직과 유효성 검사 로직이 너무 많이 달라집니다. 서비스 계층에서는 일반적으로 유효성 검사 논리를 입력합니다. 이렇게 많은 I 필요 더 ...
chobo2

2

내가하는 일은 다음과 같이 정의 된 추상 기본 클래스가 있습니다.

public abstract class ReadOnlyRepository<T,V>
{
     V Find(T lookupKey);
}

public abstract class InsertRepository<T>
{
     void Add(T entityToSave);
}

public abstract class UpdateRepository<T,V>
{
     V Update(T entityToUpdate);
}

public abstract class DeleteRepository<T>
{
     void Delete(T entityToDelete);
}

그런 다음 추상 기본 클래스에서 저장소를 파생시키고 예를 들어 일반 인수가 다른 경우 단일 저장소를 확장 할 수 있습니다.

public class RegistrationRepository: ReadOnlyRepository<int, IRegistrationItem>,
                                     ReadOnlyRepository<string, IRegistrationItem> 

기타....

일부 리포지토리에 제한이 있고 최대 유연성을 제공하기 때문에 별도의 리포지토리가 필요합니다. 도움이 되었기를 바랍니다.


이 모든 것을 처리하기 위해 일반 저장소를 만들려고하십니까?
chobo2

그래서 실제로는 Update 메소드를 말합니다. 이 V 업데이트를 좋아하고 T entitytoUpdate를 전달하지만 실제로 업데이트하는 코드가 없거나 있습니까?
chobo2

예 .. 일반 저장소에서 내려 오는 클래스를 작성하기 때문에 update 메소드에 코드가 있습니다. 구현 코드는 Linq2SQL 또는 ADO.NET 또는 데이터 액세스 구현 기술로 선택한 모든 것이 될 수 있습니다
Michael Mann

2

나는 이것을 내 저장소 클래스로 가지고 있고 예, 테이블 / 영역 저장소에서 확장하지만 여전히 때때로 DRY를 깨야합니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MvcRepository
{
    public class Repository<T> : IRepository<T> where T : class
    {
        protected System.Data.Linq.DataContext _dataContextFactory;

        public IQueryable<T> All()
        {
            return GetTable.AsQueryable();
        }

        public IQueryable<T> FindAll(Func<T, bool> exp)
        {
            return GetTable.Where<T>(exp).AsQueryable();
        }

        public T Single(Func<T, bool> exp)
        {
            return GetTable.Single(exp);
        }

        public virtual void MarkForDeletion(T entity)
        {
            _dataContextFactory.GetTable<T>().DeleteOnSubmit(entity);
        }

        public virtual T CreateInstance()
        {
            T entity = Activator.CreateInstance<T>();
            GetTable.InsertOnSubmit(entity);
            return entity;
        }

        public void SaveAll()
        {
            _dataContextFactory.SubmitChanges();
        }

        public Repository(System.Data.Linq.DataContext dataContextFactory)
        {
            _dataContextFactory = dataContextFactory;
        }

        public System.Data.Linq.Table<T> GetTable
        {
            get { return _dataContextFactory.GetTable<T>(); }
        }

    }
}

편집하다

public class AdminRepository<T> : Repository<T> where T: class
{
    static AdminDataContext dc = new AdminDataContext(System.Configuration.ConfigurationManager.ConnectionStrings["MY_ConnectionString"].ConnectionString);

    public AdminRepository()
        : base( dc )
    {
    }

또한 Linq2SQL.dbml 클래스를 사용하여 만든 데이터 컨텍스트가 있습니다.

이제 All 및 Find와 같은 표준 호출을 구현하는 표준 저장소가 있고 AdminRepository에 특정 호출이 있습니다.

나는 생각하지 않지만 DRY의 질문에 대답하지 않습니다.


"저장소"는 무엇입니까? CreateInstance처럼? 당신이 가진 모든 것이 무엇을하고 있는지 확실하지 않습니다.
chobo2

그것은 무엇이든 일반적입니다. 기본적으로 (지역)에 대한 특정 저장소가 필요합니다. 내 AdminRepository에 대한 위의 편집을 확인하십시오.
griegs

1

다음은 FluentNHibernate를 사용하는 일반적인 Repository 구현 의 예 입니다. 매퍼를 작성한 모든 클래스를 지속 할 수 있습니다. 매퍼 클래스를 기반으로 데이터베이스를 생성 할 수도 있습니다.


1

리포지토리 패턴은 잘못된 디자인 패턴입니다. 저는 많은 오래된 .Net 프로젝트에서 작업하며이 패턴은 일반적으로 피할 수있는 "분산 트랜잭션", "부분 롤백"및 "연결 풀 소진"오류를 발생시킵니다. 문제는 패턴이 내부적으로 연결과 트랜잭션을 처리하려고하지만 컨트롤러 계층에서 처리해야한다는 것입니다. 또한 EntityFramework는 이미 많은 논리를 추상화합니다. 공유 코드를 재사용하는 대신 서비스 패턴을 사용하는 것이 좋습니다.


0

Sharp Architecture 를 살펴볼 것을 제안합니다 . 그들은 엔티티 당 하나의 저장소를 사용하는 것이 좋습니다. 현재 내 프로젝트에서 사용하고 있으며 결과에 만족합니다.


여기에 +1을 주겠지 만, 그는 DI 또는 IoC 컨테이너가 선택 사항이 아니라는 점을 지적했습니다 (샤프 아치의 유일한 이점은 아닙니다). 그가 작업하고있는 기존 코드 중 일부가 있다고 생각합니다.
neouser99

엔티티로 간주되는 것은 무엇입니까? 그게 전체 데이터베이스입니까? 아니면 데이터베이스 테이블입니까? DI 또는 IoC 컨테이너는 동시에 배우고있는 다른 10 가지 사항을 배우고 싶지 않기 때문에 현재로서는 옵션이 아닙니다. 그들은 내 사이트의 다음 개정판이나 다음 프로젝트를 살펴볼 것입니다. 이것이 사이트를 빠르게 살펴볼 것인지 확실하지 않지만 nhirbrate를 사용하기를 바라는 것 같고 현재이 시간에 SQL에 linq를 사용하고 있습니다.
chobo2
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.