엔티티 메소드 호출에 대한 DDD 인젝션 서비스


11

질문의 짧은 형식

엔티티 메소드 호출에 서비스를 삽입하는 것이 DDD 및 OOP의 우수 사례 내에 있습니까?

긴 형식 예

DDD에 고전적인 Order-LineItems 사례가 있다고 가정합니다. 여기에서 Order라는 도메인 엔터티가 있으며,이 루트는 또한 Root Root 역할을하며 엔터티는 Value Objects뿐만 아니라 Line Item의 컬렉션으로 구성됩니다. 엔티티.

응용 프로그램에서 유창한 구문을 원한다고 가정하면 다음과 같이 할 수 있습니다 ( getLineItems방법 을 호출하는 2 행의 구문에 유의하십시오 ).

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems($orderService) as $lineItem) {
  ...
}

우리는 일종의 LineItemRepository를 OrderEntity에 주입하고 싶지 않습니다. 이는 내가 생각할 수있는 몇 가지 원칙을 위반하기 때문입니다. 그러나 구문의 유창함은 테스트뿐만 아니라 읽고 유지 관리하기 쉽기 때문에 실제로 원하는 것입니다.

다음 코드를 고려하여 메소드 getLineItems를 기록하십시오 OrderEntity.

interface IOrderService {
    public function getOrderByID($orderID) : OrderEntity;
    public function getLineItems(OrderEntity $orderEntity) : LineItemCollection;
}

class OrderService implements IOrderService {
    private $orderRepository;
    private $lineItemRepository;

    public function __construct(IOrderRepository $orderRepository, ILineItemRepository $lineItemRepository) {
        $this->orderRepository = $orderRepository;
        $this->lineItemRepository = $lineItemRepository;
    }

    public function getOrderByID($orderID) : OrderEntity {
        return $this->orderRepository->getByID($orderID);
    }

    public function getLineItems(OrderEntity $orderEntity) : LineItemCollection {
        return $this->lineItemRepository->getLineItemsByOrderID($orderEntity->ID());
    }
}

class OrderEntity {
    private $ID;
    private $lineItems;

    public function getLineItems(IOrderServiceInternal $orderService) {
        if(!is_null($this->lineItems)) {
            $this->lineItems = $orderService->getLineItems($this);
        }
        return $this->lineItems;
    }
}

DDD 및 OOP의 핵심 원칙을 위반하지 않고 엔터티에서 유창한 구문을 구현하는 것이 허용되는 방법입니까? 우리에게는 인프라 계층 (서비스 내에 중첩되어 있음)이 아닌 서비스 계층 만 노출하기 때문에 괜찮습니다.

답변:


9

그건 완전히 잘 엔티티 호출 도메인 서비스를 전달합니다. 예를 들어 고객 유형에 의존 할 수있는 복잡한 알고리즘으로 송장 합계를 계산해야합니다. 다음과 같이 보일 것입니다 :

class Invoice
{
    private $currency;
    private $customerId;

    public function __construct()
    {
    }

    public function sum(InvoiceCalculator $calculator)
    {
        $sum =
            new SumRecord(
                $calculator->calculate($this)
            )
        ;

        if ($sum->isZero()) {
            $this->events->add(new ZeroSumCalculated());
        }

        return $sum;
    }
}

그러나 다른 방법은 도메인 이벤트 를 통해 도메인 서비스에있는 비즈니스 로직을 분리하는 것입니다 . 이 접근 방식은 다른 응용 프로그램 서비스뿐만 아니라 동일한 데이터베이스 트랜잭션 범위를 의미합니다.

세 번째 접근 방식은 내가 선호하는 방법입니다. 도메인 서비스를 사용하는 경우, 주로 동사가 아닌 명사로 개념을 모델링하기 때문에 일부 도메인 개념을 놓 쳤음을 의미 합니다. 따라서 이상적으로 는 도메인 서비스가 전혀 필요하지 않으며 비즈니스 논리의 모든 부분이 데코레이터에 있습니다.


6

나는 여기에 몇 가지 답변을 읽는 것에 충격을 받았습니다.

