루트를 집계하기 위해 리포지토리 줄이기


83

저는 현재 데이터베이스의 거의 모든 테이블에 대한 리포지토리를 가지고 있으며 루트 만 집계하도록 줄여서 DDD에 더 잘 맞추고 싶습니다.

다음 테이블 UserPhone. 각 사용자는 하나 이상의 전화기를 가질 수 있습니다. 집계 루트의 개념이 없으면 다음과 같이 할 수 있습니다.

//assuming I have the userId in session for example and I want to update a phone number
List<Phone> phones = PhoneRepository.GetPhoneNumberByUserId(userId);
phones[0].Number = “911”;
PhoneRepository.Update(phones[0]);

집계 뿌리의 개념은 실제보다 종이에서 이해하기가 더 쉽습니다. 사용자에게 속하지 않는 전화 번호는 절대 가지지 않으므로 PhoneRepository를 없애고 전화 관련 메서드를 UserRepository에 통합하는 것이 합리적입니까? 대답이 예라고 가정하고 이전 코드 샘플을 다시 작성하겠습니다.

UserRepository에 전화 번호를 반환하는 메서드를 사용할 수 있습니까? 또는 항상 사용자에 대한 참조를 반환 한 다음 사용자를 통해 관계를 탐색하여 전화 번호를 가져와야합니다.

List<Phone> phones = UserRepository.GetPhoneNumbers(userId);
// Or
User user = UserRepository.GetUserWithPhoneNumbers(userId); //this method will join to Phone

휴대 전화 중 하나를 수정했다고 가정 할 때 휴대 전화를 획득하는 방법에 관계없이 어떻게 업데이트해야합니까? 나의 제한된 이해는 루트 아래의 객체가 루트를 통해 업데이트되어야한다는 것입니다. 이것은 Entity Framework에서 완벽하게 잘 작동하지만 코드를 읽으면 Entity Framework가 그래프 내에서 변경된 개체에 대한 탭을 유지하고 있음에도 불구하고 실제로 업데이트하는 내용을 알지 못하기 때문에 매우 설명 적이 지 않은 것처럼 보입니다.

UserRepository.Update(user);
// Or
UserRepository.UpdatePhone(phone);

마지막으로, 나는 같은 정말 아무것도에 연결되지 않은 여러 개의 룩업 테이블을 가지고 가정 CountryCodes, ColorsCodes, SomethingElseCodes. 드롭 다운을 채우는 데 또는 다른 이유로 사용할 수 있습니다. 독립형 저장소입니까? 그것들은 어떤 종류의 논리적 그룹화 / 저장소로 결합 될 수 있습니까 CodesRepository? 아니면 모범 사례에 위배됩니다.


2
정말 좋은 질문입니다. 저는 제 자신과 많은 어려움을 겪었습니다. "올바른"솔루션이없는 절충점 중 하나 인 것 같습니다. 내가이 글을 쓰는 시점에 구할 수있는 답변은 훌륭하고 대부분의 문제를 다루지 만 "최종"솔루션을 제공하는 것 같지는 않습니다. :(
cwap

나는 당신이 얻을 수있는 "올바른"해결책에 얼마나 가까운 지에 제한이 없다고 들었습니다. 더 나은 방법을 배울 때까지 최선을
다해야한다고

+1-나도이 문제로 어려움을 겪고 있습니다. 모든 테이블에 대해 별도의 저장소와 서비스 계층을 갖기 전에. 합리적 일 때 이들을 결합하기 시작했지만 결국 1k 라인 이상의 코드가있는 저장소 및 서비스 레이어로 끝났습니다. 내 최신 애플리케이션 슬라이스에서는 해당 항목이 종속 된 경우에도 동일한 리포지토리 / 서비스 레이어에 밀접하게 관련된 개념 만 배치하기 위해 약간 백업했습니다. 예-블로그의 경우 게시물 리포지토리 집계에 댓글을 추가했지만 이제는 별도의 댓글 리포지토리 / 서비스로 분리했습니다.
jpshook

답변:


12

당신은 당신이 당신의 저장소에서 원하는 어떤 방법을 가질 수 있습니다 :) 당신이 언급 한 두 경우 모두 전화 목록이 채워진 사용자를 반환하는 것이 좋습니다. 일반적으로 사용자 개체는 모든 하위 정보 (예 : 모든 주소, 전화 번호)로 완전히 채워지지 않으며 사용자 개체를 다른 종류의 정보로 채우는 방법이 다를 수 있습니다. 이를 지연 로딩이라고합니다.

