Распределённые системы
Микросервисы, событийная архитектура, CQRS
Как только части системы работают в разных процессах и говорят по сети, вызов перестаёт быть надёжным: сообщение может потеряться, прийти поздно или прийти дважды, а у каждой части свои данные, которые какое-то время расходятся с соседскими.
Все три паттерна раздела показаны на той же регистрации по email, и в каждом есть свой ответ на эти беды.
- Тайм-аут - не отказ: сервис, который не ответил вовремя, мог уже сделать работу. Поэтому повтор идёт с ключом идемпотентности, и работа не выполняется дважды.
- Доставка «хотя бы один раз»: брокер может доставить событие повторно, поэтому получатель запоминает, что уже обработал.
- Микросервисы - когда части системы нужно развёртывать и масштабировать независимо, у каждой своя команда и свои данные. Цена - сеть между всеми.
- Событийная архитектура - когда на одно действие реагируют несколько независимых частей, и отправителю не нужен их результат, чтобы ответить пользователю.
- CQRS - для отдельной части системы, где запись охраняет сложные правила, а чтение нужно в нескольких формах. По Фаулеру, для большинства систем это рискованная сложность.
Паттерны сочетаются: микросервисы могут общаться событиями, а модель чтения CQRS обновляется теми же событиями.
Когда данные живут в нескольких местах, они какое-то время расходятся: аккаунт уже есть, а письма ещё нет, команда принята, а список её ещё не показывает. Это итоговая согласованность (eventual consistency), и интерфейс должен её пережить.
- Прочитать свою запись (read-your-writes): пользователь ждёт увидеть своё действие сразу. В примере CQRS страница ждёт, пока модель чтения догонит версию из ответа команды.
- Outbox: запись в базу и отправка события - две системы. Событие пишут в ту же базу вместе с данными, а отдельный процесс потом отправляет его в брокер.
- Идемпотентный получатель: повторная доставка того же события не меняет результат - получатель помнит
idили версию обработанных событий.
- По способу общения: запрос с ответом (микросервисы, команды в CQRS) или событие без ответа (событийная архитектура, обновление модели чтения).
- По тому, что разделено: процессы и данные по бизнес-возможностям (микросервисы), отправитель и получатели реакции (событийная), модели записи и чтения (CQRS).
- По масштабу: микросервисы и событийная архитектура описывают устройство системы, CQRS - приём для одного её участка.
Микросервисная архитектура
Микросервисная архитектура (Microservices Architecture) строит одно приложение как набор небольших сервисов: каждый работает в своём процессе, развёртывается независимо от остальных и сам владеет своими данными. Систему режут по бизнес-возможностям - учётные записи, письма, оплата, - а не по техническим слоям, и сервисы общаются только по сети через API. Общей базы данных нет: чужие данные можно получить, только спросив их владельца. За независимость платят распределённостью: любой вызов может опоздать или потеряться, а согласованность приходится держать без общей транзакции.
Событийная архитектура
Событийная архитектура (Event-Driven Architecture) связывает части системы через события: компонент сообщает, что уже произошло, например «пользователь зарегистрирован», и не ждёт ответа. Событие уходит в брокер, а брокер доставляет копию каждому, кто на него подписан. Отправитель не знает, сколько у него подписчиков и что они сделают, поэтому новую реакцию можно добавить, не трогая его код. За эту свободу платят: событие может прийти дважды или с опозданием, данные у частей системы какое-то время расходятся, а путь одного действия через систему труднее проследить.
CQRS
CQRS (Command Query Responsibility Segregation) разводит запись и чтение по двум разным моделям. Модель записи принимает команды, проверяет правила и хранит данные так, чтобы их было удобно защищать. Модель чтения хранит те же данные уже в том виде, в каком их показывает конкретный экран, и отвечает на запросы, ничего не меняя. Между ними ходят события, поэтому модель чтения отстаёт от записи на какое-то время. Это приём для отдельной части системы, где чтение и запись действительно тянут модель в разные стороны, а не архитектура всего приложения.