Обновить

Комментарии 9

В состав контекста почему-то включены Presentation и Application. Хотя контекст - это набор доменных объектов/логики и соответствующей им инфраструктуры для взаимодействия с внешними хранилищами данных, но никак не Presentation и Application.
Набор вызовов идёт по цепочке: внешний консюмер -> Presentation -> Application -> контекст (домен->инфраструктура).

Если в Presentation (например на визуальной форме) надо отобразить данные из нескольких контекстов, то соответствующий этому вызову функционал Application может извлечь данные из нескольких контекстов.

Я понял ваш поинт, он тоже есть верным. В моем предложенном варианте я использовал подход (альтернативный), где каждый контекст является максимально автономным и содержит все слои. Это обеспечивает высокую степень инкапсуляции и независимости контекстов, но может привести к некоторому дублированию кода в Interface и Application слоях.

В классическом понимании DDD, ограниченный контекст (Bounded Context) действительно определяет границы модели предметной области и соответствующей инфраструктуры. Однако есть несколько причин, почему я включил Application и Presentation слои в каждый контекст:

  • Каждый контекст должен быть полностью автономным и иметь собственные механизмы взаимодействия с внешним миром

  • Включение всех слоев в контекст позволяет рассматривать каждый контекст как полноценный модуль

  • Особенно полезно если вы в будущем решите превратить эти контексты в микросервисы

Вы выделили ACL в отдельный слой, но без детализации. Интересно Ваше мнение как должны располагаться эти "преобразоваторы" относительно слоёв.

Вводная часть:

Допустим у нас RESTful API и верхнеуровневая задача из джиры "создать роут для добавления записи в туду лист". В этом случае получаем следующие новые "контексты" в основном баунд контексте приложения.

  1. Presentation\CreateRecordController

  2. Application\CreateRecordUseCase

  3. Domain\ энтити, vo и доменные сервисы

  4. Infrastructure\DatabaseRecordRepository, Infrastructure\ApiRecordRepository + etc

Структуры между слоями:

Соответственно, если говорим о DTO и иже с ними, то получаем:

  1. Presentation\

  • CreateRecordRequest DTO

  • CreateRecordResponse DTO

  1. Application\

  • CreateRecordCommand команда

  • CreateRecordResult результат (допустим комманд бас синхронный)

  1. Domain\

  • Record Энтитя

  • RecordId VO

  1. Infrastructure\

  • Ну допустим array{id: non-empty-string, title: non-empty-string, ...} для энтити (допустим у нас нет ORM)

Детализация самого вопроса:

Вы вводите только два понятия из адаптеров + мапперов. Внешние взаимодействия -- это адаптеры, а внутренние -- мапперы. Из Ваших слов, я могу сделать вывод что должно получиться нечто такое, где Adapter<TInput, TOuput> (+ mapper):

  1. ACL\Adapter\CreateRecordRequestAdapter<array<array-key, mixed>, CreateRecordRequest> - создаём CreateRecordRequest из внешних данных

  2. ACL\Mapper\CreateRecordCommandMapper<CreateRecordRequest, CreateRecordCommand> - создаём команду из объекта реквеста

  • А тут создавать команды же можно из разных реквестов, допустим: array -> CreateRecordCommand для CLI интерфейса

  1. ACL\Mapper\RecordMapper<..., Record> + ACL\Mapper\RecordIdMapper<..., RecordId> - создаём доменные объекты исходя из внутренней логики апп сервиса

  2. ACL\Adapter\RecordDatabaseAdapter<Record, array{id: non-empty-string, ...}> - получаем данные для БД из энтити

  3. и т.д. В обратную сторону, типа апп сервис вернул ОК, что всё сохранилось (синхронно), а потом из результата уже в Response

Сомнения

Выделяя ACL в корень - мы запихиваем туда преобразования, связанные со всеми слоями сразу и получается такая большая мусорка, где преобразования Presentation <-> Application и Infrastructure <-> Domain располагаются в Adapter, а всё остальное в Mapper. Причём фактически получая работу с представлением и работу с инфрой на одном и том же уровне ACL\Adapter\XxxRequestToCommand + ACL\Adapter\XXXEntityToDatabaseData.

И что-то вот это меня как-то смущает.

Можете пожалуйста раскрыть где я ошибаюсь и/или что упускаю?

Спасибо за комментарий! Надеюсь я верно его интерпретировал в своей голове), если нет напиши (напишите) мне лично и можно отдельно обсудить этот момент.

И так, У нас есть слой Application, который агрегирует бизнес-логику. Если на этом уровне я понимаю что мне нужны данные из другого контекста, но в моей доменной модели их нет, я не обращаюсь к чужой доменной модели напрямую. Вместо этого я обращаюсь к ACL, который инкапсулирует взаимодействие с другим контекстом.

Таким образом, связь всегда выглядит так:
Application_XXX → (Domain_XXX + ACL_YYY), где ACL_YYY → Domain_YYY.
И в обратную сторону:
Domain_YYY → ACL_YYY → потом ACL-маппер → Application_XXX

Ключевые моменты:

  1. ACL — это не просто сборник мапперов и адаптеров, а слой, который управляет взаимодействием между контекстами.

  2. В ACL ходит только Application. Он не смешивает Presentation и Infrastructure, а изолирует зависимости между контекстами. С других мест в ACL стараемся не ходить.

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

Таким образом, ACL — это не превращаеться в "мусоркау", а есть границей, защищающая наш домен от внешних изменений и позволяющая четко управлять зависимостями между контекстами.

deleted content

> В ACL ходит только Application. Он не смешивает Presentation и Infrastructure, а изолирует зависимости между контекстами. С других мест в ACL стараемся не ходить.

Ну вот тут, мне кажется, противоречие о котором я и упомянул.

Вы пишите, что он изолирует зависимости между контекстами, однако у нас таких мест, где требуется конвертация (изоляция в т.ч.) несколько:

  1. Physical User Data (array<array-key, mixed>) + Presentation

  2. Presentation + Application

  3. Application + Domain

  4. Application + (via IoC) Infrastructure

  5. Infrastructure + Physical Internal Data (array<array-key, mixed>)

Но при этом вы так же говорите, что в идеале это только Application слой.

А что делать с DTO для реквестов/респонзов, например? Переводя на более "человеческий" -- какой-нибудь самописный #[MapRequestPayload]. Он ведь никакого отношения к Application не имеет.

Там же ровно такие же "трансформеры" и "мапперы" используются, что и в случае "защиты домена".

P.S. Аааааа.... Между контекстами, а не между слоями. Теперь понял, спасибо!
*Вопрос засунул под спойлер, снимается

Взаимодействие ACL_YYY → Domain_YYY идёт полным путём ACL_YYY → Presentation_YYY → Application_YYY → Domain_YYY ?

Нет. В основном через Application

Каждый контекст - это отдельное приложение или как-то иначе организовано?

В целом да если у вас монорепозиторий. Но не обязательно.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации