Микросервисная архитектура: разработка, интеграция и деплой

Курс научит проектировать микросервисное приложение, разрабатывать отдельные сервисы, связывать их через API и сообщения, а затем развертывать в контейнерах. Разберем базовые паттерны надежности, наблюдаемости и практический CI/CD для доставки изменений.

1. Введение в микросервисы: границы сервисов и ответственность

Введение в микросервисы: границы сервисов и ответственность

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

Что такое «граница сервиса»

Граница сервиса — это договорённость, что находится внутри (логика, данные, правила) и что находится снаружи (контракты, события, API). Хорошая граница снижает количество причин для изменений и уменьшает связность.

Практичная формулировка:

  • Сервис отвечает за одну область бизнеса.
  • Внутри сервиса — высокая связность (всё связано общей задачей).
  • Между сервисами — минимальная связанность (взаимодействие только через контракты).
  • Визуализация идеи границ

    Ответственность сервиса: что обязательно определить

    1) Бизнес-ответственность (что сервис делает)

    Ответственность должна быть сформулирована как результат для бизнеса, а не как набор CRUD-операций.

    Примеры удачных формулировок:

  • «Управляет жизненным циклом заказа: создание, подтверждение, отмена»
  • «Обрабатывает платежи и отражает их статус»
  • Пример слабой формулировки:

  • «Сервис работы с таблицей orders» (это про хранение, а не про бизнес-смысл)
  • 2) Владение данными (что сервис контролирует)

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

    Что это даёт:

  • меньше конфликтов изменений схемы;
  • ясные источники истины;
  • проще гарантировать целостность внутри сервиса.
  • Компромисс: другим сервисам могут быть нужны данные «чужой» сущности. Тогда используются:

  • запросы через API владельца;
  • публикация событий и построение локальных представлений (кэш/проекция) у потребителя.
  • 3) Контракты взаимодействия (как сервис общается)

    Контракт — это обещание сервиса внешнему миру. Он должен быть стабильнее внутренней реализации.

    Типовые формы:

  • синхронный запрос-ответ (API) — когда нужен немедленный результат;
  • асинхронные события/сообщения — когда важна слабая связанность и реактивная обработка.
  • Важно: контракт описывает что доступно, но не раскрывает как внутри устроено.

    Как проводить границы: рабочие эвристики

    Эвристика 1: «Сначала бизнес, потом техника»

    Границы лучше определять по бизнес-процессам и правилам, а не по слоям приложения (UI/DAO/Service) и не по технологическим компонентам.

    Эвристика 2: Bounded Context (ограниченный контекст)

    Один и тот же термин может означать разное в разных частях бизнеса.

    Пример: «Клиент»

  • в продажах — тот, кто оформляет заказ;
  • в поддержке — тот, у кого есть обращения и SLA.
  • Если значения различаются, это сигнал к разным контекстам и потенциально разным сервисам.

    Эвристика 3: Изменяемость как главный индикатор

    Сгруппируйте вместе то, что меняется по одной причине. Если часть функциональности меняется независимо и часто конфликтует с другими изменениями — возможно, граница проведена неверно.

    Эвристика 4: «Один сервис — один владелец данных»

    Если два сервиса должны совместно обновлять одну таблицу — это почти всегда плохой знак. Либо данные нужно разделить, либо выбрать владельца и перенести ответственность.

    Типичные ошибки при выделении сервисов

  • Сервис на каждую таблицу
  • - приводит к множеству чатов между сервисами и росту задержек; - бизнес-правила расползаются.
  • Общая база на все сервисы
  • - деплой становится связанным (любое изменение схемы влияет на всех); - сложно контролировать целостность ответственности.
  • Слишком мелкие сервисы без автономности
  • - каждый запрос требует каскада вызовов; - растёт операционная сложность без пользы.
  • «Распределённый монолит»
  • - сервисы формально разделены, но изменения всегда делаются пачкой и требуют синхронного релиза.

    Мини-чеклист хорошей границы

  • У сервиса есть чёткая бизнес-формулировка ответственности (1–2 предложения).
  • Владелец данных определён, прямого доступа к БД сервиса извне нет.
  • Внешние контракты минимальны и понятны.
  • Большинство изменений можно вносить и деплоить без изменения соседних сервисов.
  • ---

    Задания для закрепления

    1) Разделение на сервисы. Есть интернет-магазин: каталог товаров, корзина, оформление заказа, оплата, доставка, поддержка. Предложите 4–6 сервисов и сформулируйте ответственность каждого.

    2) Владение данными. Для сущностей Order, Payment, Shipment, CustomerProfile укажите владельца-сервис и способ получения данных другими сервисами (API или события).

    3) Поиск ошибок границ. Ситуация: сервис «Заказы» пишет в таблицу платежей, потому что «так проще». Какие риски и как исправить?

    4) Сигналы “распределённого монолита”. Назовите 3 признака, что сервисы выделены формально, но автономности нет.

    <details> <summary> Ответы </summary>

    1) Пример разбиения:

  • Каталог — управляет товарами, ценами (в части каталога), атрибутами, доступностью для витрины.
  • Корзина — хранит и пересчитывает корзину пользователя, применяет промокоды на уровне корзины.
  • Заказы — управляет жизненным циклом заказа (создание, подтверждение, отмена), фиксирует состав и цены на момент заказа.
  • Оплата — создаёт платежи, проводит/подтверждает оплату, хранит статусы и транзакционные идентификаторы.
  • Доставка — создаёт отправления, трекинг, статусы доставки.
  • Поддержка (опционально) — обращения, статусы, SLA.
  • 2) Владение и доступ:

  • Order — владелец Заказы; другим: события OrderCreated/OrderCancelled и/или API чтения.
  • Payment — владелец Оплата; другим: события PaymentSucceeded/PaymentFailed.
  • Shipment — владелец Доставка; другим: события ShipmentCreated/ShipmentDelivered.
  • CustomerProfile — владелец Профиль/Клиенты (если выделен) или Поддержка (если профиль тесно связан с обращениями); другим: API чтения + события об изменениях профиля.
  • 3) Риски и исправление:

    Риски:

  • нарушение единого источника истины (кто «главный» за платеж?);
  • невозможно безопасно менять схему таблицы платежей;
  • растёт связанность деплоя и тестирования;
  • повышается шанс частичных/неконсистентных обновлений.
  • Исправление:

  • назначить сервис Оплата владельцем Payment;
  • сервис Заказы взаимодействует через API оплаты (создать платеж) и/или ждёт событие об успешной оплате;
  • при необходимости — хранить у заказов только ссылку/идентификатор платежа и агрегированный статус.
  • 4) Признаки распределённого монолита:

  • изменения почти всегда затрагивают несколько сервисов одновременно;
  • релизы делаются «пакетом», потому что контракты нестабильны;
  • много синхронных вызовов в цепочке на один пользовательский сценарий;
  • общая БД или прямые записи в чужие данные;
  • команды боятся менять контракты, потому что “всё сломается везде”.
  • </details>

    2. Проектирование API: REST, контракты, версионирование

    Проектирование API: REST, контракты, версионирование

    API в микросервисах — это внешний «шов» системы. Если границы и владение данными вы определили (см. статью про границы сервисов), то качество API решает, будет ли сервис независимо развиваемым, или изменения начнут «цеплять» соседей.

    REST как практический стиль

    REST полезен как набор ограничений, помогающих делать предсказуемые HTTP-интерфейсы.

    1) Ресурсы и идентификаторы

    Думайте не “методами”, а ресурсами (существительными): orders, payments, customers.

    Примеры удачных URI:

  • /orders — коллекция
  • /orders/{orderId} — конкретный заказ
  • /orders/{orderId}/items — подресурс
  • Пример, которого лучше избегать:

  • /createOrder (глагол в URI часто ведёт к RPC-стилю)
  • 2) HTTP-методы и идемпотентность

  • GET — чтение (без побочных эффектов)
  • POST — создание/команда, когда сервер назначает идентификатор
  • PUT — полная замена ресурса (обычно идемпотентен)
  • PATCH — частичное обновление
  • DELETE — удаление
  • Идемпотентность важна из‑за повторов запросов при таймаутах и ретраях. Практика для POST, который создаёт сущность: поддержать Idempotency-Key (одинаковый ключ → один результат).

    3) Статусы ответов: договор на уровне протокола

    | Ситуация | Код | Комментарий | |---|---:|---| | Успех чтения | 200 | Тело с данными | | Успех создания | 201 | Желательно Location на новый ресурс | | Команда принята в обработку | 202 | Асинхронная обработка | | Нет тела | 204 | Например, DELETE | | Ошибка валидации | 400 | Неверные данные клиента | | Нет прав | 401/403 | Аутентификация/авторизация | | Не найдено | 404 | Ресурс отсутствует | | Конфликт | 409 | Например, нарушена бизнес-уникальность | | Серверная ошибка | 5xx | Проблема на стороне сервиса |

    4) Единый формат ошибок

    Чтобы потребители не “парсили текст”, закрепите контракт ошибки, например:

    traceId полезен для поддержки и трассировки в распределённой системе.

    5) Фильтрация, сортировка, пагинация

    Закрепляйте правила явно, иначе каждый клиент будет понимать их по‑своему.

    Практичный минимум:

  • GET /orders?status=PAID&from=2026-01-01&to=2026-01-31
  • Пагинация: limit + cursor (стабильнее, чем offset при изменениях данных)
  • Сортировка: sort=createdAt,-id (если поддерживаете)
  • Контракт API: что именно нужно зафиксировать

    Контракт — это не только JSON-поля. Полезно явно зафиксировать следующие части:

  • Endpoint + метод: путь, HTTP-метод, обязательные заголовки.
  • Схемы: поля, типы, обязательность, ограничения (диапазоны, форматы).
  • Семантика: что означает операция и какие инварианты соблюдаются.
  • Ошибки: коды, формат, список типовых причин.
  • Совместимость: какие изменения допускаются без версии.
  • Нефункциональные условия:
  • 1) таймауты и ожидаемая латентность (на уровне договорённостей команд), 2) лимиты (rate limit), 3) идемпотентность и ретраи, 4) корреляционные заголовки (traceId, correlationId).

    Contract-first vs code-first

  • Contract-first: сначала описываете API (например, в OpenAPI), затем реализуете. Плюсы — меньше сюрпризов, проще согласование.
  • Code-first: сначала реализация, контракт получается из кода. Быстрее старт, но выше риск “случайного” изменения.
  • Для микросервисов часто выигрывает contract-first хотя бы для внешних (межсервисных и публичных) API.

    Consumer-driven contracts (CDC)

    CDC — подход, где потребители фиксируют ожидания от сервиса (контракты/пакты), а провайдер проверяет, что их не нарушает. Это снижает риск “сломать соседа” при независимом деплое.

    Версионирование: когда и как

    Главная цель — эволюция без массовых синхронных релизов.

    1) Предпочитайте “эволюцию без версии”

    Большинство изменений можно сделать обратно совместимыми:

  • Добавить новое поле в ответ (старые клиенты игнорируют).
  • Добавить новый необязательный параметр запроса.
  • Добавить новый endpoint.
  • Опасные изменения, почти всегда требующие версии или миграционного периода:

  • Переименовать/удалить поле.
  • Изменить тип поля (например, число → строка).
  • Изменить смысл поля (семантику) без изменения имени.
  • Сделать ранее необязательное поле обязательным.
  • 2) Где хранить версию

    Практики (у каждой есть компромиссы):

  • В URL: /v1/orders — просто и явно, удобно для API-шлюзов.
  • В заголовке: например, Accept/кастомный заголовок — чище URI, сложнее диагностировать.
  • В media type: application/vnd.company.orders-v1+json — строго, но усложняет клиентов.
  • Для внутренних микросервисов часто выбирают версию в URL из‑за прозрачности.

    3) Политика поддержки и деприкации

    Даже при версионировании нужен процесс:

  • Объявить срок поддержки версии.
  • Параллельно держать v1 и v2 до миграции.
  • Добавить наблюдаемость по использованию версий (кто ещё ходит в v1).
  • Снимать версию только после подтверждения миграции.
  • 4) Версии и конкурентные изменения (optimistic concurrency)

    Если клиенты могут перезаписывать данные друг друга, используйте ETag + If-Match (или аналог) и возвращайте 409 Conflict при конфликте. Это часть контракта обновления.

    Мини-чеклист проектирования API

  • Ресурсы названы существительными, операции предсказуемы по HTTP-методам.
  • Описаны идемпотентность и ретраи (особенно для “создать/оплатить/зарезервировать”).
  • Ошибки имеют единый формат и стабильные коды/типы.
  • Пагинация и фильтры формализованы.
  • Определены правила совместимости и версионирования.
  • ---

    Задания для закрепления

    1) Для домена интернет-магазина спроектируйте 5 endpoint’ов сервиса «Заказы» (URI + метод) для: создать заказ, получить заказ, отменить, список заказов с фильтром, добавить позицию.

    2) Придумайте контракт ошибки для сценария «недостаточно товара на складе» и укажите подходящий HTTP-код.

    3) Определите, какие изменения в ответе API обратно совместимы:

  • добавили поле currency
  • удалили поле status
  • поменяли тип total с number на string
  • добавили новый endpoint /orders/{id}/history
  • 4) Выберите стратегию версионирования (URL/заголовок/media type) для межсервисного API в компании и аргументируйте в 3–4 предложениях.

    <details> <summary> Ответы </summary>

    1) Пример:

  • POST /orders — создать заказ (поддержать Idempotency-Key).
  • GET /orders/{orderId} — получить заказ.
  • POST /orders/{orderId}/cancellation или DELETE /orders/{orderId} (если “удаление” соответствует семантике отмены; чаще лучше отдельная отмена как доменное действие).
  • GET /orders?status=PAID&cursor=...&limit=50 — список с фильтром и пагинацией.
  • POST /orders/{orderId}/items — добавить позицию (или PATCH /orders/{orderId} если модель позволяет частичное обновление; но отдельный подресурс часто яснее).
  • 2) Обычно 409 Conflict (бизнес-конфликт) или 422 Unprocessable Entity (если принято в компании). Формат:

    3) Обратно совместимы: (1) и (4). Не совместимы: (2) удаление поля, (3) смена типа.

    4) Пример аргументации за URL-версию: проще диагностировать, видно в логах и трассировках без распаковки заголовков, удобно маршрутизировать в API-gateway/ingress, снижает вероятность “случайно” забыть нужный заголовок в клиентах.

    </details>

    3. Взаимодействие сервисов: синхронно, асинхронно, очереди

    Взаимодействие сервисов: синхронно, асинхронно, очереди

    В микросервисах «правильно выбранный способ общения» часто важнее, чем конкретный протокол. От него зависят связность, задержки, устойчивость к сбоям и то, насколько реально сервисы деплоить независимо (см. статью про границы сервисов и ответственность). Здесь разберём два базовых подхода — синхронный и асинхронный — и роль очередей/брокеров сообщений.

    1) Синхронное взаимодействие (request/response)

    Суть: сервис A отправляет запрос сервису B и ждёт ответ.

    Когда подходит

  • Нужен немедленный результат для продолжения сценария (например, «проверить право доступа», «получить текущий статус»).
  • Операция короткая по времени и её удобно трактовать как «чтение» или «быстрая команда».
  • Ошибка должна быть видна сразу вызывающему (например, нельзя создать заказ, если не прошла валидация).
  • Цена синхронности

  • Связность по доступности: если B недоступен или медленный, страдает A.
  • Каскадные задержки: цепочка из нескольких вызовов умножает латентность.
  • Ретраи усиливают нагрузку: при деградации B повторные запросы могут добить систему.
  • Минимальная «гигиена» синхронных вызовов

  • Таймауты: лучше “быстро упасть”, чем зависнуть.
  • Ограничение параллелизма и очереди на стороне клиента (bulkhead).
  • Circuit breaker: временно прекращать вызовы в явно «плохой» сервис.
  • Идемпотентность команд, где возможны повторы (подробнее — в статье про проектирование API).
  • Визуально:

    2) Асинхронное взаимодействие (events/messages)

    Суть: сервис A публикует событие/сообщение и не ждёт немедленного результата. Сервис(ы) B, C… обрабатывают позже.

    Когда подходит

  • Действие может быть длинным или включает внешние системы.
  • Нужна слабая связность и независимость по времени (consumer может быть временно недоступен).
  • Есть fan-out: одно событие интересно нескольким подписчикам.
  • Допустима eventual consistency: состояние системы сходится не мгновенно.
  • События vs команды

  • Команда (command): “сделай X” — адресована конкретному получателю и выражает намерение.
  • Событие (event): “X произошло” — факт, который могут читать многие.
  • Практичное правило: межконтекстное взаимодействие чаще строят на событиях, чтобы не превращать соседние сервисы в «выполнители команд» вашего процесса.

    Визуально:

    3) Очереди, топики и брокер сообщений

    Брокер (Kafka/RabbitMQ и т.п.) даёт инфраструктуру доставки и буферизации.

    Очередь (queue) — «один обработчик из группы»

  • Сообщения обрабатываются конкурирующими потребителями (competing consumers).
  • Хорошо для задач типа “обработать платёж”, “отправить письмо”, “сгенерировать документ”.
  • Позволяет сглаживать пики: очередь накапливает, потребители догоняют.
  • Топик/паб-саб (pub/sub) — «каждому подписчику своё»

  • Каждая подписанная группа получает свою копию потока.
  • Хорошо для доменных событий: “OrderCreated”, “PaymentSucceeded”.
  • 4) Доставка и неизбежные дубликаты

    В распределённых системах «идеальной доставки» почти нет. Частая модель — at-least-once: сообщение будет доставлено минимум один раз, но иногда повторно.

    Отсюда требования:

  • Идемпотентный обработчик: повторная обработка не портит состояние.
  • Дедупликация: хранить messageId/eventId обработанных сообщений (inbox) или использовать уникальные ключи в данных.
  • Понимание порядка: глобальный порядок обычно дорог или невозможен; часто можно гарантировать порядок лишь внутри ключа (например, по orderId).
  • 5) Надёжная публикация: Outbox и DLQ

    Outbox (согласованность «БД + событие»)

    Проблема: сервис записал изменения в свою БД, но упал до публикации события — другие сервисы не узнают.

    Решение (паттерн Outbox):

  • В той же транзакции, что и бизнес-изменение, записать событие в таблицу outbox.
  • Отдельный процесс/воркер читает outbox и публикует в брокер.
  • После успешной публикации помечает событие как отправленное.
  • DLQ (dead-letter queue) и «ядовитые» сообщения

    Если сообщение постоянно ломает обработчик (битые данные, несовместимость схемы), нужны:

  • Лимит попыток и backoff.
  • Перенос в DLQ с причиной ошибки.
  • Процесс разбора и переигрывания после исправления.
  • 6) Как выбрать: короткая матрица решения

    | Вопрос | Скорее синхронно | Скорее асинхронно | |---|---|---| | Нужен ответ прямо сейчас? | Да | Нет | | Допустима eventual consistency? | Редко | Часто | | Нужен fan-out на несколько сервисов? | Неудобно | Естественно | | Важна устойчивость к временному падению потребителя? | Сложно | Естественно | | Действие долгое/фоновое? | Плохо | Хорошо |

    Важно: смешанные схемы — норма. Например, пользователь получает 202 Accepted (синхронно), а результат приходит событием/уведомлением (асинхронно).

    Мини-чеклист проектирования межсервисного взаимодействия

  • Вы явно выбрали стиль: sync или async, и можете объяснить почему.
  • Для async определены: ключи порядка (если нужны), политика ретраев, DLQ, дедупликация.
  • Контракты сообщений/событий версионируются и обратно совместимы (по смыслу — как и API).
  • Есть корреляция: traceId/correlationId прокидываются через сообщения и логи.
  • Для публикации событий из БД предусмотрен Outbox или эквивалентная гарантия.
  • ---

    Задания для закрепления

    1) Для сценария интернет-магазина выберите стиль взаимодействия (sync/async) и коротко объясните:

  • «Проверить промокод в корзине»
  • «Списать деньги»
  • «Отправить письмо о заказе»
  • «Показать пользователю статус заказа на странице»
  • 2) У вас at-least-once доставка. Предложите 2 способа сделать обработчик события PaymentSucceeded безопасным к повторам.

    3) Сервис записывает заказ в БД и публикует OrderCreated. Опишите проблему “запись прошла, событие не ушло” и как её закрывает Outbox (3–5 шагов).

    4) Сообщение падает на обработке 50 раз из-за данных. Какие 3 меры вы добавите в систему доставки/обработки?

    <details> <summary> Ответы </summary>

    1) Возможный выбор:

  • Промокод — чаще sync: нужен немедленный ответ для расчёта цены.
  • Списание денег — часто смешанно: инициирование может быть sync (приняли запрос), подтверждение результата — async событиями (успех/ошибка), потому что платёжные провайдеры и 3DS бывают долгими/непредсказуемыми.
  • Письмо — async: фоновая задача, не должна блокировать пользователя.
  • Статус заказа на странице — sync чтение (быстрый запрос к владельцу статуса или к read-модели), но сам статус может обновляться async событиями.
  • 2) Два способа:

  • Дедуп по eventId: хранить обработанные идентификаторы и пропускать повтор.
  • Идемпотентное обновление: например, переводить платёж в SUCCEEDED только если он ещё не SUCCEEDED, или использовать уникальный ключ внешней транзакции и обновлять по нему.
  • 3) Проблема и Outbox:

  • Проблема: если событие публикуется «после транзакции» и сервис падает, в БД заказ есть, а остальные сервисы не узнают.
  • Outbox:
  • 1) В одной транзакции: записать заказ и запись в outbox (OrderCreated, payload, eventId). 2) Компонент-публикатор читает outbox. 3) Публикует событие в брокер. 4) Помечает outbox-запись как отправленную (или удаляет). 5) При сбое процесс повторит попытку, не теряя событие.

    4) Меры:

  • Ограничить число ретраев + backoff, чтобы не «молотить» бесконечно.
  • Отправлять в DLQ с причиной и метаданными (traceId, messageId).
  • Добавить валидацию/схему и мониторинг алертов по DLQ, плюс процесс ручного/автоматического переигрывания после исправления.
  • </details>

    4. Данные в микросервисах: отдельные БД, согласованность, саги

    Данные в микросервисах: отдельные БД, согласованность, саги

    Ключевой принцип микросервисов — сервис владеет своими данными. Это делает сервис автономным, но ломает привычную модель «одна транзакция на всё приложение». В результате приходится осознанно проектировать согласованность, дублирование данных и сквозные бизнес-процессы.

    1) Отдельная БД на сервис: зачем и что это меняет

    Один сервис — один владелец данных означает:

  • Другие сервисы не пишут в его хранилище напрямую.
  • Изменение схемы и оптимизации хранения можно делать без координации со всеми.
  • Границы ответственности проще удерживать (см. статью про границы сервисов).
  • Цена:

  • Нельзя «просто сделать JOIN» между данными разных сервисов.
  • Сквозные операции (заказ + оплата + доставка) становятся распределёнными.
  • Согласованность чаще становится eventual (сходится со временем).
  • Типичные анти-паттерны

  • Общая база на несколько сервисов: быстрый старт, но затем связанный деплой, конфликт изменений схемы и расползание ответственности.
  • Прямой доступ к чужой БД “только на чтение”: со временем превращается в зависимость от схемы и скрытые контракты.
  • 2) Как делиться данными без общей БД

    На практике применяют две модели (часто вместе):

  • Запрос к владельцу данных (API) — когда нужно “прямо сейчас” получить актуальное значение.
  • Локальная копия/проекция у потребителя (read model) — когда важнее скорость, автономность и устойчивость. Тогда сервис-потребитель хранит у себя денормализованное представление, обновляя его событиями.
  • Важный вывод: дублирование данных — нормально, если:

  • Есть явный источник истины (владелец).
  • Понятно, какая задержка обновления допустима.
  • Обновление идёт через контракты (события/сообщения), а не через чтение чужих таблиц.
  • 3) Согласованность: strong vs eventual на уровне бизнеса

    В монолите часто ожидают “сразу везде одинаково”. В микросервисах согласованность — это бизнес-решение:

  • Strong consistency нужна редко и обычно локально — внутри одного сервиса и его транзакции.
  • Eventual consistency часто приемлема между сервисами: данные могут быть кратковременно “не синхронизированы”, но затем сойдутся.
  • Практический вопрос, который нужно задать на проектировании:

  • Что пользователь должен видеть немедленно?
  • Что можно показать “в обработке” и обновить позже?
  • Какие состояния допустимы промежуточно (например, PAYMENT_PENDING)?
  • 4) Почему распределённые транзакции почти всегда плохая идея

    Технически можно попытаться сделать атомарность между несколькими сервисами (например, 2PC), но в микросервисной архитектуре это обычно приводит к:

  • Связности по доступности (все участники должны быть «живы» одновременно).
  • Блокировкам и сложной деградации.
  • Сложности эксплуатации и восстановления.
  • Вместо “одной глобальной транзакции” чаще проектируют бизнес-процесс, который допускает шаги и компенсации. Это и есть поле для саг.

    5) Сага: распределённый бизнес-процесс с компенсациями

    Сага — это цепочка локальных транзакций в разных сервисах. Каждый шаг:

  • Делает изменение в своём сервисе (локально атомарно).
  • Публикует факт/результат, чтобы инициировать следующий шаг.
  • При ошибке запускается компенсация — действие, отменяющее эффект ранее выполненных шагов (насколько это возможно по бизнесу).
  • Важно: компенсация — не “технический rollback”. Это бизнес-операция: “отменить заказ”, “снять резерв”, “сделать возврат”.

    Два стиля саг

    #### 1) Оркестрация (orchestration) Есть отдельный компонент/сервис-оркестратор, который:

  • Хранит состояние процесса.
  • Решает, какой шаг следующий.
  • Запускает компенсации.
  • Плюсы: проще видеть весь процесс, централизованная логика. Минусы: риск “умного центра”, который знает слишком много.

    #### 2) Хореография (choreography) Нет центра: сервисы реагируют на события друг друга.

    Плюсы: меньше централизованной связанности. Минусы: сложнее отлаживать поток и понимать “кто за что отвечает”, выше риск циклов.

    Пример саги (упрощённо)

    6) Практические правила проектирования саг

  • Явные состояния. Введите промежуточные статусы (PENDING, AWAITING_PAYMENT, CANCELLING), чтобы система могла “жить” между шагами.
  • Компенсации не всегда симметричны. Например, “отправленную посылку” нельзя “отменить”, но можно “оформить возврат”. Это влияет на допустимые точки отказа.
  • Идемпотентность шагов и обработчиков. Повторы неизбежны (см. статью про доставку сообщений): шаг должен быть безопасен к повторному выполнению.
  • Храните корреляцию. У процесса должен быть единый correlationId/sagaId, чтобы связывать события, логи и трассировки.
  • Согласованная публикация событий. Если вы публикуете события из БД, используйте гарантии вроде Outbox (подробно было в статье про взаимодействие сервисов).
  • Мини-чеклист по данным в микросервисах

  • Владелец каждой сущности определён, прямой доступ к чужим данным запрещён.
  • Для межсервисных данных выбран подход: запрос к владельцу или локальная проекция (и понятна допустимая задержка).
  • Сквозные операции оформлены как сага: шаги, состояния, компенсации.
  • Обработчики устойчивы к повторам и имеют корреляцию.
  • ---

    Задания для закрепления

    1) Для интернет-магазина выберите, где допустима eventual consistency:

  • Статус оплаты на странице заказа
  • Остатки товара на витрине
  • Отображение истории заказов пользователя
  • 2) Сага “Оформление заказа”: предложите 4 шага и по одной компенсации для каждого шага, где это возможно.

    3) Выберите стиль саги (оркестрация или хореография) для процесса “возврат денег + отмена доставки + уведомление клиента” и объясните выбор в 3–5 предложениях.

    4) Вы строите read model “Заказы пользователя” в сервисе “Профиль”. Какие 3 риска нужно учесть (с точки зрения данных и эксплуатации)?

    <details> <summary> Ответы </summary>

    1) Пример:

  • Статус оплаты на странице заказа — часто eventual допустима (можно показать “в обработке”, обновить через секунды).
  • Остатки на витрине — часто eventual допустима, но зависит от бизнеса; если “последние 1–2 штуки” критичны, потребуется аккуратное резервирование и явные статусы “резерв/нет”.
  • История заказов — почти всегда eventual допустима (пользователь переживёт задержку появления нового заказа на 1–5 секунд).
  • 2) Пример саги:

  • Заказы: создать заказ в статусе PENDING.
  • - Компенсация: отменить заказ (CANCELLED).
  • Склад/Резерв: зарезервировать товар.
  • - Компенсация: снять резерв.
  • Оплата: инициировать/подтвердить платёж.
  • - Компенсация: оформить возврат (если платёж уже успешен).
  • Доставка: создать отправление.
  • - Компенсация: отменить отправление (если ещё возможно) или создать процесс возврата/перехвата (если уже в пути).

    3) Пример выбора: оркестрация.

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

    4) Риски read model:

  • Рассинхронизация/задержки: пользователь может видеть устаревшие данные; нужно определить SLA на обновление и UX-статусы.
  • Повторы и порядок событий: при at-least-once возможны дубликаты, а при параллельной обработке — перестановка; нужна идемпотентность и аккуратная модель обновления.
  • Эволюция схемы событий: изменения контрактов могут ломать построение проекции; нужны версии/обратная совместимость и стратегия пересборки проекций.
  • </details>

    5. Контейнеризация: Docker, сборка образов, локальная среда

    Контейнеризация: Docker, сборка образов, локальная среда

    Контейнеризация — практический способ упаковать сервис вместе с его зависимостями и окружением так, чтобы он одинаково запускался на ноутбуке разработчика, на CI и в продакшене. Это особенно важно в микросервисах: сервисов много, релизы независимые, а среда должна быть воспроизводимой.

    1) Базовые понятия: образ и контейнер

  • Образ (image) — неизменяемый «шаблон» файловой системы и метаданных (как собирать/запускать).
  • Контейнер (container) — запущенный экземпляр образа с конкретными настройками (переменные окружения, порты, тома).
  • Полезная ментальная модель:

    Что даёт Docker в микросервисах

  • Одинаковый способ запуска всех сервисов (не важно, на каком языке написаны).
  • Воспроизводимость: «работает у меня» становится редкостью.
  • Изоляция зависимостей: разные версии библиотек/рантаймов не конфликтуют.
  • 2) Dockerfile как контракт упаковки

    Dockerfile описывает, как собрать образ. В нём обычно фиксируют:

  • Базовый образ (например, с нужным рантаймом).
  • Копирование артефактов (исходники или собранные бинарники).
  • Команду запуска.
  • (Опционально) пользователя, порты, healthcheck.
  • Минимальная структура выглядит так:

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

    3) Практики сборки образов

    Теги и версии

  • Всегда различайте immutable и mutable теги.
  • - Immutable: service:1.4.2 (конкретная версия). - Mutable: service:latest (переезжает и плохо подходит для продакшена).

    Хорошая практика: публиковать оба — версионный тег и тег “ветки/канала” (например, main).

    Кэш сборки

    Docker кэширует слои. Чтобы кэш работал на вас:

  • Редко меняющиеся шаги (установка зависимостей) располагайте раньше.
  • Часто меняющиеся (копирование приложения) — ближе к концу.
  • .dockerignore

    Смысл — не отправлять в контекст сборки лишнее:

  • директории сборки, временные файлы, локальные кеши;
  • .git (обычно), логи, секреты.
  • Это ускоряет сборку и снижает риск утечек.

    Multi-stage сборка

    Частый подход: собрать артефакт в одном “stage”, а запускать — в более лёгком рантайм-образе.

    Плюсы:

  • Меньше размер образа.
  • Меньше поверхность атаки.
  • Быстрее скачивание и старт.
  • 4) Runtime-настройки: что не «зашивать» в образ

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

  • Переменные окружения (URL зависимостей, флаги, режимы).
  • Секреты (пароли/токены) — не хранить в образе.
  • Порты и параметры запуска.
  • Принцип: образ — артефакт сборки, конфигурация — артефакт окружения.

    5) Сеть, порты, тома (минимум для локальной работы)

  • Порты: контейнер может слушать внутри, а наружу публикуется нужный порт хоста.
  • Сети (networks): контейнеры в одной сети находят друг друга по имени сервиса (удобно для локальной среды).
  • Тома (volumes):
  • - для данных БД, чтобы переживали перезапуск; - для разработки — чтобы монтировать исходники и ускорять цикл (если ваша технология это поддерживает).

    6) Локальная среда: Docker Compose как “мини-интеграция”

    Для микросервисов локально важно поднимать не только ваш сервис, но и инфраструктуру: БД, брокер сообщений, кеш. Docker Compose позволяет описать набор контейнеров и их связи.

    Что обычно фиксируют в compose-файле:

  • Список сервисов (ваш сервис + зависимости).
  • Переменные окружения.
  • Порты для доступа с хоста.
  • Том(а) для данных.
  • Общую сеть.
  • Визуально:

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

    7) Мини-безопасность и эксплуатационная гигиена

  • Запускайте процесс не под root (если возможно).
  • Используйте минимальные базовые образы.
  • Не кладите секреты в Dockerfile, слои образа и репозиторий.
  • Добавляйте healthcheck, чтобы оркестрация/compose могли отличать «процесс запущен» от «сервис готов обслуживать запросы».
  • ---

    Задания для закрепления

    1) Объясните разницу между образом и контейнером на примере сервиса оплаты.

    2) Вам нужно ускорить сборку: какие 3 причины могут ломать кэш Docker и что с ними сделать?

    3) Перечислите 5 вещей, которые нельзя “зашивать” внутрь образа, если вы хотите один и тот же образ для dev/stage/prod.

    4) Вы поднимаете локально сервис “Заказы”, Postgres и брокер сообщений. Какие элементы (минимум 4) вы зафиксируете в Docker Compose, чтобы сервисы стабильно находили друг друга?

    5) Ситуация: контейнер «запущен», но запросы не проходят. Назовите 4 шага диагностики, не выходя за рамки Docker (логи/порты/сети/health).

    <details> <summary> Ответы </summary>

    1) Образ vs контейнер:

  • Образ сервиса оплаты — это “упакованный сервис” (файлы приложения + инструкции запуска).
  • Контейнер — конкретный запуск этого образа, например:
  • - с переменными PAYMENT_PROVIDER_URL=..., - с проброшенным портом, - с подключением к сети, где доступна БД.

    2) Почему не работает кэш и что делать:

  • Вы копируете “весь проект” слишком рано, и любое изменение файла инвалидирует кэш ранних слоёв — копируйте сначала файлы зависимостей/манифестов, а исходники позже.
  • В контекст сборки попадают лишние файлы (логи, build-артефакты) — добавьте .dockerignore.
  • Шаги сборки не детерминированы (например, скачивание “последней версии” без фиксирования) — фиксируйте версии зависимостей.
  • 3) Что не зашивать в образ (примеры):

  • Пароли, токены, ключи.
  • URL-адреса внешних сервисов, специфичные для окружения.
  • Конкретные доменные имена/хосты инфраструктуры.
  • Конфигурацию логирования/уровни логов, зависящие от окружения.
  • Включение/выключение фич (feature flags), если они меняются по средам.
  • 4) Минимум для Compose:

  • Определение трёх сервисов: orders, postgres, broker.
  • Общая сеть, чтобы они резолвились по именам.
  • Переменные окружения для orders: строка подключения к Postgres, адрес брокера.
  • Порты (например, порт orders наружу для тестов) и том для данных Postgres.
  • 5) Диагностика “контейнер жив, сервис не отвечает”:

  • Посмотреть логи контейнера: есть ли ошибки старта, миграций, подключения к БД.
  • Проверить проброс портов: опубликован ли нужный порт на хост и совпадает ли он с тем, что слушает процесс в контейнере.
  • Проверить сеть/резолвинг имён: видит ли сервис контейнер БД/брокера по имени.
  • Проверить healthcheck (если настроен) и реальные зависимости: часто процесс поднялся, но сервис не “готов” из-за ожидания БД.
  • </details>

    6. Оркестрация и деплой: Kubernetes, конфиги, сервис-дискавери

    Оркестрация и деплой: Kubernetes, конфиги, сервис-дискавери

    Когда микросервисов становится много, одного Docker уже недостаточно: нужны автоматическое размещение контейнеров, перезапуск при падениях, масштабирование, сетевое подключение и управляемые релизы. Эту роль обычно выполняет Kubernetes (K8s). Сборку образов и локальную среду см. в статье про Docker и Docker Compose.

    1) Ментальная модель Kubernetes: «что запустить» и «как сделать доступным»

    Kubernetes оперирует двумя слоями:

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

  • Pod — минимальная единица запуска (обычно 1 контейнер = 1 pod). Pod краткоживущ: его IP может меняться.
  • Deployment — декларация желаемого состояния: сколько реплик, как обновлять, какие метки. Именно Deployment чаще всего «деплоят».
  • Service — стабильная точка доступа (виртуальный адрес + балансировка) к pod’ам по меткам.
  • Визуально:

    2) Декларативный деплой и «желательное состояние»

    В Kubernetes вы описываете что должно быть (например, 3 реплики, такая-то версия образа), а контроллеры доводят реальность до описания.

    Практичные последствия:

  • Самовосстановление: pod упал — будет создан новый.
  • Горизонтальное масштабирование: меняете число реплик — K8s перераспределяет.
  • Повторяемость: манифесты — это «инфраструктура как код», удобно хранить и ревьюить.
  • 3) Релизы без простоя: rolling update и rollback

    Типовой режим обновления Deployment — rolling update:

  • Создаются новые pod’ы с новой версией.
  • Когда они готовы, трафик постепенно переключается.
  • Старые pod’ы удаляются.
  • Ключевое условие безопасного обновления — корректные проверки готовности.

    Liveness vs Readiness

  • Liveness probe отвечает на вопрос: «процесс жив?» Если нет — pod перезапустят.
  • Readiness probe: «готов обслуживать трафик?» Если нет — pod исключат из балансировки Service.
  • Если перепутать их роли, можно получить либо вечные перезапуски, либо подачу трафика на “непрогретый” сервис.

    4) Конфигурация: ConfigMap и Secret

    Образ контейнера должен оставаться одинаковым между окружениями, а отличия задаются конфигом (этот принцип уже обсуждался в статье про контейнеризацию).

    В Kubernetes для этого обычно используют:

  • ConfigMap — некритичные настройки (URL внутренних сервисов, флаги, таймауты).
  • Secret — чувствительные данные (пароли, токены). Важно: Secret — это механизм распространения, а не полноценная система управления секретами.
  • Способы доставки конфигурации в pod:

  • переменные окружения;
  • файлы, смонтированные в контейнер (удобно для конфигов приложений).
  • Практики, чтобы конфиги не стали источником инцидентов:

  • Явное именование и версионирование (хотя бы через имена/лейблы релиза).
  • Минимальные права доступа: сервису не нужен доступ ко всем Secret’ам namespace.
  • Безопасная смена: некоторые приложения читают конфиг только на старте — тогда смена ConfigMap потребует перезапуска pod’ов.
  • 5) Сервис-дискавери: как сервисы находят друг друга

    Поскольку pod’ы пересоздаются и их адреса меняются, обращение «по IP» неприемлемо. Kubernetes решает это через Service + DNS.

    DNS-имя сервиса

    Внутри кластера сервис обычно доступен по имени вида:

    Чаще всего достаточно короткой формы:

    Типы Service (практически)

  • ClusterIP (по умолчанию) — доступен только внутри кластера. База для межсервисного общения.
  • NodePort/LoadBalancer — варианты опубликовать сервис наружу (выбор зависит от платформы и инфраструктуры).
  • Для входящего HTTP-трафика часто используют Ingress: он принимает внешние запросы и маршрутизирует их на нужные Service’ы по host/path.

    6) Изоляция и порядок: namespaces, labels, selectors

  • Namespace — логическое разделение (например, dev, stage, prod или команды/домены). Упрощает квоты, права и поиск ресурсов.
  • Labels — метки на объектах (app=orders, version=v2).
  • Selectors — правила, по которым Service и Deployment «выбирают» pod’ы.
  • Если селекторы настроены неверно, возможны два опасных сценария:

  • сервис не получает трафик (нет подходящих pod’ов);
  • сервис отправляет трафик «не туда» (подхватил чужие pod’ы).
  • 7) Ресурсы и масштабирование (минимум)

    Чтобы кластер был предсказуемым, задают:

  • requests — гарантируемый минимум CPU/памяти;
  • limits — верхняя граница.
  • Для автоскейлинга часто применяют HPA (Horizontal Pod Autoscaler), который меняет число реплик по метрикам (например, CPU). Даже без деталей важно помнить: автоскейлинг работает лучше, когда сервис статeless и готов к нескольким экземплярам.

    ---

    Задания для закрепления

    1) Для сервиса orders перечислите, какие Kubernetes-ресурсы нужны минимум для запуска 3 реплик и стабильного обращения к ним из других сервисов.

    2) Приведите пример, чем опасно использовать IP pod’а для межсервисного вызова.

    3) В чём разница между liveness и readiness probe? Опишите по одному инциденту, который возможен при неправильной настройке каждой.

    4) У вас есть настройки: PAYMENTS_URL, LOG_LEVEL, DB_PASSWORD. Что положите в ConfigMap, а что в Secret, и как это доставить в контейнер (общими словами)?

    5) Сервис billing в namespace finance должен вызвать сервис users в namespace core. Как будет выглядеть обращение по DNS (достаточно формы с namespace)?

    <details> <summary> Ответы </summary>

    1) Минимум:

  • Deployment orders (replicas=3, образ, labels).
  • Service orders (selector по labels, порт).
  • 2) IP pod’а не стабилен: pod может быть пересоздан при падении/обновлении/эвикшене ноды, и IP изменится. Клиент начнёт ходить в «никуда» или, хуже, в другой pod/сервис при повторном использовании адреса.

    3) Разница:

  • Readiness: «готов принимать трафик». Инцидент: pod ещё не подключился к БД/не прогрел кэш, но уже получает запросы и отвечает ошибками.
  • Liveness: «жив ли процесс». Инцидент: временная деградация (долгие GC/нагрузка/внешняя зависимость) ошибочно трактуется как “не жив”, pod уходит в цикл перезапусков.
  • 4) Пример:

  • ConfigMap: PAYMENTS_URL, LOG_LEVEL.
  • Secret: DB_PASSWORD.
  • Доставка: либо как переменные окружения, либо как смонтированные файлы в контейнер (зависит от того, как приложение читает конфиг).

    5) Пример обращения: users.core (короткая форма) или полнее users.core.svc.cluster.local.

    </details>

    7. Надежность и CI/CD: логирование, метрики, трассировка, пайплайны

    Надежность и CI/CD: логирование, метрики, трассировка, пайплайны

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

  • Наблюдаемость (observability): логи, метрики, трассировка.
  • Практичный CI/CD: автоматизированная проверка и поставка, чтобы изменения не ломали систему и откатывались контролируемо.
  • > Про таймауты, ретраи, идемпотентность, корреляцию и readiness/liveness мы уже говорили в статьях про взаимодействие сервисов и Kubernetes — здесь опираемся на эти идеи и добавляем эксплуатационный слой.

    1) Логирование: минимальный контракт для диагностики

    Цель логов — ответить на вопрос “что произошло и почему”, не «рассказывая историю приложения». В микросервисах важнее всего структурированные логи (JSON или другой машинно-читаемый формат), чтобы их можно было агрегировать и искать.

    Что должно быть в каждом событии лога

  • timestamp — время.
  • level — INFO/WARN/ERROR.
  • service и version — кто пишет и какая версия.
  • traceId/correlationId — сквозная связка (эти идентификаторы вы уже прокидывали в API/сообщениях).
  • requestId (опционально) — удобно для конкретного входящего запроса.
  • event — короткое имя события (например, payment.capture_failed).
  • context — ключевые поля (например, orderId, paymentId, customerId).
  • error — тип/код и краткое сообщение (без «простыней» и секретов).
  • Практики, которые реально повышают надежность

  • Логировать результаты, а не шаги: “оплата не подтверждена из-за таймаута” важнее “вошли в метод X”.
  • Не логировать секреты и персональные данные. Лучше хранить ссылки/идентификаторы.
  • Разделять бизнес-ошибки и сбои: бизнес-конфликт (например, insufficient_stock) — это не всегда ERROR.
  • Единый формат ошибок (см. статью про API) — чтобы в логах и ответах были одинаковые коды/типы.
  • 2) Метрики: измеряем поведение сервиса численно

    Логи отвечают на “почему”, метрики — на “насколько плохо и где тренд”. Полезная базовая модель метрик:

  • RED для HTTP/gRPC: Rate (RPS), Errors, Duration.
  • USE для ресурсов: Utilization, Saturation, Errors.
  • Минимальный набор метрик на сервис

  • Трафик: количество запросов/сообщений по типам.
  • Ошибки: доля 5xx/таймаутов/ошибок обработки сообщений.
  • Латентность: p50/p95/p99 (важны хвосты, не только среднее).
  • Очереди/брокер: lag/размер очереди, число ретраев, DLQ-объем.
  • Зависимости: отдельно измеряйте вызовы внешних сервисов (ошибки и время).
  • SLI/SLO как рабочая договоренность

  • SLI — измерение (например, доля успешных запросов).
  • SLO — цель (например, “99.9% запросов успешны за 7 дней”).
  • Смысл SLO в микросервисах: ограничить риск изменений. Если SLO «горит», релизы должны замедляться и включаться режим стабилизации.

    3) Распределенная трассировка: склеиваем цепочку вызовов

    В микросервисах один пользовательский запрос часто превращается в цепочку вызовов и сообщений. Трассировка отвечает на вопрос: “где именно возникла задержка/ошибка”.

    Что важно сделать архитектурно

  • Единый traceId проходит через:
  • 1) входящий запрос, 2) межсервисные вызовы, 3) публикацию/обработку сообщений.
  • Спаны (spans) должны отражать границы: входящий запрос, вызов зависимости, обработка сообщения, запрос к БД.
  • Сэмплинг: в проде часто нельзя хранить 100% трасс. Но ошибки и медленные запросы полезно собирать чаще.
  • Мини-визуализация:

    4) CI/CD: пайплайн как “конвейер надежности”

    Цель CI/CD в микросервисах — не просто «собрать и выкатить», а уменьшить вероятность инцидента и ускорить восстановление.

    Базовые стадии пайплайна (практичный минимум)

  • Build: сборка и публикация неизменяемого артефакта (образ с версией, не latest).
  • Static checks: линтеры, базовые security checks.
  • Unit tests: быстрые проверки.
  • Contract checks:
  • 1) провайдер не ломает потребителей (CDC), 2) совместимость схем событий.
  • Integration tests (тонко): поднять зависимости/эмуляторы и прогнать критические сценарии.
  • Deploy to stage: деплой как в проде по способу, но меньшего масштаба.
  • Gates: автоматические критерии (ошибки/латентность/здоровье) + опционально ручное окно.
  • Deploy to prod: раскатка и мониторинг.
  • Стратегии выкладки и отката

  • Rolling update (по умолчанию в Kubernetes): безопасно при корректной readiness probe.
  • Canary: небольшой процент трафика на новую версию; метрики решают, расширять ли.
  • Blue/Green: две среды, быстрое переключение трафика, простой откат.
  • Важно: откат — это не «команда в голове», а операция в пайплайне: вернуться на предыдущий образ/манифест.

    Наблюдаемость как часть релиза

    После выкладки пайплайн должен уметь ответить:

  • Ухудшилась ли доля ошибок?
  • Уехали ли p95/p99?
  • Не вырос ли lag очередей/DLQ?
  • Есть ли всплеск ошибок по зависимостям?
  • Если да — остановить раскатку или откатить.

    ---

    Задания для закрепления

    1) Для сервиса “Оплата” перечислите 8 полей структурированного лога, которые вы сделаете обязательными.

    2) Придумайте 3 SLI для сервиса “Заказы” и по одному SLO для каждого.

    3) У вас есть цепочка: Orders -> Payments -> Provider. Где вы добавите спаны и какие 2 атрибута (тега) будут полезны для поиска проблем?

    4) Составьте пайплайн из 7–9 шагов для микросервиса, который:

  • имеет REST API,
  • публикует события в брокер,
  • деплоится в Kubernetes.
  • 5) Выберите стратегию релиза (rolling/canary/blue-green) для критичного сервиса оплаты и аргументируйте в 4–5 предложениях, учитывая откат и метрики.

    <details> <summary> Ответы </summary>

    1) Пример обязательных полей:

  • timestamp
  • level
  • service (например, payments)
  • version
  • traceId
  • correlationId (или одно из них, если у вас единый идентификатор)
  • event (например, payment.capture_failed)
  • paymentId/orderId (как ключевой контекст)
  • 2) Пример SLI/SLO:

  • SLI: доля успешных POST /orders (без 5xx). SLO: 99.9% за 7 дней.
  • SLI: p95 латентности GET /orders/{id}. SLO: p95 < 200 мс за 7 дней.
  • SLI: “время до появления заказа в read-model профиля” (если есть проекции). SLO: 99% заказов появляются < 5 секунд.
  • 3) Спаны и атрибуты:

  • Спаны: входящий HTTP в Orders; исходящий вызов Orders->Payments; входящий HTTP в Payments; вызов Payments->Provider; (опционально) операции БД и публикация/обработка сообщений как отдельные спаны.
  • Атрибуты: orderId, paymentProvider (или providerRequestId). Дополнительно полезны http.status_code и error.type.
  • 4) Пример пайплайна:

  • Build образа с тегом версии.
  • SAST/линт + проверка зависимостей.
  • Unit tests.
  • Проверка контракта OpenAPI (если фиксируете) и/или CDC.
  • Проверка схем событий (совместимость/версия).
  • Интеграционные тесты с поднятыми зависимостями.
  • Deploy в stage (Kubernetes).
  • Автопроверки метрик/здоровья (ошибки, p95, readiness).
  • Deploy в prod (canary или rolling) + автоматический rollback при деградации.
  • 5) Пример выбора: canary. Аргументы: платежи критичны, ошибка дорого стоит, поэтому лучше сначала направить малую долю трафика на новую версию и сравнить метрики (ошибки, p95/p99, таймауты к провайдеру, ретраи, DLQ). Откат должен быть быстрым (возврат на предыдущий образ) и автоматизированным при нарушении порогов. Rolling тоже возможен, но canary снижает радиус поражения. Blue/green хорош для быстрых переключений, но требует больше ресурсов и дисциплины управления двумя средами.

    </details>