일부 비즈니스 계산을 위임하기 위해 도메인 서비스를 DDD의 엔티티 메소드로 전달하는 것은 완벽하게 유효합니다. 예를 들어, 비즈니스 로직을 수행하고 이벤트를 발생시키기 위해 집계 루트 (엔터티)가 http를 통해 외부 리소스에 액세스해야한다고 가정하십시오. 엔티티의 비즈니스 방법을 통해 서비스를 주입하지 않으면 어떻게해야합니까? 엔티티 내에서 http 클라이언트를 인스턴스화 하시겠습니까? 그것은 끔찍한 생각처럼 들립니다.

잘못된 것은 생성자를 통해 서비스를 집계하여 주입하는 것입니다. 그러나 비즈니스 방법을 통해 그것은 정상이며 완벽합니다.


1
귀하가 제공 한 사례가 도메인 서비스의 책임이 아닌 이유는 무엇입니까?
e_i_pi

1
도메인 서비스이지만 비즈니스 방식으로 주입됩니다. 응용 프로그램 계층은 오케 스트레이터
일뿐입니다

DDD에 경험이 없지만 Application Service에서 도메인 서비스를 호출해서는 안되며 도메인 서비스 유효성 검사 후에도 해당 Application Service를 통해 엔티티 메소드를 계속 호출합니까? 도메인 서비스가 리포지토리를 통해 데이터베이스 호출을 실행하기 때문에 프로젝트에서 동일한 문제에 직면하고 있습니다 ... 이것이 괜찮은지 모르겠습니다.
Muflix

도메인 서비스는 오케스트레이션해야합니다. 나중에 응용 프로그램에서 호출하면 응답을 처리 한 다음 무언가를 수행한다는 의미입니다. 아마도 그것은 비즈니스 로직처럼 들릴 것입니다. 그렇다면 도메인 계층에 속하고 나중에 응용 프로그램이 종속성을 해결하고 집계에 주입합니다. 도메인 서비스는 구현 적중 데이터베이스가 인프라 스트럭처 계층 (인터페이스 / 계약이 아닌 구현)에 속해야하는 저장소를 삽입 할 수 있습니다. 유비쿼터스 언어를 설명하면 도메인에 속합니다.
diegosasw 19 년

5

엔티티 메소드 호출에 서비스를 삽입하는 것이 DDD 및 OOP의 우수 사례 내에 있습니까?

아닙니다. 도메인 계층 (엔터티, 값 개체, 팩토리 및 도메인 서비스 포함) 내부에 어떤 것도 주입해서는 안됩니다. 이 계층은 모든 프레임 워크, 타사 라이브러리 또는 기술을 무시하고 IO 호출을하지 않아야합니다.

$order->getLineItems($orderService)

집계는 주문 항목을 반환하기 위해 다른 것이 필요하지 않기 때문에 잘못되었습니다. 전체 집계는 이미 메서드 호출 전에로드해야합니다. 이것이 게으른로드되어야한다고 생각되면 두 가지 가능성이 있습니다.

  1. 집계 경계가 잘못되어 너무 큽니다.

  2. 이 사용 사례에서는 집계를 읽기 전용으로 사용합니다. 가장 좋은 해결책은 쓰기 모델을 읽기 모델에서 분리하는 것입니다 (예 : CQRS 사용 ). 이 더 깨끗한 아키텍처에서는 집계를 쿼리 할 수 ​​없지만 읽기 모델을 쿼리 할 수 ​​있습니다.


유효성 검사를 위해 데이터베이스 호출이 필요한 경우 응용 프로그램 서비스에서 호출하여 결과를 도메인 서비스로 전달하거나 직접 루트 루트로 전달한 다음 리포지토리를 도메인 서비스에 주입해야합니까?
Muflix

1
@Muflix 네, 맞습니다
Constantin Galbenu

3

DDD 전술 패턴의 핵심 아이디어 : 응용 프로그램은 집계 루트에 작용하여 응용 프로그램의 모든 데이터에 액세스합니다. 이는 도메인 모델 외부에서 액세스 할 수 있는 유일한 개체 는 집계 루트 라는 것을 의미합니다 .

