집계 간 참조 유효성 검사를 처리하는 방법은 무엇입니까?


11

집계 간 참조로 조금 어려움을 겪고 있습니다. 집계에 집계 Car에 대한 참조가 있다고 가정합니다 Driver. 이 참조는를 사용하여 모델링됩니다 Car.driverId.

이제 내 문제는에서 Car집계 생성을 확인하기 위해 얼마나 멀리 가야 하는가 입니다 CarFactory. 전달 된 DriverId것이 기존을 참조 한다고 믿어야합니까 , Driver아니면 그 불변을 확인해야합니까?

확인을 위해 두 가지 가능성이 있습니다.

  • 완전한 운전자 실체를 수용하기 위해 자동차 공장의 서명을 변경할 수 있습니다. 그런 다음 공장은 해당 엔티티에서 ID를 선택하고 그로 자동차를 제작합니다. 여기서 불변은 암시 적으로 확인됩니다.
  • 나는의 참조 할 수 DriverRepository의를 CarFactory명시 적으로 호출합니다 driverRepository.exists(driverId).

그러나 이제는 불변 점검이 너무 많지 않은지 궁금합니다. 나는 이러한 집합체가 별도의 제한된 맥락에서 살 수 있다고 생각할 수 있으며 이제 Driver BC의 DriverRepository 또는 Driver 엔티티에 의존하여 자동차 BC를 오염시킬 것입니다.

또한 도메인 전문가와 이야기한다면 그러한 참조의 타당성을 의심하지 않을 것입니다. 관련없는 우려로 도메인 모델을 오염시키는 것을 감지하고 있습니다. 그러나 다시, 어느 시점에서 사용자 입력의 유효성을 검사해야합니다.

답변:


6

완전한 운전자 실체를 수용하기 위해 자동차 공장의 서명을 변경할 수 있습니다. 그런 다음 공장은 해당 엔티티에서 ID를 선택하고 그로 자동차를 제작합니다. 여기서 불변은 암시 적으로 확인됩니다.

이 방법은 무료로 수표를 받고 유비쿼터스 언어와 잘 어울리므로 매력적입니다. (A)는 Cara로 구동되지 driverId하지만 의해 Driver.

이 접근법은 실제로 본 버논이 아이덴티티 및 액세스 (Identity & Access) 샘플 경계 컨텍스트에서 User집계를 집계에 전달 Group하지만 Group값 유형 만 보유 하는 컨텍스트에서 사용 됩니다 GroupMember. 당신이 볼 수 있듯이 이것은 또한 사용자의 인 에이블먼트를 점검 할 수있게한다.

    public void addUser(User aUser) {
        //original code omitted
        this.assertArgumentTrue(aUser.isEnabled(), "User is not enabled.");

        if (this.groupMembers().add(aUser.toGroupMember()) && !this.isInternalGroup()) {
            //original code omitted
        }
    }

그러나 Driver인스턴스를 전달하면 실수로 Driverwithin을 수정하게 됩니다 Car. 값 참조를 전달하면 프로그래머의 관점에서 변경 사항을 쉽게 추론 할 수 있지만 동시에 DDD는 유비쿼터스 언어에 관한 것이므로 위험할만한 가치가 있습니다.

실제로 인터페이스 분리 원칙 (ISP) 을 적용하기 위해 적절한 이름을 제시 할 수있는 경우 동작 방법이없는 인터페이스에 의존 할 수 있습니다. 또한 불변의 드라이버 참조를 나타내고 기존 드라이버 (예 :)에서만 인스턴스화 할 수있는 값 객체 개념을 생각해 낼 수도 있습니다 DriverDescriptor driver = driver.descriptor().

저는 이러한 집합체가 별도의 경계에있을 수 있다고 생각할 수있게되었으며 이제 Driver BC의 DriverRepository 또는 Driver 엔티티에 의존하여 자동차 BC를 오염시킬 것입니다.