User GetUserDetailsWithPhones()
{
    // Populate User along with Phones
}

이 경우 업데이트의 경우 전화 번호 자체가 아닌 사용자가 업데이트됩니다. 스토리지 모델은 전화기를 다른 테이블에 저장할 수 있으며 그런 식으로 전화기 만 업데이트되고 있다고 생각할 수 있지만 DDD 관점에서 생각하면 그렇지 않습니다. 가독성에 관한 한, 라인은

UserRepository.Update(user)

단독으로는 업데이트되는 내용을 전달하지 않으며 위의 코드를 통해 업데이트되는 내용을 명확하게 알 수 있습니다. 또한 업데이트되는 항목을 나타낼 수있는 프런트 엔드 메서드 호출의 일부일 가능성이 높습니다.

조회 테이블의 경우, 실제로는 다른 경우에도 GenericRepository를 가지고 사용하는 것이 유용합니다. 사용자 정의 저장소는 GenericRepository에서 상속 할 수 있습니다.

public class UserRepository : GenericRepository<User>
{
    IEnumerable<User> GetUserByCustomCriteria()
    {
    }

    User GetUserDetailsWithPhones()
    {
        // Populate User along with Phones
    }

    User GetUserDetailsWithAllSubInfo()
    {
        // Populate User along with all sub information e.g. phones, addresses etc.
    }
}

Generic Repository Entity Framework를 검색하면 많은 멋진 구현이 가능합니다. 그 중 하나를 사용하거나 직접 작성하십시오.


@amit_g, 정보 주셔서 감사합니다. 나는 이미 다른 모든 사람들이 상속하는 일반 / 기본 저장소를 사용하고 있습니다. "조회"테이블을 하나의 저장소로 논리적으로 그룹화하는 방법은 단순히 시간을 절약하고 저장소 수를 줄이는 것이 었습니다. 따라서 ColorCodeRepository 및 AnotherCodeRepository를 생성하는 대신 CodesRepository.GetColorCodes () 및 CodesRepository.GetAnotherCodes ()를 생성합니다. 그러나 관련되지 않은 엔티티를 하나의 저장소로 논리적으로 그룹화하는 것이 나쁜 습관인지 확실하지 않습니다.
e36M3

또한 DDD 규칙에 따라 루트에 해당하는 리포지토리의 메서드가 그래프 내의 기본 엔터티가 아닌 루트를 반환해야 함을 확인합니다. 따라서 내 예제에서 UserRepository의 모든 메서드는 나머지 그래프의 모양 (또는 주소 또는 전화와 같이 내가 정말로 관심이있는 그래프의 일부)에 관계없이 사용자 유형 만 반환 할 수 있습니까?
e36M3

CodesRepository는 괜찮지 만 그 안에 속한 것을 일관되게 유지하는 것은 어려울 것입니다. GenericRepository <ColorCodes> GetAll ()만으로도 동일한 작업을 수행 할 수 있습니다. GenericRepository에는 매우 일반적인 메서드 (GetAll, GetByID 등) 만 있으므로 조회 테이블에서 잘 작동합니다.
amit_g 2011 년


2
불행히도이 대답은 잘못되었습니다. 리포지토리는 메모리 내 개체의 모음으로 취급되어야하며 지연로드를 피해야합니다. 여기 besnikgeek.blogspot.com/2010/07/
Rafał

9

Aggregate Root 저장소에 대한 귀하의 예는 완벽하게 괜찮습니다. 즉, 다른 항목에 의존하지 않고 합리적으로 존재할 수없는 엔티티에는 자체 저장소 (귀하의 경우 전화)가 없어야합니다. 이러한 고려 사항이 없으면 db 테이블에 대한 1-1 매핑에서 폭발적인 리포지토리를 빠르게 찾을 수 있습니다.

