비즈니스 로직을 서비스 계층으로 이동하지 않고 도메인 개체 속성에 대한 고유 한 제약 조건을 확인하는 우아한 방법이 있습니까?


10

나는 지금까지 약 8 년 동안 도메인 중심 디자인을 적용 해 왔으며이 모든 세월이 지난 후에도 여전히 나를 괴롭힌 한 가지가 있습니다. 그것은 도메인 객체에 대한 데이터 스토리지의 고유 레코드를 확인하는 것입니다.

2013 년 9 월 Martin Fowler는 가능한 경우 모든 도메인 객체에 적용해야하는 TellDon'tAsk 원칙을 언급했으며 ,이 메시지는 작업이 어떻게 진행되었는지 메시지를 반환해야합니다 (객체 지향 설계에서는 대부분 예외를 통해 수행됩니다 작업이 실패했습니다).

내 프로젝트는 일반적으로 여러 부분으로 나뉘며, 그 중 두 개는 도메인 (비즈니스 규칙을 포함하고 그 밖의 다른 것, 도메인은 완전히 영속 무지 함)과 서비스입니다. 데이터를 CRUD하는 데 사용되는 저장소 계층에 대해 알고있는 서비스

오브젝트에 속하는 속성의 고유성은 도메인 / 비즈니스 규칙이므로 도메인 모듈까지 길어야하므로 규칙이 정확히 있어야합니다.

레코드의 고유성을 확인하려면 현재 데이터 세트 (일반적으로 데이터베이스)를 쿼리하여 다른 레코드가 Name이미 존재 하는지 확인해야 합니다.

도메인 계층이 지속성을 모르고 데이터를 검색하는 방법을 모르지만 작업을 수행하는 방법 만 알면 실제로 저장소 자체를 건드릴 수 없습니다.

내가 적응 한 디자인은 다음과 같습니다.

class ProductRepository
{
    // throws Repository.RecordNotFoundException
    public Product GetBySKU(string sku);
}

class ProductCrudService
{
    private ProductRepository pr;

    public ProductCrudService(ProductRepository repository)
    {
        pr = repository;
    }

    public void SaveProduct(Domain.Product product)
    {
        try {
            pr.GetBySKU(product.SKU);

            throw Service.ProductWithSKUAlreadyExistsException("msg");
        } catch (Repository.RecordNotFoundException e) {
            // suppress/log exception
        }

        pr.MarkFresh(product);
        pr.ProcessChanges();
    }
}

이로 인해 도메인 계층 자체가 아닌 도메인 규칙을 정의하는 서비스가 제공되고 코드의 여러 섹션에 규칙이 분산됩니다.

TellDon'tAsk 원칙을 언급했습니다. 분명히 알 수 있듯이 서비스는 작업을 제공 Product하지만 (예외를 저장 하거나 예외를 발생) 메소드 내부에서는 절차 적 접근 방식을 통해 객체에 대해 작업합니다.

확실한 해결책은을 던지는 메소드 로 Domain.ProductCollection클래스 를 작성하는 것이지만 제품에 이미 동일한 SKU가 있는지 여부를 코드에서 찾으려면 데이터 스토리지에서 모든 제품을 가져와야하므로 성능이 많이 부족합니다. 추가하려는 제품으로Add(Domain.Product)ProductWithSKUAlreadyExistsException

이 특정 문제를 어떻게 해결합니까? 이것은 실제로 그 자체로 문제가되지는 않습니다. 서비스 계층에서 수년간 특정 도메인 규칙을 대표했습니다. 서비스 계층은 일반적으로 더 복잡한 도메인 작업을 제공합니다. 커리어 중에 더 중앙 집중화 된 솔루션을 발견했는지 궁금합니다.


하나만 요청하고 찾을 수 있는지에 대한 논리를 기반으로 할 수 있는데 왜 전체 이름 목록을 가져 옵니까? 인터페이스와 일치하는 한 지속성 계층에 수행 할 작업을 지시하고 수행하지 않는 방법을 알려줍니다.
JeffO

1
서비스라는 용어를 사용할 때는 도메인 계층의 도메인 서비스와 응용 프로그램 계층의 응용 프로그램 서비스의 차이와 같이보다 명확하게하기 위해 노력해야합니다. gorodinski.com/blog/2012/04/14/…
Erik Eidt

@ErikEidt의 훌륭한 기사, 감사합니다. Gorodinski 씨를 믿을 수 있다면 내 디자인이 잘못되지 않은 것 같습니다. 더 나은 해결책은 응용 프로그램 서비스가 엔티티에 필요한 정보를 검색하고 효과적으로 실행 환경을 설정하고 제공하는 것입니다. 그것은 엔티티에 대한 것이며, SRP를 위반함으로써 저와 직접 저장소를 도메인 모델에 주입하는 것에 대한 반대 의견이 있습니다.
Andy

