Как стать автором
Обновить
27
0
Илья Шихалеев @ilyashikhaleev

Веб-разработчик

Отправить сообщение

Интерфейс находится на уровне домена, чтобы оттуда выбрасывать доменные события.

Для обработки события в этом же контексте в другом агрегате используется уровень приложения, именно там происходит "подписывание" агрегатов на разные события.

Можно ли это сделать только на уровне домена - думаю да. Но появится код, который должен будет заниматься подпиской при инициализации/создании агрегатов на доменные события :) если на вашем языке/фреймворке это можно сделать красиво - ок :)

Отличная получилась футболка! 🍻

Думаю позже выложу в отдельной статье :) пока дам время решить тем, кому интересно :) если актуально решающим, могу выкладывать подсказки раз в неделю, как и планировал изначально :)

Поздравляю! Шифр с третьей футболки верно разгадан! :) сейчас с вами свяжемся по размерам :)

Похоже вы на верном пути :) Но ответ неправильный(

Да, всего подсказок было 6, причём две самые большие и выданные коллегам последними были в видео, ссылка на которое в статье :) итого статья содержит 3 из 6 подсказок. Коллегам в момент вручения футболок я озвучил только 1 подсказку про надпись на футболках :)

Поздравляю! Шифр со второй футболки верно разгадан! :) сейчас с вами в лс свяжемся вопросам доставки и размеров :)

Поздравляю! Ответ верный :) сегодня с вами свяжемся вопросам доставки и размеров!

В статье картинки в исходном разрешении, для разгадки этого разрешения достаточно :)

На вопрос "откуда" - одного конкретного источника не укажу)

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

Реализация всех слоёв в рамках модуля позволяет нам легко вынести контекст в отдельный микросервис и изолирует код контекстов. Используя deptrac мы настроили правила, по которым модули друг от друга не зависят ни на одном из уровней, кроме anti corruption layer.

На примере используемой нами Doctrine: https://www.doctrine-project.org/projects/doctrine-orm/en/2.9/tutorials/getting-started.html

Doctrine ORM does not use any of the methods you defined: it uses reflection to read and write values to your objects, and will never call methods, not even __construct.

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

"ORM как раз ничего не создает, а восстанавливает из хранилища в соответствии с persistance ignorance." - тут всё верно. При восстановлении агрегата из базы конструктор не вызывается. Это особо важно в случаях, когда есть определённые бизнес правила создания агрегата. Потому за всё время жизни он и правда создаётся один раз. В примере с заказами - заказ создан один раз, во всех остальных use case он восстанавливается из базы в текущем состоянии.

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

"Архитектурная ошибка где-то здесь, получилась жёсткая связь". Жёсткой связи между ORM и доменом нет, как создавать агрегат решается в домене, а вот как его восстановить - это отдельный вопрос вопрос. В рамках ORM может быть прописан маппинг на базу (инфраструктурный код), в рамках go сервисов мы пробовали в домен добавлять специальный метод "восстановления" с набором параметров, мапающихся на состояние агрегата. При этом в обоих случаях надо будет вызывать дополнительно инъекцию диспатчера, если он будет полем агрегата. И вот этого как раз делать не хочется.

Интеграционные события у нас не обрабатываются синхронно даже в рамках монолита. Они всегда проходят через брокер и обрабатываются асинхронно. Если же надо обновить данные модуля А на изменения модели из модуля Б синхронно, то тут обычно делается сага через оркестрацию с синхронным вызовом методов модулей А и Б и с компенсирующими действиями. Мы реализуем это либо отдельно написанной сагой, либо путём вызова из app сервиса модуля Б через anti corruption layer методов API модуля А. И тут в целом не важно, оба модуля в монолите, или один из них уже перенесён в микросервис. Но важно помнить, что синхронный вызов может увеличивать связанность модулей.

А насчёт получения данных из домена для отдачи в ответ на запрос - тут просто добавляется подписка на доменное событие и данные, полученные из него, возвращаются наружу)

