
Комментарии 9
В состав контекста почему-то включены Presentation и Application. Хотя контекст - это набор доменных объектов/логики и соответствующей им инфраструктуры для взаимодействия с внешними хранилищами данных, но никак не Presentation и Application.
Набор вызовов идёт по цепочке: внешний консюмер -> Presentation -> Application -> контекст (домен->инфраструктура).
Если в Presentation (например на визуальной форме) надо отобразить данные из нескольких контекстов, то соответствующий этому вызову функционал Application может извлечь данные из нескольких контекстов.
Я понял ваш поинт, он тоже есть верным. В моем предложенном варианте я использовал подход (альтернативный), где каждый контекст является максимально автономным и содержит все слои. Это обеспечивает высокую степень инкапсуляции и независимости контекстов, но может привести к некоторому дублированию кода в Interface и Application слоях.
В классическом понимании DDD, ограниченный контекст (Bounded Context) действительно определяет границы модели предметной области и соответствующей инфраструктуры. Однако есть несколько причин, почему я включил Application и Presentation слои в каждый контекст:
Каждый контекст должен быть полностью автономным и иметь собственные механизмы взаимодействия с внешним миром
Включение всех слоев в контекст позволяет рассматривать каждый контекст как полноценный модуль
Особенно полезно если вы в будущем решите превратить эти контексты в микросервисы
Вы выделили ACL в отдельный слой, но без детализации. Интересно Ваше мнение как должны располагаться эти "преобразоваторы" относительно слоёв.
Вводная часть:
Допустим у нас RESTful API и верхнеуровневая задача из джиры "создать роут для добавления записи в туду лист". В этом случае получаем следующие новые "контексты" в основном баунд контексте приложения.
Presentation\CreateRecordControllerApplication\CreateRecordUseCaseDomain\энтити, vo и доменные сервисыInfrastructure\DatabaseRecordRepository,Infrastructure\ApiRecordRepository+ etc
Структуры между слоями:
Соответственно, если говорим о DTO и иже с ними, то получаем:
Presentation\
CreateRecordRequestDTOCreateRecordResponseDTO
Application\
CreateRecordCommandкомандаCreateRecordResultрезультат (допустим комманд бас синхронный)
Domain\
RecordЭнтитяRecordIdVO
Infrastructure\
Ну допустим
array{id: non-empty-string, title: non-empty-string, ...}для энтити (допустим у нас нет ORM)
Детализация самого вопроса:
Вы вводите только два понятия из адаптеров + мапперов. Внешние взаимодействия -- это адаптеры, а внутренние -- мапперы. Из Ваших слов, я могу сделать вывод что должно получиться нечто такое, где Adapter<TInput, TOuput> (+ mapper):
ACL\Adapter\CreateRecordRequestAdapter<array<array-key, mixed>, CreateRecordRequest>- создаёмCreateRecordRequestиз внешних данныхACL\Mapper\CreateRecordCommandMapper<CreateRecordRequest, CreateRecordCommand>- создаём команду из объекта реквеста
А тут создавать команды же можно из разных реквестов, допустим:
array->CreateRecordCommandдля CLI интерфейса
ACL\Mapper\RecordMapper<..., Record>+ACL\Mapper\RecordIdMapper<..., RecordId>- создаём доменные объекты исходя из внутренней логики апп сервисаACL\Adapter\RecordDatabaseAdapter<Record, array{id: non-empty-string, ...}>- получаем данные для БД из энтитии т.д. В обратную сторону, типа апп сервис вернул ОК, что всё сохранилось (синхронно), а потом из результата уже в 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
Ключевые моменты:
ACL — это не просто сборник мапперов и адаптеров, а слой, который управляет взаимодействием между контекстами.
В ACL ходит только Application. Он не смешивает Presentation и Infrastructure, а изолирует зависимости между контекстами. С других мест в ACL стараемся не ходить.
ACL обеспечивает анти-коррупционный барьер, чтобы наш домен не зависел от чужих моделей напрямую.
Таким образом, ACL — это не превращаеться в "мусоркау", а есть границей, защищающая наш домен от внешних изменений и позволяющая четко управлять зависимостями между контекстами.
deleted content
> В ACL ходит только Application. Он не смешивает Presentation и Infrastructure, а изолирует зависимости между контекстами. С других мест в ACL стараемся не ходить.
Ну вот тут, мне кажется, противоречие о котором я и упомянул.
Вы пишите, что он изолирует зависимости между контекстами, однако у нас таких мест, где требуется конвертация (изоляция в т.ч.) несколько:
Physical User Data (
array<array-key, mixed>) + PresentationPresentation + Application
Application + Domain
Application + (via IoC) Infrastructure
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 ?
Связывая Контексты: Руководство по Эффективному Взаимодействию