Кладовочка

О разном, чтобы было под рукой онлайн

Справочник архитектурных паттернов ИТ-систем

Вы абсолютно правы. Моя доработка выкинула важные элементы: таблицы "Проблема → Решение" для монолита, диаграммы Mermaid и таксономию из 80+ паттернов. Сейчас я восстановлю полную структуру, сохранив новый формат (описание + когда применять + когда не применять), но вернув:

  1. Все Mermaid-диаграммы.
  2. Таблицы "Проблема → Решение" там, где они были.
  3. Полную таксономию (все разделы с 4 по 23).

Ниже — полностью восстановленный и доработанный справочник.


(объединённая таксономия с примерами и Mermaid-диаграммами)


Введение

Архитектурные паттерны — проверенные решения типовых проблем проектирования. Документ объединяет информацию из :

  • Конспектов по System Design,
  • Материалов VK Cloud и microservices.io (Крис Ричардсон) — классические паттерны микросервисов,
  • Книга Брендана Бернса «Паттерны проектирования распределённых систем» — одноузловые, обслуживающие, пакетные и координационные паттерны,
  • Современные AI‑архитектуры (Agentic Loop, RAG).

Все диаграммы выполнены в Mermaid.


1. Фундаментальные архитектурные подходы

1.1 Монолитная архитектура

Описание: Монолит — приложение, которое собирается, тестируется и развёртывается как единый артефакт (JAR, WAR, бинарник). Все компоненты — UI, бизнес-логика, работа с БД — выполняются в одном процессе. Масштабирование: вертикальное (scale up) или запуск нескольких копий за балансировщиком.

Ключевая идея: Простота на старте важнее гибкости в будущем. Монолит позволяет быстро итерировать фичи одной командой.

Когда применять:

  • Стартап или MVP (первые 6–12 месяцев).
  • Команда до 10 разработчиков.
  • Требования к uptime не выше 99.9%.
  • Бизнес-логика не имеет чётких естественных границ.

Когда не применять:

  • Команда > 20 человек.
  • Разные модули требуют разного масштабирования.
  • Нужно zero-downtime обновление отдельных компонентов.

Проблемы и решения монолита:

Проблема Решение
Разрастание кода, потеря управляемости Модульный монолит (логические границы через DDD)
Конфликты мержа между командами Feature-флаги, модули с явными API, contract testing
Горячий модуль требует больше ресурсов Сателлитная архитектура (выделение модуля в отдельный сервис)
Медленные сборки и тесты Инкрементальная сборка, build cache, модульные тесты
Невозможность обновить часть системы без downtime Blue-Green деплой всего монолита

1.2 Модульный монолит

Описание: Эволюция монолита: код разделён на слабо связанные модули по DDD (bounded contexts). Модули общаются через интерфейсы внутри процесса. Сохраняется единый деплой и, как правило, общая БД с логическим разделением таблиц. Это «лучший мир»: модули изолированы для разработчиков, но физически в одном процессе.

flowchart TD subgraph "Модульный монолит" M1[Модуль Заказы] M2[Модуль Платежи] M3[Модуль Каталог] end M1 -- интерфейс --> M2 M2 -- интерфейс --> M3 M3 -- интерфейс --> M1 DB[(Общая БД)] M1 --> DB M2 --> DB M3 --> DB

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

Когда применять:

  • Простой монолит вырос до 100+ файлов и теряет управляемость.
  • Есть чёткие business capabilities, но инфраструктура не готова к микросервисам.
  • Нужны атомарные ACID-транзакции через несколько модулей.
  • Команды готовы соблюдать контракты, но не хотят DevOps на каждый сервис.

Когда не применять:

  • Модулям нужны разные технологии (Python vs Go).
  • Разные модули должны масштабироваться в десятки раз по-разному.
  • Сбой в одном модуле физически валит весь процесс.

1.3 Микросервисная архитектура

Описание: Архитектурный стиль, где приложение строится как набор слабо связанных сервисов, каждый под свою бизнес-способность. Каждый сервис владеет своей БД, развёртывается независимо. Общение через синхронные протоколы (REST/gRPC) или асинхронные сообщения (Kafka). Команды могут использовать разные технологии.

Ключевая идея: Независимая доставка ценности разными командами. Один сервис — одна команда.

Когда применять:

  • Организация выросла до 3+ команд, которые не хотят ждать друг друга.
  • Разные части системы имеют разные требования к масштабированию, времени отклика, надёжности.
  • Есть инвестиции в инфраструктуру: service discovery, distributed tracing, централизованное логирование.
  • Нужна изоляция отказов.

Когда не применять:

  • Меньше 15–20 разработчиков (оверхед превысит пользу).
  • Нет зрелых DevOps и CI/CD.
  • Бизнес-логика укладывается в 3–5 таблиц.
  • Нужна сильная согласованность в реальном времени.

2. Паттерны декомпозиции

Паттерн Суть
Decompose by Business Capability По бизнес-способностям (заказы, биллинг, доставка)
Decompose by Subdomain DDD-субдомены (core, supporting, generic)
Self-Contained Service Минимум синхронных вызовов; самодостаточный сервис
Service per Team Организационная граница = граница сервиса

2.1 Decompose by Business Capability

