Соотношения уровня сервиса и модели с поддержкой домена

Я занимаюсь разработкой базовой архитектуры веб-приложения. Проект соответствует подходу, основанному на управлении доменами, поскольку бизнес-модель и логика очень сложны.

Проект также нацелен на проект SOA (сервис-ориентированная архитектура). Поэтому я многому учусь о сервисах и о том, как создать проект вокруг него.

Следуя предыдущему моему вопросу , у меня есть вопрос относительно ассоциаций в модельных классах .

Я понимаю, что классы моделей не должны знать и делать что-либо, связанное с настойчивостью. Однако мне трудно решить ситуации, связанные с ассоциациями между классами моделей.

Например:

  • Person класса
  • класс Car имеет один драйвер (например)

Где должен быть getDriver и getCars ?

  1. в классах модели: $car->getDriver()
  2. в сервисном слое с примитивными типами: $personService->getPerson($car->getDriverId())
  3. в служебном слое с помощью OOP: $carService->getDriver($car)

Решение 1. кажется более естественным. Я использую Doctrine 2, поэтому ассоциации моделей обрабатываются с аннотациями отображения DB. Таким образом, модель не делает ничего, связанного с настойчивостью (хотя это действительно происходит через Doctrine). Это мое любимое решение, но тогда в чем смысл Сервиса, за исключением загрузки списка «автомобилей» для начала?

Решение 2. кажется просто глупым, поскольку оно выбрасывает ООП, и пользователь Model / Service должен знать о модели базы данных для извлечения ассоциации (он должен знать, что этот идентификатор является идентификатором «Человек»). И он должен сам это сделать.

Решение 3. немного лучше, чем решение 2, но все же где OOP ?

Итак, для меня решение 1. является лучшим. Но я видел Решение 2. и Решение 3., используемые в реальных проектах (иногда смешанные вместе), и поэтому у меня есть сомнения.

И вопрос становится более сложным, когда есть дополнительные параметры, например:

 $person->getCars($nameFilter, $maxNumberOfResults, $offset); 

(в этом случае он действительно выглядит как запрос SQL / запрос на сохранение)