"tell, do n't ask"참조의 인용문 : "하지만 개인적으로는 tell-dont-ask를 사용하지 않습니다."
radarbob 2016 년

답변:


7

도메인 계층이 지속성을 모르고 데이터를 검색하는 방법을 모르지만 작업을 수행하는 방법 만 알면 실제로 저장소 자체를 건드릴 수 없습니다.

이 부분에 동의하지 않습니다. 특히 마지막 문장.

도메인이 지속성에 대해 무지해야한다는 것은 사실이지만 "도메인 엔터티 모음"이 있다는 것을 알고 있습니다. 그리고이 컬렉션 전체와 관련된 도메인 규칙이 있습니다. 독창성은 그들 중 하나입니다. 실제 논리의 구현은 특정 지속성 모드에 크게 의존하기 때문에이 논리의 필요성을 지정하는 일종의 추상화가 도메인에 있어야합니다.

따라서 이름이 이미 존재하는지 쿼리 할 수있는 인터페이스를 만드는 것만 큼 간단합니다. 그런 다음 데이터 저장소에 구현되어 이름이 고유한지 알아야하는 사람이 호출합니다.

그리고 리포지토리는 DOMAIN 서비스라는 점을 강조하고 싶습니다. 그것들은 지속성에 대한 추상화입니다. 도메인과 분리되어야하는 저장소의 구현입니다. 도메인 엔티티가 도메인 서비스를 호출하는 데 아무런 문제가 없습니다. 한 엔터티가 리포지토리를 사용하여 다른 엔터티를 검색하거나 특정 정보를 검색하여 메모리에 쉽게 보관할 수없는 것은 아무 문제가 없습니다. 이것이 에반스의 책 에서 리포지토리가 핵심 개념 인 이유 입니다 .


의견을 보내 주셔서 감사합니다. 저장소는 실제로 Domain.ProductCollection도메인 레이어에서 객체를 검색하는 것을 고려할 때 내가 염두에 두었던 것을 나타낼 수 있습니까?
Andy

@DavidPacker 나는 당신이 무엇을 의미하는지 이해하지 못합니다. 모든 항목을 메모리에 보관할 필요가 없기 때문입니다. "DoesNameExist"메소드의 구현은 데이터 저장소 측에서 (대부분의 경우) SQL 쿼리 여야합니다.
Euphoric

의미하는 것은 데이터를 컬렉션의 메모리에 저장하는 대신 모든 제품을 알고 싶을 때 데이터를 가져올 필요는 Domain.ProductCollection없지만 요청하는 것과 동일한 방법으로 리포지토리 Domain.ProductCollection에 미리로드 된 제품을 반복하는 대신 기본 데이터베이스를 쿼리하는 대신 리포지토리를 요청하는 (이것은 실제로는 예) SKU를 다시 전달했습니다. 꼭 필요한 경우가 아니면 모든 제품을 메모리에 저장한다는 의미는 아닙니다.
Andy

그러면 저장소가 속성이 고유해야하는지 여부를 알아야하는 또 다른 질문으로 이어질 수있는 것은 무엇입니까? 나는 항상 저장소를 꽤 멍청한 구성 요소로 구현하여 전달 한 것을 저장하고 전달 된 조건에 따라 데이터를 검색하고 검색을 시도하여 결정을 서비스에 넣습니다.
Andy

@DavidPacker "도메인 지식"이 인터페이스에 있습니다. 인터페이스에 "도메인에이 기능이 필요합니다"라고 표시되고 데이터 저장소가 해당 기능을 제공합니다. IProductRepository와 인터페이스가 있으면 DoesNameExist메소드가 수행해야 할 작업이 분명합니다. 그러나 제품이 기존 제품과 이름이 같지 않아야 도메인의 다른 곳에 있어야합니다.
Euphoric

4

세트 유효성 검사 에서 Greg Young을 읽어야 합니다 .

짧은 대답 : 쥐의 둥지를 너무 멀리 내려 가기 전에 비즈니스 관점에서 요구 사항의 가치를 이해해야합니다. 실제로 중복을 감지하지 않고 감지하고 완화하는 데 비용이 얼마나 듭니까?

“고유성”요구 사항의 문제점은 사람들이 원하는 이유가 더 깊은 근본적인 이유가 있다는 것입니다. Yves Reynhout

더 긴 대답 : 가능성의 메뉴를 보았지만 모두 단점이 있습니다.

도메인으로 명령을 보내기 전에 중복을 확인할 수 있습니다. 이 작업은 클라이언트 또는 서비스에서 수행 할 수 있습니다 (예는 기술을 보여줍니다). 도메인 계층에서 유출되는 논리가 마음에 들지 않으면와 같은 결과를 얻을 수 있습니다 DomainService.

