Краткий формат вопроса
Находится ли в лучших практиках DDD и OOP внедрение служб при вызовах методов сущностей?
Пример длинного формата
Допустим, у нас есть классический случай Order-LineItems в DDD, где у нас есть объект домена, называемый заказом, который также действует как совокупный корень, и этот объект состоит не только из его объектов-значений, но и из коллекции элементов строки Сущности.
Предположим, нам нужен свободный синтаксис в нашем приложении, чтобы мы могли сделать что-то вроде этого (отметив синтаксис в строке 2, где мы вызываем getLineItems
метод):
$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 и ООП? Мне кажется, это нормально, так как мы показываем только уровень сервиса, а не уровень инфраструктуры (который вложен в сервис)
Нет, вы не должны внедрять что-либо внутри вашего доменного уровня (это включает сущности, объекты-ценности, фабрики и доменные службы). Этот уровень не должен зависеть от какой-либо инфраструктуры, сторонних библиотек или технологий и не должен делать никаких вызовов ввода-вывода.
Это неправильно, так как Агрегату не нужно больше ничего, кроме себя, чтобы возвращать элементы заказа. Весь совокупный уже должен быть загружен до его вызова метода. Если вы чувствуете, что это должно быть отложено, есть две возможности:
Границы ваших агрегатов неверны, они слишком велики.
В этом случае вы используете Агрегат только для чтения. Лучшее решение - отделить модель записи от модели чтения (т.е. использовать CQRS ). В этой более чистой архитектуре вам не разрешено запрашивать Агрегат, но модель чтения.
источник
Основная идея тактических шаблонов DDD: приложение получает доступ ко всем данным в приложении, действуя на основе общего корня. Это означает, что единственными объектами, которые доступны за пределами доменной модели, являются совокупные корни.
Корень агрегата Order никогда не будет давать ссылку на его коллекцию lineitem, которая позволит вам изменить коллекцию, а также не даст коллекцию ссылок на любую позицию, которая позволит вам изменить ее. Если вы хотите изменить агрегат заказа, применяется голливудский принцип: «Скажи, не спрашивай».
Возврат значений из агрегата - это нормально, потому что значения по своей природе неизменны; Вы не можете изменить мои данные, изменив свою копию.
Использование доменной службы в качестве аргумента для помощи агрегату в предоставлении правильных значений - вполне разумная вещь.
Обычно вы не используете доменную службу для предоставления доступа к данным, находящимся внутри агрегата, поскольку агрегат уже должен иметь к нему доступ.
Так что это написание странно, если мы пытаемся получить доступ к коллекции значений позиций в этом заказе. Более естественное написание будет
Конечно, это предполагает, что позиции уже загружены.
Обычный шаблон заключается в том, что загрузка агрегата будет включать все состояния, необходимые для конкретного варианта использования. Другими словами, у вас может быть несколько разных способов загрузки одного и того же агрегата; ваши методы хранилища подходят для цели .
Этот подход не тот, который вы найдете в оригинальном Эване, где он предполагал, что с агрегатом будет связана одна модель данных. Это более естественно выпадает из CQRS.
источник
lineItems()
и предварительной загрузки при первом извлечении совокупного корня.Вообще говоря, объекты-ценности, принадлежащие агрегату, сами по себе не имеют репозитория. Совокупная ответственность root - заполнять их. В вашем случае ответственность вашего OrderRepository состоит в заполнении сущности Order и объектов значений OrderLine.
Что касается реализации инфраструктуры OrderRepository, то в случае ORM это отношение один-ко-многим, и вы можете выбрать либо горячую, либо ленивую загрузку OrderLine.
Я не уверен, что именно означают ваши услуги. Это довольно близко к «Службе приложений». Если это так, то, как правило, не рекомендуется внедрять сервисы в Aggregate root / Entity / Value Object. Служба приложений должна быть клиентом Агрегированного корневого / Entity / Value Object и Domain Service. Еще одна вещь, связанная с вашими сервисами, это то, что показ значимых объектов в Application Service также не очень хорошая идея. Они должны быть доступны по совокупному корню.
источник
Ответ: определенно НЕТ, избегайте прохождения служб в методах сущностей.
Решение простое: просто позвольте репозиторию Order вернуть Order со всеми его LineItems. В вашем случае агрегат - это Order + LineItems, поэтому, если репозиторий не возвращает полный агрегат, он не выполняет свою работу.
Более широкий принцип: хранить функциональные биты (например, логика домена) отдельно от нефункциональных битов (например, постоянство).
Еще одна вещь: если вы можете, постарайтесь избежать этого:
Сделай это вместо
В объектно-ориентированном дизайне мы стараемся избегать рыбной ловли в объектных данных. Мы предпочитаем просить объект делать то, что мы хотим.
источник