Итак, какой из них следует использовать для архитектуры Model / Service в проекте, основанном на подходе, основанном на управлении доменами ? С SOA, должна ли моя модель быть только «тупым» контейнером данных без логики? Если да, то где подход DDD?

    В контексте DDD это проблема решения вопроса о том, выражена ли связь между сущностями через прямую ассоциацию объектов и репозиторий. Оба подхода могут быть действительными и зависят от характера отношений. Например, в вашем домене у человека может быть много автомобилей, связанных с ними, и на самом деле нет смысла иметь прямую связь с набором автомобилей от лица. Помните, что работа объекта или, более конкретно, совокупный корень – это защита инвариантов и обеспечение соблюдения бизнес-правил. Если набор автомобилей, связанных с человеком, не требуется для какого-либо поведения, которое существует в классе человека, то нет причин помещать ассоциацию в лицо человека. Кроме того, как показывает ваш пример, запрос на автомобили может потребоваться отфильтровать. Чтобы ответить на ваш вопрос, я поставил бы ответственность за представление отношений между людьми в репозитории . SOA ортогональна DDD и больше ориентирована на доступ и развертывание бизнес-функций. Информативно рассмотреть взаимодействие между DDD и SOA с точки зрения гексагональной архитектуры, также называемой архитектурой лука . Ваш домен находится в ядре, инкапсулированный набором приложений, которые формируют API-интерфейс вокруг вашего домена. Они отличаются от сервисов SOA, которые являются портами / адаптерами в архитектуре гексагональной / луковой, и они служат для предоставления этих приложений в качестве сервисов SOA.

    Если ваш проект DDD, я не понимаю, почему вам нужна архитектура Model / Service. ИМО создает анемичную модель, и все довольно процедурно.

    Теперь, будучи DDD, это означает, что вы не заботитесь о db. У вас есть хотя (по крайней мере логически) 2 модели: домен и постоянство. Модель домена относится к ассоциациям самым естественным образом, наиболее подходящим для представления бизнес-кейса. «Имеет один драйвер», или у него много, это мышление db, которое не имеет места в DDD. Модель Persistence обрабатывает способ хранения Aggregate Root в db (здесь вы определяете объекты ORM и их отношения и т. Д.).

    О ваших вопросах, прежде всего, имеет значение контекст и цель. Если это строго для запросов (для отображения пользователю), может использоваться простая модель, не требуется DDD и бизнес-правила. Контроллер может запросить непосредственно специализированный репозиторий запросов для данных, возвращенный как DTO.

    Если вы хотите обновить Person или автомобиль, то в Application Layer (обычно я использую подход, основанный на команде, поэтому в моем случае все это происходит в обработчике команд, но в архитектурном плане это все еще часть уровня приложения), вы можете retreieve AR, наиболее подходящий для задачи из (домена) репозитория. Репозиторий домена знает, что getPerson ($ id) должен возвращать объект домена, а не репозиторий запросов, который возвращает простой DTO.

     $person=$repo->getPerson($id); //do stuff $repo->save($person); //optionally raise event (if you're using the domain events apprach) 

    Но сложнее решить, что такое AR в каком контексте. У автомобиля есть один водитель в каком контексте? Водитель действительно принадлежит машине? Есть ли концепция владельца? У вас есть класс Person, но человек может быть водителем или владельцем (или нет, если это компания по прокату). Как вы видите, это в значительной степени зависит от домена, и только после того, как у вас есть четкое изображение домена, вы можете начать думать о том, как вы храните данные и какой объект (объект) возвращается репозиторием.

    Когда вы думаете о том, что происходит, рассмотрите цель как службы, так и модели. Службы находятся на уровне приложения, в то время как модели находятся на уровне домена. Итак, что ваше приложение должно знать о Person ? Не так много, наверное. Пользовательский интерфейс скорее всего отправит некоторые идентификаторы для обработки с запрошенным действием.

    Здесь AR – модель Driver . Имейте в виду, что услуги могут содержать другие услуги, и что доктрина «Учение» является POPOs и не нуждается в анемии . Кроме того, попытайтесь отделить развитие мыслительных процессов от настойчивости. Например, $driverId не обязательно должен быть целым числом, это может быть любой уникальный идентификатор, относящийся к домену.

     // DriverService // If more parameters are needed, consider passing in a command object public function beginTrip($driverId, $carId, $fromLocationId, $toLocationId) { $driver = $this->repository->find($driverId); $car = $this->carService->getAvailableCar($carId, $driverId); $withItenerary = $this->locationService->buildItenerary( [$fromLocationId, $toLocationId] ); $driver->drive($car, $withItenerary); // actual 'driving' logic goes here $this->eventService->publish(new BeginTripEvent($driver, $car, $withItenerary)); } 

    ОК сначала я понимаю, что я использовал SOA и прикладные сервисы.

    Правда. Вы смешиваете методы уровня домена (DDD) с объектами Domain Objects и Service Layer (SOA) с объектами передачи данных в вопросе.

    Объекты уровня домена – это разные объекты, кроме объектов уровня обслуживания! Например, на уровне сервиса может быть объект CarDTO вместо объекта « Car объекта « DriverDTO вместо объекта « Driver .

    1. $car->getDriver() – это совершенно правильный способ доступа к $car->getDriver() в слое домена, а также может использоваться на уровне обслуживания с ограничением на то, что везде, где пользовательский сервис запрашивает данные о Car , Служба всегда возвращает Car с Driver .

    2. $personService->getPerson($car->getDriverId()) действителен только на уровне обслуживания и недействителен в слое домена. Причина этого метода – данные Driver слишком велики и сложны, чтобы их всегда возвращали с помощью Car . Таким образом, служба предоставляет отдельный метод запроса данных Driver .

    3. $carService->getDriver($car) недействителен в слое домена и странно видеть на уровне обслуживания, поскольку эта конструкция означает, что пользователь службы должен отправить все данные Car в CarService чтобы получить данные Driver . Лучше отправлять только CarID и, возможно, в PersonService , а не в CarService (вариант 2).

    Более сложный пример $person->getCars($nameFilter, $maxNumberOfResults, $offset); выглядит странно в Domain Layer, так как в нем нет много бизнес-логики. Но если он изменен на $CarService->getCars($nameFilter, $maxNumberOfResults, $offset); он становится подходящим в Service Layer для частичных запросов.