Я занимаюсь разработкой базовой архитектуры веб-приложения. Проект соответствует подходу, основанному на управлении доменами, поскольку бизнес-модель и логика очень сложны.
Проект также нацелен на проект SOA (сервис-ориентированная архитектура). Поэтому я многому учусь о сервисах и о том, как создать проект вокруг него.
Следуя предыдущему моему вопросу , у меня есть вопрос относительно ассоциаций в модельных классах .
Я понимаю, что классы моделей не должны знать и делать что-либо, связанное с настойчивостью. Однако мне трудно решить ситуации, связанные с ассоциациями между классами моделей.
Например:
Person
класса Car
имеет один драйвер (например) Где должен быть getDriver
и getCars
?
$car->getDriver()
$personService->getPerson($car->getDriverId())
$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
.
$car->getDriver()
– это совершенно правильный способ доступа к $car->getDriver()
в слое домена, а также может использоваться на уровне обслуживания с ограничением на то, что везде, где пользовательский сервис запрашивает данные о Car
, Служба всегда возвращает Car
с Driver
.
$personService->getPerson($car->getDriverId())
действителен только на уровне обслуживания и недействителен в слое домена. Причина этого метода – данные Driver
слишком велики и сложны, чтобы их всегда возвращали с помощью Car
. Таким образом, служба предоставляет отдельный метод запроса данных Driver
.
$carService->getDriver($car)
недействителен в слое домена и странно видеть на уровне обслуживания, поскольку эта конструкция означает, что пользователь службы должен отправить все данные Car
в CarService
чтобы получить данные Driver
. Лучше отправлять только CarID
и, возможно, в PersonService
, а не в CarService
(вариант 2).
Более сложный пример $person->getCars($nameFilter, $maxNumberOfResults, $offset);
выглядит странно в Domain Layer, так как в нем нет много бизнес-логики. Но если он изменен на $CarService->getCars($nameFilter, $maxNumberOfResults, $offset);
он становится подходящим в Service Layer для частичных запросов.