아니요, 실제로는 그렇지 않습니다. 한 컨텍스트의 개념이 다른 컨텍스트로 번지지 않도록하기 위해 항상 부패 방지 계층이 있습니다. BC 주 자동차 운전자 협회에 전념하는 경우 해당 컨텍스트 Car와 같은 기존 개념을 모델링 할 수 있으므로 실제로 훨씬 쉽습니다 Driver.

따라서 DriverLookupServiceBC 주에서 자동차 운전자 협회를 관리 할 책임 이있는 정의 가있을 수 있습니다 . 이 서비스는 Driver Management 컨텍스트에 의해 노출되는 웹 서비스를 호출 할 수 있으며이 컨텍스트 Driver는이 컨텍스트에서 가치 가있는 인스턴스 를 반환 합니다.

웹 서비스가 BC 주간에 반드시 최고의 통합 방법은 아닙니다. 예를 들어 UserCreated드라이버 관리 컨텍스트 의 메시지가 자체 컨텍스트 DB에 드라이버의 표현을 저장하는 원격 컨텍스트에서 사용되는 메시징에 의존 할 수 있습니다 . 그러면 DriverLookupService이 DB를 사용할 수 있고 추가 메시지 (예 :)와 함께 드라이버 데이터가 최신 상태로 유지됩니다 DriverLicenceRevoked.

도메인에 어떤 접근 방식이 더 좋은지 실제로는 알 수 없지만 이것이 결정을 내리기에 충분한 통찰력을 제공 할 수 있기를 바랍니다.


3

질문을하고 (두 가지 대안을 제안하는) 방법은 자동차가 생성 될 때 driverId가 여전히 유효하다는 것입니다.

그러나 차량을 삭제하거나 다른 운전자에게 제공하기 전에 driverId와 관련된 드라이버가 삭제되지 않아야하며, 또한 다른 차량에 운전자가 할당되지 않았을 수도 있습니다 (도메인이 운전자 만 드라이버를 제한하는 경우) 하나의 자동차와 관련이 있습니다)).

유효성 검사 대신 (존재 유효성 검사 포함)을 할당하는 것이 좋습니다. 그런 다음 여전히 할당 된 상태에서 삭제를 허용하지 않기 때문에 생성하는 동안 오래된 데이터의 경쟁 조건과 다른 장기적인 문제를 방지 할 수 있습니다. (할당은 할당을 확인하고 표시하며 원자 적으로 작동합니다.)

Btw, 나는 차와 운전자 사이의 관계가 아마도 차나 운전자와는 ​​별개의 책임이라는 @PriceJones에 동의합니다. 이러한 종류의 연결은 일정 문제 (드라이버, 자동차, 시간대 / 창문, 대체품 등)와 비슷하기 때문에 시간이 지남에 따라 복잡성이 증가합니다. 현재 등록뿐만 아니라 등록. 따라서 BC 주 자체의 가치를 충분히 누릴 수 있습니다.

할당되는 집계 엔티티의 BC 내에서 또는 자동차와 운전자 간의 연관을 담당하는 별도의 BC 내에서 할당 체계 (예 : 부울 또는 참조 카운트)를 제공 할 수 있습니다. 전자를 사용하면 자동차 또는 운전자 BC에 발급 된 (유효한) 삭제 작업을 허용 할 수 있습니다. 후자를 수행하는 경우 자동차 및 운전자 BC의 삭제를 방지하고 대신 자동차 및 운전자 협회 스케줄러를 통해 전송해야합니다.

BC간에 할당 책임을 다음과 같이 나눌 수도 있습니다. 자동차 및 운전자 BC는 각각 할당 된 부울을 BC로 확인하고 설정하는 "할당"체계를 제공합니다. 할당 부울이 설정되면 BC는 해당 엔티티의 삭제를 방지합니다. (그리고 시스템은 자동차 및 운전자 BC가 자동차 / 운전자 협회 스케줄링 BC로부터 할당 및 할당 해제 만 허용하도록 설정됩니다.)

