Skip to Content
PatternsОбзор

Patterns

Архитектурные паттерны, которые применяются в наших сервисах. Каждый — deep-dive: не только «как», но и «почему», «какие trade-off’ы», «когда не использовать».

Reference-правила про Watermill, Kafka и HTTP — в ../conventions/. Страницы здесь отвечают на вопрос «какой архитектурный приём применить для этой задачи».

Страницы

  • outboxTransactional Outbox. Публикация в Kafka идёт в две стадии: запись в таблицу outbox внутри бизнес- транзакции + асинхронный forwarder. Убирает dual-write.
  • sagaSaga. Координированная запись в N сервисов с компенсациями. Orchestration-style поверх outbox + Kafka, state в saga_instances.
  • cqrsCQRS через watermill/components/cqrs. Типизированные Command/Event handler’ы вместо switch по Event-Type. Lite-вариант, без отдельных read-моделей.
  • idempotent-consumerIdempotent Consumer. Защита от at-least-once дублей Kafka: middleware.Deduplicator поверх Redis + идемпотентность на БД-уровне.
  • api-compositionAPI Composition. Склейка данных из нескольких сервисов для list-view; без shared database и cross-service JOIN. Timeouts, partial failure, N+1.
  • retry-and-circuit-breakerRetry + Circuit Breaker. Как повторять запросы к downstream и когда «размыкать цепь», чтобы не добивать уже лежащий сервис. Exponential backoff + jitter, идемпотентность как условие, fallback стратегии.

Когда использовать какой

ЗадачаПаттерн
Публикация события в Kafka из бизнес-операцииoutbox
Consumer принимает событие и меняет БДidempotent-consumer (вместе с outbox на publisher-стороне)
Координированная запись в N сервисов с откатомsaga
В сервисе > 3 команд/событий на одну доменную модельcqrs
Нужно отдать UI-клиенту данные из 2+ сервисов одним ответомapi-composition
Внешний сервис / БД / Redis периодически падает, нужна защита от transient-ошибок и каскадного паденияretry-and-circuit-breaker

Как паттерны связаны между собой

Outbox и idempotent consumer — инфраструктурные паттерны, поверх которых работают saga и CQRS. Любая асинхронная коммуникация между сервисами проходит через outbox на publisher-стороне и Deduplicator на consumer-стороне. Saga использует те же каналы для команд и событий шагов; CQRS-проекции подписываются на те же topic’и.

Outbox отвечает за «запись в БД и публикация события — атомарны»; idempotent consumer — за «повторная доставка не вызывает повторного эффекта». Saga поверх этих двух паттернов строит координацию: команды шагам она шлёт через outbox (то есть в той же транзакции, что и запись в saga_instances), ответы шагов читает через обычный consumer со стандартным Deduplicator’ом. CQRS-проекция поверх них же делает read- модель в своей БД.

Decision tree: какой паттерн применить

  • Нужно опубликовать событие в том же tx, что бизнес-операция → outbox.
  • Consumer может получить сообщение дважды (at-least-once от Kafka) → idempotent-consumer.
  • Нужна read-модель с денормализацией по событиям из нескольких сервисов → cqrs (через projection).
  • Нужно собрать данные из N сервисов для одного API-ответа (read-only composition) → api-composition.
  • Нужна координированная запись в N сервисов с откатом (мутации с компенсациями) → saga.
  • Нестабильный downstream (HTTP к чужому сервису / Redis / внешний API) — нужна защита от transient-ошибок и каскадного падения → retry-and-circuit-breaker.

Если задача не попадает ни в один пункт — скорее всего, паттерн здесь не нужен. Не подключай CQRS «на вырост», не заводи outbox без Kafka-события, не оборачивай всё в сагу, если на самом деле изменение локально в одном сервисе.

Last updated on