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

Микросервис — автономно развёртываемая часть системы вокруг бизнес-возможности. Главная граница проходит не по числу классов, а по владению моделью, данными, контрактом и жизненным циклом выпуска.

flowchart LR
    UI["Client"] --> A["Orders"]
    A --> B["Payments"]
    A --> K["Event log"]
    K --> C["Notifications"]
    A --- DA[("Orders DB")]
    B --- DB[("Payments DB")]

Отдельная БД на схеме означает ownership, а не запрет любых интеграционных проекций. Прямое чтение чужих таблиц связывает схемы и усложняет независимое изменение.

Цена распределения

Монолитный method call заменяется сетью, где возможны:

  • timeout с неизвестным результатом;
  • частичная недоступность;
  • повтор и дубликат;
  • изменение порядка сообщений;
  • несовместимые версии;
  • распределённая трассировка и сложный rollback.

Поэтому микросервисы оправданы, когда автономность команд/релизов и разные профили нагрузки окупают эту цену. Модульный монолит часто лучше как старт.

Надёжный синхронный вызов

  1. Общий latency budget и короткий timeout каждого downstream.
  2. Retry только для временной ошибки и повторяемой операции.
  3. Exponential backoff с jitter и лимитом попыток.
  4. Circuit breaker, чтобы не продолжать заведомо безуспешные calls.
  5. Bulkhead/лимит concurrency, чтобы один dependency не занял все threads.
  6. Наблюдаемость: correlation id, metrics, tracing.

Retry увеличивает нагрузку. Три уровня по три попытки могут превратить один запрос в десятки downstream calls.

Данные и события

Нельзя получить обычную ACID-транзакцию сразу для БД и Kafka простым порядком двух операций. Transactional outbox фиксирует бизнес-изменение и запись на публикацию в одной DB transaction. Consumer защищается inbox/idempotency key.

Для длительной операции используют saga: локальные transactions и compensating actions. Компенсация — новая бизнес-операция, а не магический rollback истории.

Реальный сетевой N+1

В рабочем потоке обход списка повторял получение одних и тех же связанных данных. Я вынес массовую загрузку доступных локально данных перед циклом и дедуплицировал оставшиеся удалённые обращения. Подробности: Кейс сетевой N plus 1.

Типичные сбои

  • Разделить приложение по техническим слоям и получить “микросервис базы”.
  • Создать синхронную цепочку из множества обязательных сервисов.
  • Разрешить всем читать общую схему и потерять независимость.
  • Retry любого POST без идемпотентности.
  • Выпускать breaking contract одновременно с единственным новым клиентом.
  • Централизовать каждое решение так, что сервисы нельзя развернуть независимо.

Вопросы для собеседования

В чём ключевой trade-off микросервисов? Организационная и deploy-автономность ценой распределённых отказов и эксплуатации.

Почему database per service? Чтобы сервис владел моделью и мог менять storage без скрытой связанности.

Как согласовать БД и событие без distributed transaction? Outbox плюс надёжная публикация, а на приёмной стороне — идемпотентность.

Почему retry опасен? Он усиливает нагрузку и повторяет side effects, если операция не идемпотентна.

Когда модульный монолит лучше? Когда границы ещё меняются, команда небольшая, а независимое масштабирование/релизы не окупают инфраструктурную цену.

См. REST, Kafka, Docker, Circuit Breaker, Предохранитель, автоматический выключатель, Обновление приложений в микросервисной и монолитной архитектурах.