자동차 및 운전자 스케쥴링 BC는 현재 및 미래의 일정 기간 / 지속 기간 동안 자동차와 관련된 운전자의 캘린더를 유지하고, 스케쥴 된 자동차 또는 운전자의 마지막 사용시에만 다른 BC의 할당 해제를 통지합니다.


보다 급진적 인 솔루션으로 자동차 및 운전자 BC를 자동차 전용 / 역사 기록표 공장으로 취급하여 자동차 / 운전자 협회 스케줄러의 소유권을 남길 수 있습니다. 자동차 BC는 자동차의 모든 세부 사항과 함께 VIN과 함께 새로운 자동차를 생성 할 수 있습니다. 자동차의 소유권은 자동차 / 운전자 협회 스케줄러가 처리합니다. 자동차 / 운전자 협회가 삭제되고 자동차 자체가 파손 된 경우에도 자동차 레코드는 정의에 따라 자동차 BC에 여전히 존재하며 자동차 BC를 사용하여 과거 데이터를 조회 할 수 있습니다. 자동차 / 운전자 협회 / 소유권 (과거, 현재, 그리고 잠재적으로 미래에 예정된)은 다른 BC 주에 의해 처리되고 있습니다.


2

집합체 Car에 집합체 Driver에 대한 참조가 있다고 가정 해 봅시다. 이 참조는 Car.driverId를 사용하여 모델링됩니다.

예, 그것은 하나의 집계를 다른 집계에 연결하는 올바른 방법입니다.

도메인 전문가와 대화를 나눌 경우 그러한 참조의 타당성을 의심하지 않습니다

도메인 전문가에게 물어볼만한 올바른 질문은 아닙니다. "드라이버가없는 경우 비즈니스 비용은 얼마입니까?"

아마도 driverRepository를 사용하여 driverId를 확인하지 않을 것입니다. 대신 도메인 서비스를 사용하여 도메인 서비스를 사용하려고합니다. 도메인 서비스는 여전히 기록 시스템을 점검합니다.

그래서 같은

class DriverService {
    private final DriverRepository driverRepository;

    boolean doesDriverExist(DriverId driverId) {
        return driverRepository.exists(driverId);
    }
}

실제로 여러 지점에서 driverId에 대한 도메인을 쿼리합니다.

  • 클라이언트에서 명령을 보내기 전에
  • 응용 프로그램에서 명령을 모델에 전달하기 전에
  • 도메인 모델 내에서 명령 처리 중

이러한 검사 중 일부 또는 전부는 사용자 입력 오류를 줄일 수 있습니다. 그러나 그들은 모두 오래된 데이터로 작업하고 있습니다. 다른 집계는 질문을 한 직후에 변경 될 수 있습니다. 따라서 항상 부정 부정 / 긍정의 위험이 있습니다.

  • 예외 보고서에서 명령이 완료된 후 실행

여기에서 여전히 오래된 데이터로 작업하고 있습니다 (보고서를 실행하는 동안 집계에서 명령이 실행 중일 수 있으며 모든 집계에 대한 최신 쓰기를 보지 못할 수 있음). 그러나 집계 간의 검사가 완벽하지는 않습니다 (Car.create (driver : 7)는 Driver.delete (driver : 7)와 동시에 실행). 따라서 위험에 대한 추가 방어 계층을 제공합니다.


1
Driver.delete존재하지 않아야합니다. 골재가 파괴되는 도메인을 본 적이 없습니다. AR을 주변에두면 고아가 될 수 없습니다.
plalx

1