Описание: Разбиение на основе бизнес-способностей — того, что бизнес делает для получения ценности. Границы подсказывает анализ бизнес-процессов и организационная структура (Conway's Law). Это самый интуитивный способ начать декомпозицию.

Ключевая идея: Структура кода должна зеркалировать структуру бизнеса. Как организованы отделы, так должны быть и сервисы.

Когда применять: Бизнес-способности легко идентифицируются, имеют собственные данные и соответствуют отделам.

Когда не применять: Способности сильно пересекаются по данным или отделы меняются быстрее архитектуры.

2.2 Decompose by Subdomain (DDD)

Описание: Более детальный подход на основе DDD. Выделяются типы субдоменов: core (ключевая компетенция), supporting (поддерживающий), generic (универсальный). Core часто пишут сами, generic — покупают. Границы — bounded contexts с чёткими моделями данных.

Ключевая идея: Защита core домена — самого ценного для бизнеса. Остальное может быть простым или купленным.

Когда применять: Есть чёткое понимание конкурентного преимущества, используется Event Storming.

Когда не применять: Бизнес-логика тривиальна (CRUD) или нет domain expert'а.


3. Одноузловые паттерны (Single‑Node Patterns)

Эти паттерны реализуются на уровне пода/контейнера и добавляют функциональность без изменения кода основного приложения.

3.1 Sidecar

Описание: Вспомогательный контейнер в том же поде, что и основное приложение. Разделяет жизненный цикл, сеть и файловую систему. Добавляет HTTPS, метрики, конфигурацию, circuit breaker — без модификации кода. Приложение не знает о sidecar: читает файлы или слушает localhost.

flowchart LR subgraph Pod App[Основное приложение] Sidecar[Sidecar-контейнер] end App <--> Sidecar Sidecar --> Config[Сервер конфигурации]

Ключевая идея: Инфраструктурные сквозные функции (cross-cutting concerns) выносятся в отдельный процесс, не загрязняя бизнес-логику.

Когда применять:

  • Legacy-приложение не поддерживает HTTPS, метрики или структурированные логи.
  • Нужна динамическая конфигурация без перезапуска.
  • Внедряется service mesh (Istio) — каждый pod получает sidecar-прокси.
  • Приложение не умеет собирать метрики в Prometheus, а переписывать код дорого.

Когда не применять:

  • Код легко изменить (дешевле добавить библиотеку).
  • Жёсткие требования к задержкам (sidecar добавляет микросекунды).
  • Операционная команда не готова управлять дополнительными контейнерами.

3.2 Ambassador (посол)

Описание: Прокси между клиентом и сервисом. Клиент подключается к Ambassador, который направляет запрос к нужному экземпляру шардированного сервиса, добавляет rate limiting, ретраи, circuit breaking, SSL-терминацию или канареечные развёртывания. Клиент не знает о сложности. Ambassador часто реализуется как sidecar, но может быть отдельным сервисом (Envoy, NGINX, HAProxy).

flowchart LR Client --> Ambassador Ambassador --> Shard1[Шард 1] Ambassador --> Shard2[Шард 2] Ambassador --> External[Внешний сервис]

Ключевая идея: Скрыть сложность маршрутизации, балансировки и экспериментов от клиентского кода.

Когда применять:

  • Шардированный сервис (Redis Cluster) — не хотим, чтобы каждый клиент знал алгоритм хеширования.
  • Канареечные развёртывания — 10% трафика на новую версию, без изменения кода клиентов.
  • Клиент — legacy-код, а нужен rate limiting.
  • Быстрая SSL-терминация для группы сервисов.

Когда не применять:

  • Клиенты уже реализовали сложную логику ретраев и выбора шарда (переход дороже).
  • Каждый запрос критичен к задержкам (дополнительный hop).

3.3 Adapter (стандартизирующий адаптер)

Описание: Приводит разнородные интерфейсы приложения к единому стандарту для внешних систем — мониторинга, логирования, health checks. Например, приложение пишет логи в свободном формате в stdout, а Adapter-контейнер (fluentd, vector) преобразует их в структурированный JSON для ELK. Или health check приложения — нестандартный эндпоинт, Adapter превращает его в /health для Kubernetes.

flowchart TD subgraph Pod App[Приложение со своим форматом логов] Adapter["Adapter-контейнер (fluentd)"] end App -- stdout/файл --> Adapter Adapter -- структурированный JSON --> LogAgg[Система агрегации логов]

Ключевая идея: Не меняйте legacy-приложение — адаптируйте его вывод к современным стандартам снаружи.

Когда применять:

  • Приложение пишет логи в устаревшем формате (не JSON), а вы внедряете ELK/Loki.
  • Метрики приложения не совместимы с Prometheus.
  • Health-эндпоинт приложения не соответствует стандартам Kubernetes (не /health или не возвращает правильные коды).
  • Вы не можете или не хотите переписывать код.

Когда не применять:

  • Вы контролируете код и можете добавить поддержку стандартов напрямую (дешевле).
  • Требуется low-latency: адаптация на уровне контейнера добавляет накладные расходы.

4. Паттерны обслуживающих систем (Serving Patterns)

Формализованные схемы построения сервисов, которые принимают запросы от пользователей.

4.1 Replicated Load‑Balanced Service

Описание: Сервис без состояния (stateless), запущенный в нескольких идентичных репликах. Перед ними — балансировщик нагрузки (L4/L7), который распределяет запросы. Каждая реплика имеет health check endpoint (/health/ready), и балансировщик не направляет трафик на неподготовленные реплики. Не хранит состояние сессии — любая реплика может обработать любой запрос. Если нужно sticky session — используется IP hash или cookie.

flowchart LR LB[Балансировщик] LB --> R1[Реплика 1] LB --> R2[Реплика 2] LB --> R3[Реплика 3] R1 -- ready? --> HC[Health Check] R2 -- ready? --> HC R3 -- ready? --> HC HC --> LB

Ключевая идея: Простейший способ горизонтального масштабирования stateless сервисов.

Когда применять:

  • Сервис не хранит состояние (или хранит в отдельной БД/кэше).
  • Трафик растёт, и нужно просто добавить больше экземпляров.
  • Нужна высокая доступность: отказ одной реплики не влияет на общую работу.
  • Балансировщик уже есть (K8s Service, NGINX, ALB).

Когда не применять:

  • Сервис stateful (хранит данные локально).
  • Требуется сложная маршрутизация (шардирование, гео-маршрутизация).
  • Клиентам нужны sticky session, а балансировщик не поддерживает cookie-based affinity.

4.2 Sharded Service

Описание: Каждый экземпляр сервиса отвечает только за свою часть ключей (шард). Клиент или Ambassador вычисляет хеш от ключа запроса (например, hash(user_id) % N) и направляет запрос в соответствующий шард. Шарды не пересекаются по данным. Добавление нового шарда требует перераспределения ключей (rebalancing). Применяется в распределённых кэшах (memcached, Redis Cluster) и базах данных с шардированием на уровне приложения.

flowchart LR Client --> SF["Шардирующая функция (hash)"] SF --> S1[Сервис-шард 0-99] SF --> S2[Сервис-шард 100-199] SF --> S3[Сервис-шард 200-...]

Ключевая идея: Масштабирование через разделение данных по горизонтали, когда одна реплика не может вместить все данные.

Когда применять:

  • Объём данных не помещается на одной машине (TB+).
  • Запросы к данным можно предсказуемо направить по ключу (user_id, product_id).
  • Вы готовы к сложности перешардирования при добавлении узлов.
  • Кэш или БД не поддерживает автоматическое шардирование (или вы хотите контролировать его сами).

Когда не применять:

  • Объём данных мал (влезает на одну машину).
  • Нет естественного ключа шардирования (запросы идут по разным атрибутам).
  • Вам нужны сложные JOIN-запросы между шардами (очень больно).

4.3 Scatter/Gather

Описание: Клиентский запрос параллельно отправляется нескольким репликам или шардам. Каждый узел обрабатывает свою часть (поиск в своём индексе, агрегация своих данных). Затем агрегатор собирает результаты, возможно, сортирует, обрезает и возвращает единый ответ. Требуется, чтобы каждый узел умел возвращать частичный результат. Классический пример: поисковые системы (Elasticsearch) и распределённые MapReduce.

sequenceDiagram participant C as Клиент participant A as Агрегатор (Scatter/Gather) participant S1 as Шард 1 participant S2 as Шард 2 participant S3 as Шард 3 C->>A: Поисковый запрос A->>S1: Поиск в своём индексе A->>S2: Поиск в своём индексе A->>S3: Поиск в своём индексе S1-->>A: результаты S2-->>A: результаты S3-->>A: результаты A-->>C: объединённый ответ

Ключевая идея: Параллельная обработка для снижения latency при работе с большим количеством данных.

Когда применять:

  • Поисковые системы (Elasticsearch, Solr) — запрос идёт ко всем шардам индекса.
  • Распределённые вычисления, где задача естественно делится на независимые подзадачи.
  • Системы рекомендаций, где каждый узел возвращает кандидатов, а агрегатор ранжирует.

Когда не применять:

  • Запрос требует атомарной транзакции (scatter/gather не гарантирует консистентность).
  • Количество шардов очень велико (сотни) — накладные расходы на параллельные вызовы высоки.
  • Один из шардов может отвечать очень долго (медленный узел тормозит весь запрос).

4.4 Кэширующая прослойка (Cache Proxy)

Описание: Выделенный прокси-сервер (или библиотека) перед сервисом, который реализует стратегии кэширования: Read-Through, Write-Through, Write-Back, Cache-Aside. При Read-Through прокси при промахе сам идёт в бэкенд, заполняет кэш и возвращает данные. При Write-Through — запись одновременно идёт в кэш и в бэкенд. Прокси скрывает логику кэширования от клиента.

flowchart LR App --> CacheProxy CacheProxy -- промах --> BackendSvc CacheProxy --> Cache[(Кэш)]

Ключевая идея: Улучшение производительности чтения без изменения клиентского кода.

Когда применять:

  • Бэкенд-сервис медленный (БД, внешнее API), а данные имеют высокую частоту чтения и низкую частоту записи.
  • Клиентов много, и вы не хотите, чтобы каждый реализовывал Cache-Aside.
  • Нужна единая политика кэширования (TTL, eviction) для всех клиентов.
  • Вы внедряете CDN-подобное поведение на уровне сервиса.

Когда не применять:

  • Данные обновляются очень часто — кэш будет постоянно инвалидироваться.
  • Требуется сильная консистенция (read-your-writes).
  • Бэкенд уже достаточно быстр (latency < 10 мс).

5. Паттерны координации и выбора владельца (Leader Election)

Когда только один экземпляр сервиса должен выполнять фоновую работу, координировать шаги саги или удерживать блокировку.

5.1 Leader Election (выбор лидера)

Описание: Механизм, при котором из группы идентичных узлов выбирается один лидер, а остальные становятся последователями (followers). Лидер выполняет эксклюзивную работу (например, координацию саги, обработку очереди заданий). При отказе лидера фолловеры запускают новый раунд выборов. Реализуется через распределённые консенсус-алгоритмы (Raft в etcd, ZooKeeper atomic ephemeral nodes) или через аренды (lease) в Consul/etcd.

stateDiagram-v2 Candidate --> Follower: теряет выборы Follower --> Candidate: таймаут без лидера Candidate --> Leader: получает большинство голосов Leader --> Follower: отказ

Ключевая идея: Гарантировать, что критический участок кода выполняется только одним узлом в распределённой системе.

Когда применять:

  • Фоновая задача должна выполняться только одним экземпляром (чистка старых данных, агрегация метрик).
  • Координация распределённой саги (orchestrator должен быть один).
  • Управление заданием из очереди без дублирования (leader получает задания, распределяет между workers).
  • Обновление shared-ресурса, который не поддерживает конкурентную запись.

Когда не применять:

  • Есть более простое решение — распределённая блокировка на короткое время.
  • Все узлы могут делать работу независимо (без состояния).
  • Вы не готовы к сложности координации (блокировки, таймауты, split-brain).

5.2 Блокировки (Locks) и аренда (Leases)

Описание: Распределённая блокировка гарантирует, что только один узел входит в критическую секцию (аналог mutex в одной машине, но на кластере). Аренда (lease) — это блокировка с таймаутом: узел получает эксклюзивное право на ресурс на ограниченное время и должен периодически продлевать аренду. При падении узла аренда истекает, и ресурс освобождается. Реализации: Redis SETNX с TTL, etcd lock, ZooKeeper ephemeral узлы.

Ключевая идея: Эксклюзивный доступ к ресурсу в распределённой системе с автоматическим освобождением при отказе.

Когда применять:

  • Обработка задания из очереди exactly-once (заблокировал задание → обработал → удалил).
  • Выделение уникальных идентификаторов или номеров (инкремент под блокировкой).
  • Обновление общей конфигурации (не более одного писателя).
  • Координация миграций схемы БД на кластере.

Когда не применять:

  • Высокочастотные операции (блокировка добавляет RPC-вызов → латентность).
  • Можно использовать оптимистичную блокировку (версионирование в БД) — дешевле.
  • Ресурс уже поддерживает идемпотентность — блокировка избыточна.

6. Паттерны коммуникации (межсервисное взаимодействие)

6.1 Внешнее API: Gateway и BFF

Описание: API Gateway — единая точка входа для всех клиентов. Выполняет маршрутизацию запросов к внутренним сервисам, агрегацию ответов от нескольких сервисов, аутентификацию, rate limiting, логирование, CORS. Backend for Frontend (BFF) — специализированный gateway для каждого типа клиента (Web, Mobile, IoT). BFF может возвращать данные в формате, оптимальном для конкретного клиента (например, для мобильного приложения — сжатые, с пагинацией, без лишних полей).

flowchart LR Web["Web-клиент"] --> BFF_Web["BFF (Web)"] Mobile["Mobile-клиент"] --> BFF_Mob["BFF (Mobile)"] BFF_Web --> GW["API Gateway"] BFF_Mob --> GW GW --> SvcA["Сервис Заказов"] GW --> SvcB["Сервис Платежей"] GW --> SvcC["Сервис Каталога"]

Ключевая идея: Отделить внешний API от внутренней архитектуры, защитить бэкенды и оптимизировать под клиента.

Когда применять (API Gateway):

  • У вас несколько бэкенд-сервисов и множество клиентов.
  • Нужна централизованная аутентификация и SSL-терминация.
  • Хотите агрегировать вызовы к нескольким сервисам в один ответ.
  • Требуется rate limiting и защита от DDoS.

Когда применять (BFF):

  • Web и Mobile клиенты имеют разные потребности: мобильному нужны сжатые данные и экономия трафика, Web — полные данные.
  • Один клиент требует синхронных вызовов, другой асинхронных (WebSocket).
  • Команды, разрабатывающие разные клиенты, хотят независимо менять свои BFF.

Когда не применять:

  • Всего 1–2 внутренних сервиса и 1 тип клиента (gateway — лишняя сложность).
  • Вы можете использовать готовый API gateway от облака (ALB, CloudFront) без своего кода.

6.2 Стили взаимодействия

Стиль Протокол Связанность Типичное применение
Синхронный RPC REST, gRPC, Thrift Высокая (клиент ждёт ответ) CRUD, запрос-ответ, команды
Асинхронный Messaging Kafka, RabbitMQ, NATS Слабая (клиент не ждёт) Фоновая обработка, интеграции
Event-Driven Kafka, NATS, Pulsar Очень слабая (издатель не знает подписчиков) Событийные системы, CDC

Описание: Синхронный RPC прост для понимания и отладки, но создаёт временную связанность — если сервис B упал, сервис A не может ответить. Асинхронный messaging разрывает связь: отправитель кладёт сообщение в очередь и продолжает работу. Event-driven — это частный случай messaging, где сервисы публикуют события (факты о том, что произошло), а подписчики реагируют независимо. Никто не знает о подписчиках заранее.

Ключевая идея: Выбор стиля — это компромисс между простотой (синхронность) и отказоустойчивостью (асинхронность).

Когда применять синхронный RPC:

  • Запрос требует немедленного ответа (UI, API).
  • Операция имеет низкую latency (< 100 мс).
  • Вы контролируете обе стороны и готовы к версионированию.

Когда применять асинхронный messaging:

  • Операция может быть выполнена в фоне (отправка email, генерация отчёта).
  • Нужно выдержать пики нагрузки (очередь сглаживает).
  • Потребитель может временно быть недоступен.

Когда применять event-driven:

  • Несколько сервисов должны реагировать на одно и то же событие (заказ создан → биллинг, логистика, аналитика).
  • Нужно слабое зацепление (можно добавить новый подписчик без изменения издателя).
  • Строится CQRS или Event Sourcing.

6.3 Координация: оркестрация vs хореография

Описание: Оркестрация (orchestration) — центральный диспетчер (SagaOrchestrator) управляет последовательностью вызовов сервисов. Знает каждый шаг и компенсацию. Хореография (choreography) — сервисы общаются через события, без центра. Каждый сервис знает только, на какие события реагировать и какие публиковать. Оркестрация проще в отладке, но создаёт центральную точку отказа. Хореография более распределённая, но поток трудно восстановить.

sequenceDiagram participant O as SagaOrchestrator participant S1 as Сервис Заказов participant S2 as Сервис Платежей participant S3 as Сервис Доставки O->>S1: Создать заказ S1-->>O: OK O->>S2: Принять платёж S2-->>O: OK O->>S3: Запланировать доставку S3-->>O: OK
flowchart LR S1["Сервис Заказов"] -- "ЗаказСоздан" --> B["Event Bus"] B -- "ЗаказСоздан" --> S2["Сервис Платежей"] S2 -- "ПлатёжПринят" --> B B -- "ПлатёжПринят" --> S3["Сервис Доставки"]

Ключевая идея: Оркестрация — для предсказуемых, централизованных процессов. Хореография — для динамичных, легко расширяемых систем.

Когда применять оркестрацию:

  • Процесс имеет 3–8 шагов, и логика компенсации сложная.
  • Вы хотите видеть весь процесс в одном месте (легче отлаживать, логировать).
  • Разные шаги принадлежат разным командам, но они не хотят знать о внутренних событиях друг друга.
  • Нужен строгий порядок шагов.

Когда применять хореографию:

  • 2–3 участника (больше — трудно понять поток).
  • Каждый сервис сам знает, как компенсировать свои действия.
  • Вы уже используете event bus для других задач.
  • Команды, владеющие сервисами, предпочитают автономию и не хотят центрального координатора.

7. Паттерны работы с данными

7.1 Database per Service

Описание: Каждый микросервис владеет своей собственной БД. Другие сервисы не имеют прямого доступа к этой БД — только через API сервиса-владельца. БД могут быть разными: один сервис использует PostgreSQL, другой — MongoDB, третий — Redis. Это обеспечивает слабую связанность: изменение схемы БД одного сервиса не ломает другие. Однако усложняются распределённые транзакции и запросы, требующие данных из нескольких сервисов.

flowchart TD subgraph "Сервис Заказов" S1((Svc)) --> DB1[(OrderDB)] end subgraph "Сервис Платежей" S2((Svc)) --> DB2[(PaymentDB)] end subgraph "Сервис Каталога" S3((Svc)) --> DB3[(CatalogDB)] end S1 -- REST/Event --> S2 S1 --> S3

Ключевая идея: Физическая граница данных соответствует границе сервиса. Никто не лезет в чужую БД.

Когда применять:

  • Вы переходите на микросервисную архитектуру.
  • Разные сервисы имеют разные требования к БД (OLTP vs аналитика, SQL vs NoSQL).
  • Команды хотят независимо развёртывать изменения схемы БД.
  • Нужна изоляция отказов (падение БД одного сервиса не ломает другие).

Когда не применять:

  • Данные критически требуют ACID-транзакций между сервисами.
  • У вас модульный монолит — общая БД проще.
  • Часто нужны JOIN-запросы между данными разных сервисов (придётся делать API composition).

7.2 CQRS

Описание: Command Query Responsibility Segregation — разделение модели записи (command) и модели чтения (query). Команды изменяют состояние через нормализованное хранилище (обычно ACID-база). Запросы читают из денормализованных (часто read-optimized) хранилищ, которые обновляются асинхронно через события. Позволяет масштабировать чтение и запись независимо, использовать разные модели для каждой стороны (например, для запросов — агрегаты и проекции). Усложняет систему: теперь есть eventual consistency.

flowchart LR W[Write API] --> WriteDB[(Normalized DB)] WriteDB -- репликация/события --> ReadDB[(Denormalized DB)] R[Read API] --> ReadDB

Ключевая идея: Оптимизация для сценариев, где чтение и запись имеют кардинально разные требования.

Когда применять:

  • Нагрузка на чтение на порядки выше, чем на запись (социальные сети, каталоги).
  • Модели для чтения и записи сильно отличаются (запись требует нормализации, чтение — агрегатов).
  • Нужно использовать разные технологии для чтения (Elasticsearch) и записи (PostgreSQL).
  • Команда хочет внедрить Event Sourcing (CQRS хорошо сочетается).

Когда не применять:

  • Традиционное CRUD приложение без высокой нагрузки.
  • Strong consistency критична (user должен видеть свои изменения мгновенно).
  • Команда не готова к eventual consistency и усложнению.

7.3 Event Sourcing

Описание: Вместо хранения текущего состояния хранится неизменяемая последовательность событий (event log). Каждое событие — это факт, который произошёл в системе (UserCreated, MoneyDeposited, MoneyWithdrawn). Текущее состояние вычисляется как проекция (projection) всей цепочки событий. Плюсы: полный аудит, возможность пересчитать состояние на любой момент времени, легкость введения новых проекций. Минусы: сложность с обновлением событий (их нельзя изменить), большой объём хранимых данных, eventual consistency.

Событие Данные
UserCreated {id:1, name:"Маша"}
MoneyDeposited {userId:1, amount:1000}
MoneyWithdrawn {userId:1, amount:500}

Текущий баланс = проекция событий (1000 - 500 = 500).

Ключевая идея: Время — это первоклассная сущность. Каждое изменение сохраняется навсегда.

Когда применять:

  • Требуется полный аудит всех изменений (банки, логги, системы учёта).
  • Нужно уметь «отматывать» состояние на любой момент времени (debug, аналитика).
  • Вы уже используете CQRS (они хорошо сочетаются).
  • События — естественная модель для предметной области (кейсы: корзина e-commerce, билетная система).

Когда не применять:

  • Маленький проект без требований к аудиту (обычная БД проще).
  • Требуется сильная консистенция и минимальная задержка чтения.
  • Часто нужно обновлять существующие события (например, исправлять ошибки в уже сохранённых).

7.4 Транзакционные паттерны

Паттерн Суть
Transactional Outbox В одной транзакции: запись в бизнес-таблицу + событие в outbox-таблицу
Transaction Log Tailing (CDC) Debezium/Kafka Connect читает лог БД, публикует события
Polling Publisher Периодический опрос outbox-таблицы (проще, но больше latency)
Saga Цепочка локальных транзакций с компенсирующими действиями

Описание: Transactional Outbox гарантирует, что событие будет опубликовано ровно один раз (at-least-once) атомарно с бизнес-изменением. Без этого паттерна вы рискуете: записали в БД, а отправка события провалилась → данные в БД есть, но другие сервисы не узнали. Outbox-таблица хранит события, а отдельный publisher (Polling или CDC) отправляет их в брокер.

Ключевая идея: Обеспечить надёжную публикацию событий без распределённых транзакций (2PC).

Когда применять (Transactional Outbox + CDC):

  • Вы используете событийную архитектуру и не можете допустить потерю события.
  • База данных умеет публиковать WAL (PostgreSQL logical replication, Debezium).
  • Нужна низкая задержка публикации (CDC → Kafka с задержкой миллисекунды).

Когда применять (Polling Publisher):

  • CDC слишком сложен для вашей инфраструктуры.
  • Задержка в несколько секунд допустима.
  • Вы используете БД без хорошей поддержки WAL (старые версии).

7.5 Репликация и шардирование

Типы репликации:

  • Master-Slave (primary-replica): запись только в master, чтение — с master или slave.
  • Master-Master (multi-primary): запись в оба узла, конфликты разрешаются приложением или БД.
flowchart TD M[(Master DB - запись)] -- репликация --> S1[(Slave 1 - чтение)] M -- репликация --> S2[(Slave 2 - чтение)]

Стратегии шардирования:

  • Range-based (ключи 0-1000 → шард 1, 1001-2000 → шард 2) — просто, но возможен hot spot.
  • Hash-based (hash(key) % N) — равномерно распределяет, но сложно добавлять шарды.
  • Consistent Hashing — минимизирует перемещение данных при добавлении шардов.
  • Directory-based — явная таблица маршрутизации.

Ключевая идея: Репликация — для отказоустойчивости и масштабирования чтения. Шардирование — для масштабирования записи и размера данных.

Когда применять репликацию (Master-Slave):

  • Чтений намного больше, чем записей (10:1 и выше).
  • Нужна высокая доступность: при падении master можно переключиться на slave.
  • Допустима небольшая задержка репликации (асинхронная).

Когда применять шардирование:

  • Данные не помещаются на один сервер (TB+).
  • Запись не масштабируется через репликацию (master bottleneck).
  • Есть естественный ключ шардирования (user_id, product_id).

8. Паттерны обнаружения сервисов

Паттерн Описание
Client-Side Discovery Клиент опрашивает Service Registry (Eureka, Consul) и сам выбирает экземпляр
Server-Side Discovery Балансировщик/gateway находит сервис через registry (K8s Service, AWS ALB)
Service Registry БД живых экземпляров с health checks (Consul, etcd, ZooKeeper)
Self-Registration Сервис сам регистрируется при старте (Eureka client)
3rd Party Registration Kubernetes регистрирует pod'ы через API Server

Описание: В динамических средах (Kubernetes, облака) IP-адреса сервисов меняются постоянно. Service Discovery решает проблему «как клиенту найти адрес сервиса». Client-side discovery: клиент получает список адресов из registry и выбирает (например, ribbon → Eureka). Server-side discovery: клиент идёт на известный load balancer, который делает запрос в registry (K8s Service → Endpoints → Pods). 3rd Party Registration — когда оркестратор сам регистрирует контейнеры (K8s, Nomad).

Ключевая идея: Изоляция клиента от динамической природы инфраструктуры.

Когда применять Client-Side Discovery:

  • Вы используете библиотеки вроде Netflix OSS, Consul API.
  • Хотите тонкий контроль над балансировкой (retry, circuit breaker на стороне клиента).
  • Сеть плоская и позволяет клиенту напрямую обращаться к сервисам.

Когда применять Server-Side Discovery:

  • Вы используете Kubernetes (Service → ClusterIP/NodePort/LoadBalancer).
  • Не хотите завязываться на конкретную библиотеку discovery в коде.
  • Балансировщик уже есть и может выполнять health checks.

9. Паттерны отказоустойчивости

stateDiagram-v2 [*] --> Closed Closed --> Open: ошибок > порог Open --> HalfOpen: таймаут ожидания HalfOpen --> Closed: успешный пробный запрос HalfOpen --> Open: ошибка
Паттерн Назначение
Circuit Breaker Прерывание вызовов к аварийному сервису (состояния Closed → Open → Half-Open)
Retry Повтор при временных ошибках (с экспоненциальной задержкой)
Timeouts Ограничение ожидания ответа (не ждать вечно)
Bulkhead Изоляция ресурсов (пулы потоков, отдельные соединения)
Rate Limiting Token Bucket, Leaky Bucket — защита от перегрузки
Health Check /health эндпоинт для оркестратора (liveness + readiness)
Graceful Degradation Отдаём «урезанный» ответ вместо падения (например, кэш вместо БД)
Backpressure Потребитель замедляет производителя (Reactive Streams, TCP window)

Описание: Circuit Breaker оборачивает вызовы к удалённому сервису и отслеживает ошибки. При превышении порога ошибок переходит в состояние Open, и все вызовы немедленно возвращают ошибку (без реального вызова). Через таймаут переходит в Half-Open, пропускает пробный запрос. Если успешен — закрывается. Защищает систему от cascading failures.

Ключевая идея: Отказ одного сервиса не должен валить всю систему. Нужно быстро «отключать» проблемных зависимостей.

Когда применять Circuit Breaker:

  • Вызовы к внешнему API или другому микросервису могут надолго зависать или падать с ошибками.
  • Вы хотите избежать cascading failure (эффекта домино).
  • У вас есть разумный таймаут для восстановления.

Когда применять Retry:

  • Ошибки временные (network timeout, 503, ConnectionRefused).
  • Запрос идемпотентен (повтор безопасен).
  • Вы реализуете exponential backoff, чтобы не усугублять ситуацию.

Когда применять Bulkhead:

  • Разные типы запросов к сервису (быстрые и медленные) не должны забирать общие ресурсы.
  • Например, вызов /health не должен ждать очереди из медленных /report.
  • Выделяем отдельные пулы потоков для критичных путей.

10. Паттерны кэширования

Стратегия Чтение Запись Описание
Cache-Aside Приложение проверяет кэш, при промахе — загружает в БД и пишет в кэш Приложение само обновляет кэш (инвалидация или прямой write) Самая популярная, но требует кода в приложении
Read-Through Кэш сам ходит в БД при промахе (прозрачно для приложения) – Прокси скрывает логику
Write-Through – Параллельно в кэш и БД (синхронно) Гарантирует консистентность, но увеличивает latency записи
Write-Back (Write-Behind) – Сначала в кэш, потом асинхронно в БД Очень быстрая запись, но риск потери данных при падении кэша
Refresh-Ahead Предзагрузка данных до истечения TTL – Хорошо для предсказуемых паттернов доступа

Инвалидация: TTL (время жизни), LRU/LFU (вытеснение по давности/частоте), ручная инвалидация по событию (CDC).

Ключевая идея: Кэш уменьшает latency и нагрузку на бэкенд, но добавляет сложность с консистентностью.

Когда применять Cache-Aside:

  • Приложение уже написано, легко добавить проверку кэша.
  • Вы контролируете как чтение, так и запись.
  • Допустима eventual consistency.

Когда применять Read-Through/Write-Through:

  • Не хотите, чтобы приложение знало о кэше.
  • У вас уже есть прокси (например, Redis как cache proxy).
  • Требуется строгая консистенция кэша и БД.

Когда применять Write-Back:

  • Высокая интенсивность записи, а потеря нескольких секунд данных допустима (логи, счётчики).
  • Бэкенд очень медленный или дорогой.

11. Паттерны балансировки нагрузки

Алгоритм Когда применять
Round Robin Одинаковые серверы, примерно одинаковое время обработки запросов
Least Connections Запросы разной длительности (одни задерживаются дольше других)
IP Hash (или Consistent Hash) Нужны sticky sessions (сохранение сессии на одном сервере)
Weighted Round Robin Серверы разной мощности (10% мощностей у одних, 90% — у других)
Least Response Time Выбирается сервер с наименьшим средним временем ответа (с учётом pending запросов)

Описание: Балансировщик распределяет входящий трафик между несколькими бэкендами. L4 балансировка работает на уровне TCP/UDP (IP + порт), быстрая, но не видит содержимого запроса. L7 (HTTP/gRPC) — видит заголовки, URI, может маршрутизировать по ним. Алгоритм выбирается исходя из природы нагрузки и требований к сессиям.

Ключевая идея: Спрятать множество бэкендов за одним IP/доменом и равномерно распределить нагрузку.

Когда применять Round Robin:

  • Бэкенды идентичны по мощности и времени отклика.
  • Нет требований к sticky sessions.

Когда применять Least Connections:

  • Есть запросы, которые обрабатываются долго (например, загрузка файлов, streaming).
  • Иначе Round Robin накопит длинную очередь на одном сервере.

Когда применять IP Hash:

  • Сервис хранит сессию в памяти (in-memory session, локальный кэш).
  • Клиент должен всегда попадать на один и тот же бэкенд.

12. Паттерны масштабирования и инфраструктуры

12.1 Масштабирование

  • Вертикальное (scale up) — увеличение ресурсов одной машины (CPU, RAM). Предел: ~до 1000 RPS на одной машине. Просто, но имеет потолок.
  • Горизонтальное (scale out) — добавление новых машин/контейнеров. Требует балансировки, репликации, шардирования. Не имеет теоретического предела.

Ключевая идея: Вертикальное — для первых лет проекта. Горизонтальное — когда упёрлись в потолок железа.

12.2 Инфраструктурные паттерны

Паттерн Суть
Microservice Chassis Каркас (framework) с logging, health checks, config, метриками "из коробки"
Externalized Configuration Конфигурация вне кода: Consul, Vault, ConfigMap, environment variables
Service Template Шаблон кода для новых сервисов (сборка, деплой, observability)
Service Mesh Istio, Linkerd – слой для трафика, безопасности, observability (обычно через sidecar)
Sidecar Вспомогательный контейнер для cross-cutting функций
Ambassador Прокси‑посредник для шардирования, экспериментов, ограничения частоты
Adapter Стандартизирующий адаптер для логов, метрик, health‑check’ов

Описание Service Mesh: Выделяет сетевые функции (retry, circuit breaker, mTLS, трассировка, метрики) в отдельный инфраструктурный слой. Каждый сервис получает sidecar-прокси (Envoy). Прокси управляют всем трафиком в/из сервиса. Разработчики не пишут код для этих функций.

Ключевая идея: Инфраструктурная сложность не должна загрязнять бизнес-код. Выносим её в отдельный слой.

Когда применять Service Mesh:

  • У вас десятки микросервисов на разных языках.
  • Требуется взаимный TLS (mTLS) между сервисами без изменения кода.
  • Нужен глубокий мониторинг трафика (golden signals: latency, traffic, errors, saturation).
  • Команда готова внедрить Istio/Linkerd и обучить разработчиков.

Когда не применять:

  • 2–5 сервисов — overhead превышает пользу.
  • Нет экспертизы в Kubernetes и sidecar-контейнерах.
  • Разработчики уже реализовали resilience в коде (библиотеками).

13. Паттерны наблюдаемости (Observability)

Паттерн Инструмент Назначение
Log Aggregation ELK (Elasticsearch, Logstash, Kibana), Loki Сбор, индексация и поиск логов
Application Metrics Prometheus + Grafana Сбор числовых метрик (RPS, latency, ошибки)
Distributed Tracing Jaeger, Zipkin, OpenTelemetry Отслеживание одного запроса через несколько сервисов
Audit Logging Отдельное хранилище (S3, ClickHouse) Кто, когда, что делал (безопасность, compliance)
Exception Tracking Sentry, Rollbar Агрегация и алертинг на ошибки
Health Check API K8s probes (liveness, readiness, startup) Автоматическое восстановление и маршрутизация трафика

Описание: Наблюдаемость — это способность понять внутреннее состояние системы по её внешним выходам (логи, метрики, трейсы). Без наблюдаемости микросервисы превращаются в «чёрный ящик». Ключевые столпы: logs (структурированные события), metrics (числовые временные ряды), traces (цепочки вызовов). OpenTelemetry пытается унифицировать сбор всех трёх типов данных.

Ключевая идея: Вы не можете управлять тем, что не можете измерить. Наблюдаемость — предварительное условие для микросервисов.

Когда применять:

  • У вас больше одного сервиса (нужны трассировка и агрегация логов).
  • Вы переходим на микросервисы (обязательно внедрить до начала миграции).
  • Есть SLO/SLA и нужно измерять их выполнение.

Когда отложить:

  • Монолит с одним экземпляром (достаточно journalctl и top).
  • Проект на старте MVP (добавьте базовые метрики, но не ELK).

14. Паттерны безопасности

Паттерн Назначение
API Gateway Auth Аутентификация и авторизация на gateway (JWT проверка, OAuth)
JWT / OAuth 2.0 Токены доступа, передаваемые в заголовках между сервисами
Access Token (propagation) Проверка токена при каждом межсервисном вызове (или передача микросервисам)
Anti-Corruption Layer (ACL) Прослойка, изолирующая старую и новую модель (при миграции)
Secrets Management HashiCorp Vault, AWS Secrets Manager, K8s Secrets
Zero Trust Проверяется каждый запрос (нет «доверенной сети»), mTLS везде

Описание: В распределённых системах безопасность сложнее, чем в монолите: сервисы общаются по сети (которую могут прослушивать). Zero Trust предполагает, что никому нельзя доверять по умолчанию. Каждый запрос аутентифицируется и авторизуется. JWT — самоодержимый токен, содержит права пользователя и подпись. Service Mesh может автоматически подписывать mTLS между сервисами.

Ключевая идея: Не полагайтесь на сетевые границы как на единственную защиту. Микросервисы — это распределённая система, и каждый вызов должен быть защищён.

Когда применять JWT токены:

  • Нужно передать контекст пользователя (user ID, роли) через несколько сервисов без перепроверки в БД.
  • Gateway проверяет подпись JWT и пробрасывает токен дальше.

Когда применять Anti-Corruption Layer:

  • Вы мигрируете со старой монолитной системы на новую, и модели данных не совпадают.
  • ACL переводит запросы из старого формата в новый и обратно.

15. Паттерны развёртывания и CI/CD

flowchart LR subgraph Blue-Green LB["Load Balancer"] --> Blue["Blue (текущий)"] LB -.-> Green["Green (новый)"] end
flowchart LR LB["Балансировщик"] --> v1["Сервис v1 (90%)"] LB --> v2["Сервис v2 (10%)"]
Паттерн Описание
Blue-Green Два окружения (Blue и Green). Переключение балансировщика с Blue на Green — мгновенный rollback
Canary Release Постепенный перенос трафика: 1% → 10% → 50% → 100% на новую версию
Rolling Update Поочередное обновление pod'ов (K8s native) — 0 downtime, но опасность одновременных версий
Feature Toggle Включение фич без деплоя (флаги в коде, конфигурации)
Dark Launch Новая версия запущена в production, но не получает трафика (только для тестов)
Serverless AWS Lambda, Cloud Functions — код как функция без управления серверами

Описание: Blue-Green даёт мгновенный rollback, но требует вдвое больше ресурсов. Canary безопасен, но медленный и требует мониторинга (A/B тест). Rolling Update — стандарт K8s, экономный, но откат не мгновенный. Feature Toggle — самый гибкий: фича включена для 10% пользователей по email.

Ключевая идея: Развёртывание без простоя — обязательное требование для современных систем. Выберите паттерн, который соответствует вашему уровню риска и инфраструктуре.

Когда применять Blue-Green:

  • Критичный сервис, где каждая минута простоя стоит больших денег.
  • У вас есть лишние ресурсы (удвоенные мощности).
  • Легко переключить балансировщик (например, AWS Route53 weighted records).

Когда применять Canary:

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

16. Паттерны UI (Micro-Frontends)

  • Server-Side Page Fragment Composition — бэкенд собирает HTML из фрагментов (например, Edge Side Includes).
  • Client-Side UI Composition — браузер загружает и собирает компоненты разных команд (Web Components, Module Federation).

Описание: Аналог микросервисов на фронтенде. Каждая команда владеет своей частью UI (например, корзина, каталог, рекомендации). Эти части собираются вместе на сервере или в браузере. Позволяет разным командам независимо развёртывать свои фрагменты. Сложность — общий стиль, роутинг, состояние.

Ключевая идея: Если вы разбили бэкенд на микросервисы, а фронтенд остался монолитом — фронтенд-команда станет узким местом.


17. Паттерны рефакторинга и миграции на микросервисы

17.1 Strangler Fig («Удушающий инжир»)

Описание: Постепенная замена частей монолита на микросервисы. На первом этапе маршрутизатор направляет часть запросов к монолиту, часть — к новому сервису. Со временем монолит «сжимается», пока не останутся одни микросервисы. Аналогия с инжиром, который оплетает дерево-хозяина и в итоге его заменяет.

flowchart TD subgraph "Фаза 1" M1[Монолит] end subgraph "Фаза 2" M2[Монолит] --- MS1[Микросервис A] end subgraph "Фаза 3" MS2[Микросервис A] --- MS3[Микросервис B] --- MS4[Микросервис C] end Фаза1 --> Фаза2 --> Фаза3

Ключевая идея: Мигрируйте постепенно, не переписывая всё сразу. Это снижает риск.

Когда применять:

  • Вы осознали, что монолит больше нельзя развивать.
  • Есть чёткие границы для первого сервиса (например, авторизация).
  • Маршрутизатор (reverse proxy) позволяет направлять трафик по правилам (URI, заголовки).

17.2 Сателлитная архитектура

Описание: Монолит-ядро оставляется, а новые функции или высоконагруженные модули выносятся в отдельные сервисы (сателлиты). Монолит продолжает общаться с сателлитами через API. Это первый шаг к микросервисам без полной перестройки ядра.

flowchart TD Core[Монолит-ядро] --- Sat1[Сервис биллинга] Core --- Sat2[Сервис нотификаций] Core --- Sat3[Аналитика]

Другие паттерны: Anti-Corruption Layer (ACL), Feature Toggle (для включения нового кода параллельно со старым).


18. Паттерны функций и событийной обработки (FaaS + Event-Driven)

18.1 Паттерн Decorator (функция‑декоратор)

Описание: Бессерверная функция (AWS Lambda, Cloud Function), которая прозрачно преобразует входящий запрос или исходящий ответ. Добавляет заголовки, проверяет права, добавляет значения по умолчанию, делает логгирование, валидацию. Вызывается перед основным сервисом и после него. Напоминает декоратор из ООП, но на уровне FaaS.

sequenceDiagram participant GW as API Gateway participant F as Декорирующая функция participant Svc as Бэкенд-сервис GW->>F: Запрос F->>F: Добавить метаданные / проверить токен F->>Svc: Модифицированный запрос Svc-->>F: Ответ F->>F: Добавить заголовки кэширования F-->>GW: Финальный ответ

Ключевая идея: Расширение API без изменения основного сервиса, с автоматическим масштабированием.

Когда применять:

  • Нужно добавить общую логику (логгирование, rate limiting) для нескольких эндпоинтов.
  • Основной сервис — legacy или закрытый код (SaaS).
  • Нагрузка непостоянная, и держать сервис ради декоратора накладно.

18.2 Событийно‑ориентированные конвейеры (Event‑Driven Pipeline)

Описание: Цепочка функций или микросервисов, связанных через event bus (Kafka, Pub/Sub). Каждый этап — отдельная функция/контейнер, которая реагирует на событие, обрабатывает его и публикует новое событие. Пайплайн легко расширять добавлением новых этапов. Подходит для асинхронной обработки, например: регистрация пользователя → запись в CRM → отправка письма → начисление бонусов.

flowchart LR User[Пользователь зарегистрирован] --> F1[Функция: создать запись в CRM] User --> F2[Функция: отправить приветственное письмо] F2 --> F3[Функция: начислить бонусы]

Ключевая идея: Слабая связанность через события и параллельная обработка.

Когда применять:

  • Обработка одного события требует нескольких независимых действий.
  • Вы уже используете event bus для других задач.
  • Не нужна строгая согласованность (eventual consistency допустима).

19. Системы на основе очередей задач (Work Queue)

19.1 Обобщённая очередь задач (Generic Work Queue)

Описание: Контейнер-источник (producer) публикует задания в очередь (RabbitMQ, Kafka, SQS). Множество контейнеров-исполнителей (workers) разбирают задания. Каждое задание обрабатывается только одним worker'ом (если не требуется broadcast). Очередь сглаживает пики нагрузки, позволяет масштабировать workers независимо от producer'а.

flowchart LR P[Источник задач] -->|публикует задание| Q["Очередь задач (RabbitMQ, Kafka)"] Q --> W1[Исполнитель 1] Q --> W2[Исполнитель 2] Q --> W3[Исполнитель 3]

Ключевая идея: Разделение постановки задачи и её выполнения для асинхронной работы и масштабирования.

Когда применять:

  • Выполнение задачи занимает время, и клиент не может ждать синхронно (обработка видео, генерация отчётов).
  • Нагрузка неравномерная, и нужно выдержать пики.
  • Задачи могут обрабатываться в любом порядке (или можно обойтись FIFO).

19.2 Multi‑Worker (разные типы исполнителей)

Описание: Разные типы исполнителей читают из одной очереди, но обрабатывают только задания определённого типа (по заголовку, полю type, routing key). Например, очередь задач на обработку изображений: worker для ресайза, worker для сжатия, worker для конвертации формата. Брокер (RabbitMQ) маршрутизирует задания в разные очереди, привязанные к разным workers.

Ключевая идея: Гибкость в типах обработки, сохраняя единый источник заданий.


20. Паттерны пакетной событийно‑ориентированной обработки (Event‑Driven Batch)

Канальные паттерны, преобразующие потоки событий.

Паттерн Описание Диаграмма
Copier Копирует событие в несколько выходных потоков для независимой параллельной обработки src → Copier → out1, out2, out3
Filter Пропускает события, удовлетворяющие условию (например, amount > 1000) src → Filter → (true: out, false: discard)
Splitter Разбивает одно составное событие на несколько простых CompositeEvent → Splitter → Event1, Event2, Event3
Sharder Распределяет события по хэшу ключа в разные партиции (аналог шардирования) События → Sharder (hash) → Part0, Part1, Part2
Merger Объединяет несколько потоков событий в один in1, in2 → Merger → out

Описание: Эти паттерны используются в потоковых движках (Apache Flink, Kafka Streams, Spark Streaming) для трансформации бесконечных потоков событий. Copier — для fan-out (разветвления). Filter — для селективной обработки. Splitter — для декомпозиции. Sharder — для распределения нагрузки по партициям. Merger — для слияния источников.

Ключевая идея: Событийные потоки требуют операторов, аналогичных операторам в функциональном программировании (map, filter, flatMap, partition, merge).


21. Координированные пакетные паттерны (Coordinated Batch)

21.1 Join (барьерная синхронизация)

Описание: Ожидает все необходимые события/результаты из нескольких источников, прежде чем продолжить обработку. Например: заказ должен дождаться подтверждения от платежей и от склада. Join-барьер собирает все части, и только когда все пришли — выдаёт объединённый пакет в следующий этап. Реализуется через stateful потоковую обработку (окна в Flink).

sequenceDiagram participant J as Join-барьер participant S1 as Источник 1 participant S2 as Источник 2 participant Next as Следующий этап S1-->>J: результат A S2-->>J: результат B J->>J: дождались всех J-->>Next: объединённый пакет

Ключевая идея: Координация разнородных событий, приходящих в разное время.

21.2 Reduce (агрегация)

Описание: Агрегирует набор событий в одно значение (сумма, гистограмма, среднее, max). Например, за 5-минутное окно собрать события кликов и посчитать их количество. Reduce часто используется вместе с оконными операторами (tumbling window, sliding window). В отличие от обычного агрегата, Reduce на потоке — это инкрементальное вычисление (не хранит все события, только текущее агрегированное значение).

flowchart LR E1[Событие 1] --> R[Reducer] E2[Событие 2] --> R E3[Событие 3] --> R R -- агрегированное значение --> Sink[Хранилище]

22. AI-специфичные архитектурные паттерны (тренд 2025–2026)

Паттерн Описание
Agentic Loop LLM-агент в цикле: думает (reason) → действует (act) → наблюдает (observe) → повторяет
ReAct Reasoning + Acting — чередование "рассуждения" и "инструментов"
Plan-and-Execute Умная модель (GPT-4) строит план, дешёвые модели выполняют шаги плана
Reflection Агент проверяет и исправляет свой собственный output (self-critique)
RAG Retrieval-Augmented Generation — сначала поиск по векторной БД, затем генерация ответа с найденным контекстом
Multi-Agent Collaboration Несколько агентов с разными ролями (планировщик, исполнитель, критик, резюмер)

Описание RAG: LLM не хранит все знания. При запросе сначала ищем в векторной БД (Embeddings) релевантные документы/чанки. Затем отправляем LLM промпт: «Вопрос: ... Контекст: ... Ответь на основе контекста». Это уменьшает галлюцинации и позволяет подключать актуальные данные.

Описание Agentic Loop: Агент получает задачу, разбивает на шаги, выполняет инструменты (API, поиск, калькулятор), анализирует результат и решает, достаточно ли, или нужны ещё шаги. Цикл продолжается, пока цель не достигнута или не превышен лимит шагов.

Ключевая идея: LLM — не просто генератор текста, а агент, который может использовать инструменты и рефлексировать.

Когда применять RAG:

  • Нужно отвечать на вопросы по частной базе знаний (документы компании, FAQ, техподдержка).
  • Модель не знает свежих данных (например, курс валют, новости).
  • Хотите уменьшить галлюцинации.

Когда применять Agentic Loop:

  • Задача требует многошагового планирования (найти отель → сравнить цены → забронировать).
  • Нужно взаимодействие с внешними API (запрос к БД, отправка письма).
  • Вы готовы к непредсказуемой задержке и стоимости (каждый шаг — вызов LLM).

23. Сводная матрица выбора

Сценарий Рекомендуемый паттерн(ы)
Стартап, скорость Модульный монолит
Нужно добавить HTTPS/логирование в legacy Sidecar
Требуется шардирование на уровне сервиса Ambassador
Нормализация логов и метрик Adapter
Простое горизонтальное масштабирование без состояния Replicated Load‑Balanced Service
Распределённый кэш/поиск по ключу Sharded Service
Параллельный поиск/агрегация Scatter/Gather
Один экземпляр‑координатор Leader Election + Lock/Lease
Разные клиенты (web, mobile) BFF + API Gateway
Разные домены, большая команда Микросервисы, Database per Service, Saga
Высокая нагрузка на чтение Master-Slave + Cache-Aside (Redis)
Сложные отчёты + OLTP CQRS
Полный аудит изменений Event Sourcing
Асинхронная связанность Event-Driven (Kafka), Choreography
Гарантированная публикация событий Transactional Outbox + CDC
Распределённые транзакции Saga (orchestration)
Отказоустойчивость Circuit Breaker, Retry, Bulkhead
Обновление без downtime Blue-Green, Canary
Бессерверное преобразование запросов Decorator (FaaS)
Асинхронная обработка заданий Generic Work Queue + Multi‑Worker
Разветвление и параллелизм в потоках событий Copier, Splitter, Sharder
Фильтрация и объединение событий Filter, Merger
Координация пакетной обработки Join, Reduce
Миграция с монолита Strangler Fig, ACL, Satellite
AI-агенты, работа с частной базой знаний RAG, Agentic Loop
Инфраструктура как код Sidecar, Adapter, Service Mesh

Заключение

Выбор архитектурного паттерна — всегда компромисс между простотой, гибкостью и отказоустойчивостью. Основные правила:

  1. Начинайте с простого — модульный монолит решает 80% задач. Усложняйте только при появлении реальной боли.
  2. Чётко определяйте границы (bounded context) и владение данными (Database per Service).
  3. Автоматизируйте наблюдаемость и развёртывание до перехода к микросервисам — иначе вы ослепнете.
  4. Используйте метрики (RPS, latency, error budget) для обоснования изменений.
  5. Одноузловые паттерны (Sidecar, Ambassador, Adapter) позволяют добавлять инфраструктурные функции без модификации кода — не пренебрегайте ими.
  6. Пакетные и потоковые паттерны (Work Queue, Copier, Filter, Sharder, Reduce) образуют основу надёжных конвейеров обработки данных — изучайте Flink/Kafka Streams.
  7. AI-паттерны (RAG, Agentic Loop) — новая реальность. Начните с RAG для работы с документами, затем пробуйте агентов.

Данный справочник объединяет 80+ паттернов из классических и современных источников, предоставляя единый язык для архитекторов и разработчиков. Используйте его как шпаргалку при проектировании и рефакторинге систем.

Связанные статьи на сайте

Что дальше


Связанные статьи: Архитектурные роли в ИТ: кто и на каком уровне применяет эти паттерны