리포지토리 자체보다는 데이터 변경에 대해 작업 단위 (UOW) 패턴을 사용하는 것이 좋습니다. 변경 사항을 db로 다시 유지하는 것과 관련하여 의도와 관련하여 혼란을 야기한다고 생각하기 때문입니다. EF 솔루션에서 작업 단위는 기본적으로 EF 컨텍스트를 둘러싼 인터페이스 래퍼입니다.

조회 데이터 용 리포지토리와 관련하여 도메인 엔터티 (국가, 색상 등)에 특별히 속하지 않는 데이터를 담당하는 ReferenceDataRepository를 생성합니다.


1
감사합니다. 작업 단위가 리포지토리를 대체하는 방법을 잘 모르겠습니까? 모든 비즈니스 트랜잭션이 끝날 때 (HTTP 요청 끝) Entity Framework 컨텍스트에 대한 단일 SaveChanges () 호출이 있다는 의미에서 이미 UOW를 사용합니다. 그러나 데이터 액세스를 위해 EF 컨텍스트가있는 저장소 (Repositories)를 계속 사용합니다. UserRepository.Delete (user) 및 UserRepository.Add (user)와 같습니다.
e36M3

5

전화가 사용자없이 의미가없는 경우, 이는 엔티티 (신원에 관심이있는 경우) 또는 값 객체이며 항상 사용자를 통해 수정하고 함께 검색 / 업데이트해야합니다.

집계 루트를 컨텍스트 정의 자로 생각하십시오. 로컬 컨텍스트를 그리지 만 글로벌 컨텍스트 (귀하의 애플리케이션) 자체에 있습니다.

도메인 기반 설계를 따르는 경우 리포지토리는 집계 루트 당 1 : 1이어야합니다.
변명하지.

나는 이것이 당신이 직면하고있는 문제라고 확신합니다 :

  • 기술적 어려움-물체 관계 임피던스 불일치. 전체 개체 그래프를 쉽게 유지하는 데 어려움을 겪고 있으며 엔티티 프레임 워크 종류가 도움이되지 않습니다.
  • 도메인 모델은 데이터 중심입니다 (행동 중심이 아님). 그 때문에-객체 계층 구조 (이전에 언급 된 컨텍스트)에 대한 지식을 잃고 마술처럼 모든 것이 집계 루트가됩니다.

첫 번째 문제를 해결하는 방법을 잘 모르겠지만 두 번째 문제를 해결하면 먼저 문제가 해결된다는 것을 알게되었습니다. 행동 중심의 의미를 이해하려면 이 문서 를 사용해보십시오.

추신 저장소를 집계 루트로 줄이는 것은 의미가 없습니다.
Pps는 피하십시오 "CodeRepositories". 이는 데이터 중심-> 절차 코드로 이어집니다.
Ppps 작업 단위 패턴을 피하십시오. 집계 루트는 트랜잭션 경계를 정의해야합니다.


1
: 용지에 대한 링크가 더 이상 활성 상태로, 대신 하나를 사용하지 web.archive.org/web/20141021055503/http://www.objectmentor.com/...
JwJosefy

3

이것은 오래된 질문이지만 간단한 해결책을 게시 할 가치가 있다고 생각했습니다.

  1. EF Context는 이미 작업 단위 (변경 사항 추적)와 리포지토리 (DB의 항목에 대한 메모리 내 참조)를 모두 제공합니다. 추가 추상화는 필수가 아닙니다.
  2. Phone은 집계 루트가 아니므로 컨텍스트 클래스에서 DBSet을 제거하십시오.
  3. 대신 사용자의 '전화'탐색 속성을 사용하세요.

static void updateNumber (int userId, string oldNumber, string newNumber)

