Уровень обслуживания и ассоциации модели с доменно-ориентированным проектированием

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

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

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

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

Например:

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

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

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

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

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

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

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

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

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

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

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


person Matthieu Napoli    schedule 28.06.2012    source источник


Ответы (4)


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

person eulerfx    schedule 29.06.2012
comment
Хорошо, сначала я понимаю, что смешал сервисы SOA и приложений. И на самом деле кажется, что весь этот вопрос об ассоциациях зависит от того, является ли сущность совокупным корнем или нет. Если это так, я должен использовать репозиторий. Если это часть агрегата, то я должен использовать ассоциации объектов. Я прав? - person Matthieu Napoli; 29.06.2012

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

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

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

Если вы хотите обновить человека или автомобиль, то на уровне приложения (я обычно использую командный подход, поэтому в моем случае все это происходит в обработчике команд, но архитектурно он все еще является частью уровня приложения) вы можете получить 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 MikeSW    schedule 29.06.2012
comment
Вы поднимаете очень хорошие вопросы. Прежде всего, я абсолютно хочу избежать модели анемии, и это, собственно, корень моих вопросов. Это то, какой должна быть архитектура Модель / Сервис? Я сбит с толку: стоит ли мне просто выбросить слой Service, чтобы сохранить логику в модели? (да, модель предметной области очень сложна и оправдывает DDD, пример, который я привел, - это всего лишь очень простой пример). - person Matthieu Napoli; 29.06.2012
comment
Сохраняйте соответствующую логику в домене, она там принадлежит. Используйте доменные службы для операций, которые являются частью домена, но не вписываются в объект домена. Используйте службы на других уровнях для инфраструктурных задач (например, авторизации). - person MikeSW; 29.06.2012

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

Здесь AR - это Driver модель. Имейте в виду, что сервисы могут содержать другие сервисы и что объекты Doctrine 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));
}
person texdc    schedule 07.01.2014
comment
Примечание: я знаю, что это некро-нить, но ответа на нее пока нет. Итак, я отправляю свой ответ на случай, если он будет полезен другим. - person texdc; 08.01.2014

Хорошо, сначала я понимаю, что смешал сервисы SOA и приложений.

Истинный. Вы смешиваете методы уровня домена (DDD) с объектами домена и методы уровня обслуживания (SOA) с объектами передачи данных в вопросе.

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

  1. $car->getDriver() - это совершенно правильный способ доступа к Driver на уровне домена, и его также можно использовать на уровне службы с ограничением, что везде, где потребитель службы запрашивает данные 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); выглядит странно на уровне домена, поскольку он не содержит большого количества бизнес-логики. Но при изменении на $CarService->getCars($nameFilter, $maxNumberOfResults, $offset); он становится пригодным на уровне обслуживания для частичных запросов.

person Lightman    schedule 21.10.2015