Только добрался до вашего вопроса) за минусы других людей не могу отвечать)

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

  • В первую очередь агрегат при восстановлении из базы у нас собирается в рамках монолита с помощью ORM, усложнять логику его конструирования, прокидывая актуальный dispatcher в методы восстановления агрегата, не хотелось. Наш диспатчер, ввиду multi tenant модели, инициализируется на каждый запрос с контекстом запроса и там же на него подписываются нужные handler'ы (ведь важно в рамках сервисов инициализировать, в каком tenant идёт сейчас работа и местами другие контекстные параметры). В рамках PHP монолита пришлось бы писать свои обёртки над ORM, что в последующем могло усложнить её обновление. В рамках go микросервисов мы восстанавливаем агрегаты специальными методами, данные подготавливают реализации репозиториев ручками из базы. В этом случае пришлось бы прокидывать диспатчер в репозитории. В целом, восстановление состояния агрегата тоже интересная задача со своими подводными камнями)

  • В момент создания агрегата тоже пришлось бы прокидывать этот же диспатчер, то есть опять не обойтись без доменного или app сервиса, который владеет ссылкой на диспатчер.

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

По вопросу попадания лишних методов в модель - это некоторый мой личный пуризм) Идея в том, что предметная модель отвечает за свои инварианты, наличие у неё метода "получить события" с точки зрения экспертов предметной области непонятно)

По поводу дублирования методов агрегата в сервисе - это тот самый компромисс, который был принят, в итоге данное решение зафиксировано у нас на уровне архитектурного стандарта) Плюс, похожий подход описывается в книге DDD in PHP. В рамках статьи у меня не было намерения показать, что выбранные мной методы единственно верные, хотелось поделиться опытом, поскольку в целом вопрос с доменными событиями мне интересен, а в процессе реализации возникло много нюансов, и ответы на возникающие вопросы приходилось искать по нескольким книжкам по DDD, сайту microsoft и различным статьям) В итоге собрал различные нюансы по событиям в рамках статьи)

Не планировали, но идея хорошая, спасибо) Если доработок для обобщения будет не много, то закинем вот сюда - https://github.com/ispringtech

У нас на каждый микросервис своя база данных. У монолита тоже отдельная база, с таблицами под каждый модуль и правилом не читать из "чужих" таблиц.

Ивенты в базе хранятся для того, чтобы быть сохранёнными в одной транзакции с агрегатами. После того, как транзакция закрывается и мы гарантированно сохранили изменения в модели, отдельная горутина (либо этот же скрипт PHP по событию закрытия транзакции) отправляет сообщения в брокер. То есть мы гарантированно сохранили изменения в базу и гарантированно, но с некоторой задержкой, отправили события в message bus. Про данный паттерн можно подробнее почитать тут - https://microservices.io/patterns/data/transactional-outbox.html Конечно же в рамках реализации есть ряд интересных моментов)

Если не ответил, пожалуйста, опишите подробнее проблему)

При выборе решения меня во втором варианте смутил лишний интерфейс у доменных объектов и необходимость либо ручками писать для каждой модели работу с коллекцией событий, либо использовать trait. Потому выбрал доменные сервисы) Когда выбирал реализацию, смотрел варианты в книжке DDD in PHP, рекомендую :) По мапингу в ОРМ - для модульного монолита придётся для каждого агрегата делать отдельный event store. Я решил хранить в одной таблице все события монолита, в каждом микросервисе тоже поднимать одну таблицу событий на микросервис.

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

Отлично написано! Круто, что не слово в слово с доклада, интересно читать! А файл для deptrac с последней версии взят? Там вроде немного формат регулярок изменился

Не было в планах, но могу помочь с организацией ;) пока из-за карантина оффлайна почти нет, не считая beer php/go митапов :) в телеграмм есть канал всех проводимых митапов в йо, про которые я знаю — it_yola :)

1

Информация

В рейтинге
Не участвует
Откуда
Йошкар-Ола, Марий Эл, Россия
Работает в
Дата рождения
Зарегистрирован
Активность