광고 주문 집계 루트는 컬렉션을 수정할 수있는 광고 항목 컬렉션에 대한 참조를 생성하지 않으며 컬렉션을 수정할 수있는 모든 광고 항목에 대한 참조 컬렉션을 생성하지 않습니다. 주문 집계를 변경하려는 경우 할리우드 원칙이 적용됩니다 : "말하지 마십시오".

값은 본질적으로 불변이므로 집계 내에서 을 반환하는 것이 좋습니다 . 사본을 변경하여 내 데이터를 변경할 수 없습니다.

도메인 서비스를 인수로 사용하여 집계가 올바른 값을 제공하도록 지원하는 것은 매우 합리적인 일입니다.

집계에 이미 액세스 권한이 있어야하기 때문에 일반적으로 도메인 서비스를 사용하여 집계 내부에있는 데이터에 대한 액세스를 제공하지 않습니다.

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems($orderService) as $lineItem) {
  ...
}

따라서이 광고 주문의 광고 항목 값 컬렉션에 액세스하려는 경우 철자가 이상합니다. 더 자연스러운 철자는

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems() as $lineItem) {
  ...
}

물론 이는 광고 항목이 이미로드되었음을 전제로합니다.

일반적인 패턴은 골재의 하중에 특정 사용 사례에 필요한 모든 상태가 포함된다는 것입니다. 다시 말해, 동일한 집계를로드하는 여러 가지 방법 이있을 수 있습니다 . 저장소 방법은 목적에 적합합니다 .

이 방법은 원래 Evans에서 찾을 수있는 것이 아니며 집계에서 단일 데이터 모델과 연관된 것으로 가정했습니다. CQRS에서 더 자연스럽게 떨어집니다.


고마워 나는 이제 "빨간 책"의 절반 정도를 읽었으며, 할리우드 계층을 인프라 계층에 올바르게 적용하는 것을 처음으로 맛 보았습니다. 이 모든 답변을 다시 읽으면 모두 좋은 지적을 할 수 있지만 귀하의 의견 lineItems()은 집계 루트의 첫 번째 검색 범위 와 사전로드와 관련하여 매우 중요한 점이 있다고 생각합니다 .
e_i_pi

3

일반적으로 집계에 속하는 가치 개체는 자체적으로 저장소가 없습니다. 그것들을 채우는 것은 전체 루트의 책임입니다. 귀하의 경우, Order 엔티티와 OrderLine 값 오브젝트를 모두 채우는 것은 OrderRepository의 책임입니다.

ORM의 경우 OrderRepository의 인프라 구현은 일대 다 관계이며 OrderLine을 열성적으로 또는 게으른 것으로 선택할 수 있습니다.

귀하의 서비스가 정확히 무엇을 의미하는지 잘 모르겠습니다. "응용 프로그램 서비스"에 매우 가깝습니다. 이 경우 일반적으로 루트 / 엔터티 / 값 개체 집계에 서비스를 삽입하는 것은 좋지 않습니다. 응용 프로그램 서비스는 집계 루트 / 엔터티 / 값 개체 및 도메인 서비스의 클라이언트 여야합니다. 서비스에 대한 또 다른 점은 Application Service에 가치 객체를 노출시키는 것도 좋은 생각이 아닙니다. 집계 루트로 액세스해야합니다.


2

정답은 '아니오'입니다. 엔티티 메소드로 서비스를 전달하지 마십시오.

해결 방법은 간단합니다. 주문 리포지토리가 모든 품목과 함께 주문을 반환하도록하세요. 귀하의 경우 집계는 Order + LineItems이므로 리포지토리가 완전한 집계를 반환하지 않으면 작업을 수행하지 않는 것입니다.

더 넓은 원리는 기능 비트 (예 : 도메인 로직)를 비 기능 비트 (예 : 지속성)와 분리하여 유지하는 것입니다.

한가지 더 : 가능하다면 이것을 피하십시오.

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems() as $lineItem) {
  ...
}

대신에 이것을하십시오

$order = $orderService->getOrderByID($orderID);
$order->doSomethingSignificant();

객체 지향 디자인에서는 객체 데이터에서 낚시를 피하려고합니다. 우리는 객체가 원하는 것을하도록 요청하는 것을 선호합니다.

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