Шаг 3: независимые подписчики
Диспетчер ловит ошибку каждого слушателя, отдаёт её в отчёт и зовёт следующих: упавшая аналитика больше не мешает письму.
Урок: События вместо прямых вызовов. Предыдущий шаг: доменное событие и синхронный диспетчер.
Что было
Checkout публиковал OrderPlaced, слушатели подписывались сами. Но ошибка любого слушателя прерывала dispatch, долетала до place, и все, кто был подписан после упавшего, не срабатывали.
Что меняем
EventDispatcherполучает в конструктореreport- функцию, которая принимает ошибку. В приложении она пишет в лог и в трекер ошибок.dispatchоборачивает вызов каждого слушателя: ошибка уходит вreport, цикл идёт дальше. В GoDispatchбольше не возвращаетerror, аPlace- тем более: оформлению нечего сообщить вызывающему коду о чужих реакциях.- Слушатели и касса не меняются.
Здесь меняется поведение, и это единственное намеренное изменение в уроке. Тест с лежащей аналитикой теперь утверждает обратное: place не падает, сборка запрошена, письмо ушло, а в отчёте одна ошибка "analytics is down". Первый тест и тест без подписчиков остались прежними.
Как это собирается
Место сборки создаёт report вместе с диспетчером: здесь решается, куда уходят сбои реакций. Подписки те же, что на шаге 2. Пример оформляет те же два заказа: оба оформлены, оба письма ушли, а сбой аналитики виден в строке reported.
Сценарий целиком с выводом: пример на PHP, он же на Go, Python и TypeScript в панели кода.
Что стало лучше
- Реакции действительно независимы: сбой одной не трогает другие, порядок подписки больше не влияет на результат.
placeсообщает только о своём: заказ сохранён. Вызывающему коду не нужно разбираться с ошибками почты или аналитики.- Сбои не теряются молча: каждый проходит через
reportи попадает туда, где их увидят.
Итог
За три шага касса прошла путь от "знаю всех и зову сама" до "сохранила заказ и сообщила". Реакции подписываются при сборке приложения, добавляются без правки кассы и падают в одиночку.
Чего этот шаг не решает:
- Повтора нет: упавшая реакция просто записана в отчёт. Для аналитики это нормально, для сборки заказа - нет.
- Диспетчер синхронный: письмо отправляется в том же запросе, и медленный сервис тормозит оформление.
- Если процесс упадёт после сохранения заказа, но до рассылки, событие потеряется. Когда реакция обязана случиться, событие пишут в ту же транзакцию и рассылают отдельно - см. Outbox.
Сам приём разобран в темах Доменное событие и Наблюдатель.
Запуск: make test T=lessons/003-events-instead-of-calls/step-3