static void updateNumber(int userId, string oldNumber, string newNumber)
    {
        using (MyContext uow = new MyContext()) // Unit of Work
        {
            DbSet<User> repo = uow.Users; // Repository
            User user = repo.Find(userId); 
            Phone oldPhone = user.Phones.Where(x => x.Number.Trim() == oldNumber).SingleOrDefault();
            oldPhone.Number = newNumber;
            uow.SaveChanges();
        }

    }

추상화는 필수는 아니지만 권장됩니다. Entity Framework는 여전히 공급자이자 인프라의 일부입니다. 공급자가 변경된 경우 어떤 일이 발생했는지 만 문제가되지 않지만 더 큰 시스템에서는 다양한 도메인 개념을 다른 지속성 매체에 유지하는 여러 유형의 공급자가있을 수 있습니다. 이것은 초기에 매우 쉽게 만들 수 있지만 충분한 시간과 복잡성에 대해 리팩토링하기에는 고통스러운 일종의 추상화입니다.
Joseph Ferris

1
리포지토리 인터페이스로 추상화하려고 할 때 EF의 ORM (예 : 지연 로딩, 쿼리 가능)의 이점을 유지하는 것이 매우 어려웠습니다.
Chalky

확실히 흥미로운 토론입니다. 지연로드는 구현에 따라 매우 구체적이기 때문에 그 값이 인프라 (계층 경계 변환이있는 도메인 개체 안팎)로 제한된다는 것을 알았습니다. 내가 본 많은 구현은 일반 추상화를 시도 할 때 문제가 발생합니다. 일반 메서드는 도메인 값이 거의 없기 때문에 명시 적으로 구현하는 경향이 있습니다. EF는 쿼리 가능을 매우 유용하게 만들지 만 문제는 저장소의 역할이됩니다. 즉, 컨트롤러가 사용하는 저장소는 추상화의 이점을 놓칩니다.
Joseph Ferris

0

전화 엔터티가 집계 루트 사용자와 함께 만 의미가있는 경우 새 전화 레코드를 추가하는 작업이 특정 메서드 (DDD 동작)를 통해 사용자 도메인 개체의 책임이고 그럴 수 있다는 것도 합리적이라고 생각합니다. 몇 가지 이유로 완벽하게 이해할 수있는 이유는 전화 엔터티가 존재에 의존하기 때문에 User 개체가 존재하는지 확인하고 다른 프로세스가 이전에 루트 집계를 삭제하지 않았는지 확인하는 동안 더 많은 유효성 검사를 수행하는 동안 트랜잭션 잠금을 유지해야한다는 것입니다. 작업 검증이 완료되었습니다. 다른 종류의 루트 집계가있는 다른 경우에는 일부 값을 집계하거나 계산하고 나중에 다른 작업에서보다 효율적으로 처리하기 위해 루트 집계의 열 속성에 유지하려고 할 수 있습니다.

또한 소유 한 사용자에 관계없이 모든 전화를 검색하는 메서드를 사용하려는 경우에도 여전히 User 저장소를 통해 모든 사용자를 IQueryable로 반환하는 하나의 메서드 만 필요하면 모든 사용자 전화를 가져 오도록 매핑하고 정제 된 작업을 수행 할 수 있습니다. 그것으로 쿼리. 따라서이 경우에는 PhoneRepository가 필요하지 않습니다. 그 외에도 메서드 뒤에서 쿼리를 추상화하려는 경우 Repository 클래스뿐만 아니라 어디에서나 사용할 수있는 IQueryable에 대한 확장 메서드가있는 클래스를 사용하고 싶습니다.

전화 리포지토리가 아닌 도메인 개체 만 사용하여 전화 엔터티를 삭제할 수있는 한 가지주의 사항은 UserId가 전화 기본 키의 일부인지, 즉 전화 레코드의 기본 키가 복합 키인지 확인해야합니다. UserId 및 Phone 엔터티의 다른 속성 (자동 생성 된 ID를 제안 함)으로 구성됩니다. 전화 레코드는 사용자 레코드가 "소유"하고 사용자 탐색 컬렉션에서 제거하면 데이터베이스에서 완전히 제거되는 것과 동일하므로 직관적으로 이해할 수 있습니다.

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