class Product {
    void register(SKU sku, DuplicationService skuLookup) {
        if (skuLookup.isKnownSku(sku) {
            throw ProductWithSKUAlreadyExistsException(...)
        }
        ...
    }
}

물론 DeduplicationService의 구현은 기존 skus를 찾는 방법에 대해 알아야 할 것입니다. 따라서 일부 작업을 도메인으로 다시 밀어 넣는 동안 여전히 동일한 기본 문제 (세트 유효성 검사에 대한 답변, 경쟁 조건 문제)이 있습니다.

지속성 계층 자체에서 유효성 검사를 수행 할 수 있습니다. 관계형 데이터베이스는 세트 유효성 검사에 능숙 합니다. 제품의 sku 컬럼에 고유 제한 조건을 설정하십시오. 응용 프로그램은 제품을 리포지토리에 저장하기 만하면 문제가 발생하면 제약 조건 위반 버블 링이 다시 발생합니다. 따라서 응용 프로그램 코드가 좋아 보이고 경쟁 조건이 제거되지만 "도메인"규칙이 유출됩니다.

알려진 skus 집합을 나타내는 별도의 집계를 도메인에 만들 수 있습니다. 여기서 두 가지 변형을 생각할 수 있습니다.

하나는 ProductCatalog와 같은 것입니다. 제품은 다른 곳에 존재하지만 제품과 skus의 관계는 sku 고유성을 보장하는 카탈로그에 의해 유지됩니다. 이는 제품에 skus 없음을 의미 하지는 않습니다 . skus는 ProductCatalog에 의해 지정됩니다 (skus가 고유해야하는 경우 단일 ProductCatalog 집계만으로이를 달성 할 수 있습니다). 도메인 전문가와 함께 유비쿼터스 언어를 검토하십시오. 그러한 것이 존재하는 경우 이것이 올바른 접근법 일 수 있습니다.

대안은 sku 예약 서비스와 비슷합니다. 기본 메커니즘은 동일합니다. 집합체는 모든 skus에 대해 알고 있으므로 복제본의 도입을 막을 수 있습니다. 그러나 메커니즘은 약간 다릅니다. 제품에 할당하기 전에 SKU에서 임대를 취득합니다. 제품을 만들 때 임대를 SKU에 전달합니다. 여전히 경쟁 조건이 있지만 (다른 집계, 따라서 별도의 트랜잭션), 다른 맛이 있습니다. 여기서 실질적인 단점은 도메인 언어를 정당화하지 않고도 도메인 모델에 임대 서비스를 투영한다는 것입니다.

모든 제품 엔터티를 단일 집계 (즉, 위에서 설명한 제품 카탈로그)로 가져올 수 있습니다. 이 작업을 수행하면 skus의 고유성이 절대적으로 필요하지만 비용은 추가 경합이며, 제품을 수정하면 전체 카탈로그를 수정해야합니다.

메모리 내 작업을 수행하기 위해 데이터베이스에서 모든 제품 SKU를 가져와야 할 필요는 없습니다.

아마 당신은 필요하지 않습니다. Bloom 필터로 sku를 테스트 하면 세트를 전혀로드하지 않고도 많은 독특한 skus를 발견 할 수 있습니다.

사용 사례에서 거부 한 skus에 대해 임의적 일 수 있으면 모든 오 탐지를 제거 할 수 있습니다 (클라이언트가 명령을 제출하기 전에 제안한 skus를 테스트하도록 허용하는 경우 큰 문제는 아님). 이렇게하면 세트를 메모리에로드하지 않아도됩니다.

(더 수용하고 싶다면 블룸 필터에서 일치하는 경우 skus를 게으른로드하는 것을 고려할 수 있습니다. 여전히 skus를 메모리에로드 할 위험이 있지만 허용하는 경우 일반적인 경우는 아닙니다. 전송하기 전에 명령의 오류를 확인하기위한 클라이언트 코드).


메모리 내 작업을 수행하기 위해 데이터베이스에서 모든 제품 SKU를 가져와야 할 필요는 없습니다. 초기 질문에서 제안하고 성능에 의문을 제기 한 확실한 솔루션입니다. 또한 고유성을 담당하기 위해 DB의 제약 조건에 의존하는 IMO는 나쁩니다. 새 데이터베이스 엔진으로 전환하고 변환 중에 고유 제한 조건이 손실 된 경우 이전에 있던 데이터베이스 정보가 더 이상 없기 때문에 코드가 손상되었습니다. 어쨌든 링크를 읽어 주셔서 감사합니다.
Andy

블룸 필터 일 것입니다 (편집 참조).
VoiceOfUnreason 14:04에
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.