다음과 같은 질문이 도움이 될 수 있습니다. 자동차가 운전자로 구성되어 있습니까? 나는 실제 세계에서 운전자로 구성된 자동차에 대해 들어 본 적이 없습니다. 이 질문이 중요한 이유는 자동차와 운전자를 독립적으로 생성 한 다음 운전자를 자동차에 할당하는 외부 메커니즘을 만드는 방향을 지시 할 수 있기 때문입니다. 자동차는 운전자 참조없이 존재할 수 있으며 여전히 유효한 자동차입니다.

자동차가 상황에 맞는 운전자를 반드시 가지고 있어야하는 경우 빌더 패턴을 고려할 수 있습니다. 이 패턴은 자동차가 기존 운전자로 구성되도록하는 역할을합니다. 공장은 독립적으로 검증 된 자동차와 운전자를 위해 서비스를 제공하지만 빌더는 자동차가 자동차를 서비스하기 전에 필요한 기준을 갖도록합니다.


자동차 / 드라이버 관계에 대해서도 생각했지만 DriverAssignment 집계를 도입하면 참조를 확인해야하는 동작 만 이동합니다.
VoiceOfUnreason

1

그러나 이제는 불변 점검이 너무 많지 않은지 궁금합니다.

나도 그렇게 생각해. 주어진 DriverId를 DB에서 가져 오면 빈 세트가 없으면 빈 세트를 반환합니다. 따라서 반환 결과를 확인하면 존재하는지 (그리고 가져 오는) 불필요한 지 묻습니다.

그런 다음 클래스 디자인으로 인해 불필요한

  • "주차 된 차량에 운전자가있을 수도 있고 없을 수도있는"요건이있는 경우
  • Driver 객체 DriverId에 생성자가 및가 필요한 경우
  • 는 IF Car요구 만 DriverId하는이 Driver.Id게터를. 세터가 없습니다.

리포지토리는 비즈니스 규칙을위한 장소가 아닙니다

  • A CarDriver(또는 적어도 그의 ID)를 가지고 있는지 걱정합니다 . A가 Driver있으면 관심을 갖습니다 DriverId. Repository데이터 무결성에 대한 염려와는 운전자없는 자동차에 대해 덜 신경 수 없었다.
  • DB에는 데이터 무결성 규칙이 있습니다. 널이 아닌 키, 널이 아닌 제한 조건 등. 데이터 무결성은 비즈니스 규칙이 아니라 데이터 / 테이블 스키마에 관한 것입니다. 우리는이 경우에 강한 상관 관계가 있고 공생 관계가 있지만 두 가지를 섞지 않습니다.
  • a DriverId가 비즈니스 도메인 이라는 사실 은 적절한 클래스에서 처리됩니다.

우려 사항 위반 분리

... Repository.DriverIdExists()질문을 할 때 발생합니다 .

도메인 객체를 만듭니다. 그렇지 않다면 Driver아마도 DriverInfo( DriverId그리고 a 와 Name) 객체를 말하십시오. 은 DriverId구축시에 확인됩니다. 존재해야하며 올바른 유형이어야합니다. 그런 다음 존재하지 않는 driver / driverId를 처리하는 방법은 클라이언트 클래스 디자인 문제입니다.

어쩌면이 Car당신이 호출 할 때까지 벌금 드라이버없이 Car.Drive(). 어떤 경우에는 Car물론 객체가 자체 상태를 보장합니다. Driver잘 하지 않고는 운전할 수 없습니다 .

클래스에서 속성을 분리하는 것은 좋지 않습니다

물론, Car.DriverId원한다면하세요. 그러나 다음과 같이 보일 것입니다.

public class Car {
    // Non-null driver has a driverId by definition/contract.
    protected DriverInfo myDriver;
    public DriverId {get { return myDriver.Id; }}

    public void Drive() {
       if (myDriver == null ) return errorMessage; // or something
       // ... continue driving
    }
}

이거 말고:

public class Car {
    public int DriverId {get; protected set;}
}

이제 Car모든 DriverId유효성 문제-단일 책임 원칙 위반을 처리해야합니다 . 중복 코드 일 것입니다.

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