Микросервисы для QA: архитектура, риски и стратегия тестирования
Около 18 мин чтения
Короткий ответ на собеседовании
Микросервисная архитектура делит систему на автономные сервисы вокруг бизнес-возможностей. Сервисы независимо разворачиваются, владеют своими данными и общаются по сети через синхронные API или события. Это помогает независимому масштабированию и работе команд, но добавляет сетевые сбои, eventual consistency, сложную наблюдаемость и распределённые транзакции. Поэтому микросервисы выбирают по контексту, а не потому что они современнее монолита.
Монолит, модульный монолит и микросервисы
Подход
Как устроен
Сильная сторона
Основной риск
Монолит
Один deployable и обычно одна БД
Простота разработки и транзакций
Сильная связанность при росте
Модульный монолит
Один процесс, но строгие доменные модули
Границы без сетевой сложности
Границы могут размыться
Микросервисы
Независимые сервисы и хранилища
Автономность и независимый deploy
Сложность распределённой системы
Модульный монолит часто является разумным промежуточным решением. Он позволяет определить доменные границы, сохранить простые вызовы и общую транзакцию, а при реальной необходимости вынести отдельный модуль в сервис.
Размер сервиса сам по себе не определяет микросервис. Важнее автономность, понятная бизнес-ответственность и независимый жизненный цикл.
Как правильно делить систему
Плохой вариант: деление только по техническим слоям.
Controller service -> Business service -> Repository service
Любая фича проходит через все сервисы и требует совместного deploy. Получается distributed monolith.
Более устойчивый вариант: деление по бизнес-доменам.
Orders
Payments
Inventory
Shipping
Users
Каждый сервис:
отвечает за определённую бизнес-возможность;
скрывает внутреннюю реализацию;
публикует стабильный контракт;
владеет своими данными;
может изменяться и разворачиваться независимо.
DDD называет такие границы bounded contexts. Это не обязательная религия, а полезный способ не смешивать разные модели одного бизнеса.
Database per service
Сервис должен владеть своими данными. Другие сервисы не должны читать его таблицы напрямую.
Почему:
схема БД становится частью внутренней реализации;
прямой доступ связывает deploy разных команд;
сложно контролировать инварианты;
миграция технологии хранения становится почти невозможной.
Интеграция происходит через API или события. Минус очевиден: одна ACID-транзакция больше не покрывает весь бизнес-процесс.
Синхронное и асинхронное взаимодействие
Синхронное
Сервис ждёт ответ по REST или gRPC.
Плюсы:
простой mental model;
немедленный ответ;
удобно для request-response.
Риски:
timeout;
cascading failure;
длинная цепочка зависимостей;
повтор запроса после неопределённого результата;
рост общей latency.
Асинхронное
Сервис публикует событие через Kafka, RabbitMQ или другой broker.
Плюсы:
слабее связанность;
сглаживание всплесков;
независимая обработка;
несколько подписчиков.
Риски:
eventual consistency;
дубли;
нарушение ожидаемого порядка;
сложный поиск причины;
poison messages;
отставание consumer.
Часто система использует оба подхода.
Главная неприятность: частичный успех
В монолите одна транзакция может атомарно обновить заказ и оплату. В микросервисах возможна ситуация:
Order Service создал заказ.
Payment Service списал деньги.
Inventory Service не смог зарезервировать товар.
Нельзя просто откатить одну общую транзакцию. Нужно выполнить компенсирующие действия.
Saga
Saga разбивает бизнес-процесс на локальные транзакции. При ошибке выполняются компенсации.
Два варианта:
Orchestration: координатор говорит сервисам, что делать, и хранит состояние процесса.
Choreography: сервисы реагируют на события друг друга без центрального координатора.
QA проверяет не только happy path, но каждую точку отказа:
ошибка до локальной транзакции;
ошибка после транзакции, но до события;
повтор события;
недоступность compensation;
событие пришло не в ожидаемом порядке;
процесс завис в промежуточном состоянии.
Компенсация не всегда является буквальным rollback. Возврат денег является новой бизнес-операцией с собственной историей.
Transactional Outbox
Классическая проблема:
сервис сохранил заказ в БД;
попытался отправить OrderCreated;
упал до успешной публикации.
БД содержит заказ, но другие сервисы о нём не знают.
Outbox pattern сохраняет бизнес-изменение и запись будущего события в одной локальной транзакции. Отдельный publisher читает outbox и отправляет событие. После сбоя отправка может повториться, поэтому consumer всё равно должен быть идемпотентным.
Идемпотентность
Операция идемпотентна, если повтор с тем же смыслом не создаёт новый бизнес-эффект.
Пример:
первый запрос с idempotency key создаёт платёж;
повтор возвращает результат первого запроса;
второй платёж не создаётся.
QA должен проверить:
одинаковый key и одинаковый payload;
одинаковый key и другой payload;
параллельные повторы;
истечение срока дедупликации;
повтор после timeout;
повтор события другим consumer instance.
CAP без магии
CAP относится к поведению распределённого хранилища при сетевом разделении. В момент partition система не может одновременно гарантировать строгую consistency и полную availability для каждого запроса.
CAP не означает, что система навсегда выбирает только две буквы из трёх. Практический вопрос: что делает конкретная операция во время сетевого разделения и какую согласованность допускает бизнес.
Платёж и рекомендация товара могут иметь разные требования.
Стратегия тестирования микросервисов
1. Unit tests
Проверяют бизнес-правила сервиса быстро и изолированно.
2. Component tests
Запускают сервис с его БД и контролируемыми заменами внешних зависимостей. Это основной слой проверки поведения сервиса.
3. Contract tests
Проверяют совместимость provider и consumers:
обязательные поля;
типы;
коды ошибок;
backward compatibility;
schema evolution для событий.
Контрактный тест не заменяет интеграцию, но ловит несовместимость раньше.
4. Integration tests
Проверяют реальные БД, broker, cache и ключевые соседние сервисы.
5. End-to-end
Оставь небольшой набор критичных бизнес-потоков. Большой E2E-набор в распределённой системе медленный, нестабильный и плохо локализует дефект.
6. Resilience tests
Проверяй:
timeout;
retry и backoff;
circuit breaker;
падение instance;
недоступность broker;
медленный ответ;
потерю соединения;
дубли;
out-of-order;
частичную недоступность.
Наблюдаемость как часть тестируемости
Для одного пользовательского действия нужны:
correlation id во всех сервисах;
distributed trace;
структурированные логи;
метрики latency, error rate и saturation;
consumer lag и queue depth;
audit trail бизнес-операции.
HTTP 500 в gateway почти ничего не объясняет. Trace должен показывать, какой сервис и на каком шаге сломал процесс.
Практический сценарий
Оформление заказа:
Order создаётся в статусе pending.
Payment списывает деньги.
Inventory резервирует товар.
Shipping создаёт доставку.
Order становится confirmed.
Минимальные проверки:
каждый шаг успешен;
Payment недоступен;
Inventory отказал после оплаты;
событие PaymentCompleted пришло дважды;
Shipping обработал событие с задержкой;
compensation возврата временно недоступна;
пользователь повторил запрос после timeout;
Order остаётся наблюдаемым в промежуточном состоянии;
повторный запуск процесса не создаёт второй заказ.
Когда микросервисы оправданы
Смотри на сигналы, а не на моду:
несколько автономных команд мешают друг другу в одном deploy;
части системы имеют сильно разную нагрузку;
нужен независимый release cadence;
отказ некритичного модуля не должен останавливать ключевой процесс;
доменные границы понятны;
команда умеет поддерживать CI/CD, мониторинг и инфраструктуру.
Порог в количестве разработчиков является эвристикой, а не законом. Даже большая система может выигрывать от модульного монолита, если автономность сервисов не окупает операционную сложность.
Вопросы на собеседовании
Чем микросервис отличается от маленького сервиса?
Автономностью, доменной ответственностью, владением данными и независимым deploy, а не числом строк.
Почему нельзя читать БД другого сервиса?
Это создаёт скрытую связанность со схемой и нарушает владение данными. Изменение одного сервиса начинает ломать другие.
Как обеспечить транзакцию между сервисами?
Обычно процесс моделируют через Saga и компенсации. Для надёжной публикации событий применяют Outbox. Выбор зависит от требований.
Зачем нужна идемпотентность?
Сетевой timeout не говорит, выполнилась операция или нет. Retry и at least once delivery создают повторы, которые не должны удваивать бизнес-эффект.
Что тестировать кроме API?
Контракты, события, данные, retries, timeouts, порядок, дубли, компенсации, наблюдаемость и поведение при частичном отказе.
Монолит хуже микросервисов?
Нет. Монолит проще разрабатывать, тестировать и разворачивать на ранних этапах. Микросервисы являются обменом простоты на автономность.
Главное запомнить
У микросервисов нет бесплатной автономности.
Сеть является ненадёжной зависимостью.
Частичный успех важнее happy path.
Saga управляет процессом, Outbox помогает не потерять событие, идемпотентность защищает от дублей.
Контрактные и component tests должны составлять основу, а E2E остаётся компактным.
Correlation id и tracing нужны не только DevOps, но и QA.