Перейти к содержанию

Тестирование и анти-паттерны

Тестирование

Распределёнка ломается не там, где зелёный юнит-тест. Юниты проходят, сервис собирается, а потом два сервиса не сходятся в формате одного поля — и узнаёшь об этом в проде. Главная боль микросервисов — не логика внутри сервиса, а интеграция между сервисами. Вокруг этого и строишь тестовую стратегию.

Пирамида тестов

Идея простая: чем выше тест, тем он медленнее, дороже и хрупче. Значит, основную массу проверок держишь внизу, а наверху — минимум.

        /\
       /  \      e2e          мало, дорого, флака
      /----\
     /      \    slice /      средний слой,
    /        \   integration  реальные зависимости
   /----------\
  /            \ unit          много, быстро, дёшево
 /--------------\
  • unit — чистая логика без сети и БД. Миллисекунды, тысячи штук, гоняются на каждый коммит. Здесь проверяешь бизнес-правила, а не интеграцию.
  • slice / integration — кусок сервиса с реальными зависимостями: своя БД, брокер, кэш. Тут ловишь то, что моки скрывают: реальный SQL, реальную сериализацию в Kafka.
  • e2e — весь сценарий через несколько поднятых сервисов. Даёт уверенность в happy-path, но медленный, хрупкий и дорогой в поддержке. Держишь штук пять на критичные потоки, не больше.

Грабли: перевернуть пирамиду в «песочные часы» или «рожок мороженого» — когда e2e много, а unit мало. Тогда CI идёт полчаса, падает по флаке, и никто не верит красному прогону.


Testcontainers для slice-тестов

Моки БД и брокера врут. In-memory H2 вместо Postgres не воспроизводит специфику диалекта, а замоканный producer не покажет, что схема сообщения поехала.

Testcontainers — это библиотека, которая поднимает реальные зависимости в Docker на время теста и гасит их после.

Что делает: - стартует контейнер с реальным Postgres, Kafka, Redis на случайном порту; - отдаёт тесту connection string уже поднятого инстанса; - сносит контейнер в teardown — никакого мусора между прогонами.

func TestOrderRepo(t *testing.T) {
    ctx := context.Background()
    pg, err := postgres.Run(ctx, "postgres:16-alpine",
        postgres.WithDatabase("orders"),
        postgres.WithUsername("test"),
        postgres.WithPassword("test"),
    )
    require.NoError(t, err)
    defer pg.Terminate(ctx)

    dsn, _ := pg.ConnectionString(ctx, "sslmode=disable")
    repo := NewOrderRepo(mustOpen(dsn))
    // дальше — реальный SQL против реального Postgres
}

Плюсы: тест видит ровно ту БД, что в проде, вместе с диалектом, индексами и миграциями. Минусы: нужен Docker в CI, прогон в секундах, не в миллисекундах. Поэтому это средний слой пирамиды, а не замена юнитам.


Contract testing: интеграция без поднятия всей системы

Главный вопрос: провайдер выкатил новую версию API — кто из потребителей сломался? e2e ответит, но дорого и поздно. Contract testing отвечает дёшево и в CI каждого сервиса по отдельности.

Consumer-Driven Contracts — это паттерн эволюции сервисов, где ожидания каждого потребителя фиксируются контрактом, а провайдер обязан их поддерживать, чтобы не сломать потребителей. Описал Ian Robinson в 2006 (статья на сайте Фаулера).

Идея простая: контракт пишет не провайдер, а потребитель. Потребитель говорит «я шлю вот такой запрос и жду вот такой ответ». Провайдер у себя в CI проверяет, что всё ещё отдаёт обещанное.

Как это работает по шагам: 1. Потребитель в своих тестах гоняет запросы против мока провайдера и фиксирует ожидания как контракт (Pact пишет его в JSON). 2. Контракт публикуется в общее место (Pact Broker). 3. Провайдер в своём CI проигрывает контракт против реального себя — verification. 4. Провайдер сломал поле — его CI краснеет, до прода не доходит.

Consumer CI                Pact Broker              Provider CI
-----------                -----------              -----------
прогнал тесты  --pact-->   хранит            <--pull-- провёл verification
зафиксировал                контракты                  против реального сервиса
ожидания                    + результаты               красный, если сломал контракт

Чем отличается от схемы провайдера: не провайдер диктует «вот мой OpenAPI», а потребители диктуют «вот что нам реально нужно». Поля, которые ни один потребитель не использует, можно менять свободно — контракт их не покрывает.

Инструменты: Pact (язык-агностик, через broker) и Spring Cloud Contract (JVM-мир).

Грабли: - путать Pact с самим паттерном — Pact лишь одна реализация Consumer-Driven Contracts; - считать, что contract testing полностью заменяет e2e — он проверяет совместимость пары consumer-provider, но не сквозной бизнес-сценарий через пять сервисов; - для async-коммуникации контракт описывает не запрос-ответ, а формат сообщения в топике — это тоже зона contract testing, не только REST. Детали протоколов — в Communication & API Style.

Note

Schema-evolution через Schema Registry (Avro/Protobuf, backward/forward compatibility) — это контроль совместимости на уровне wire-формата. Близкая идея, но другой слой: registry проверяет схему сообщения, contract testing — взаимные ожидания пары сервисов.


Migration tests в CI

Database per Service означает, что у каждого сервиса своя схема и свои миграции. Сломанная миграция кладёт деплой. Поэтому миграции прогоняют в CI, а не на проде в момент выката.

Что делать: - поднимаешь чистую БД в Docker (те же Testcontainers); - прогоняешь все миграции Flyway или Liquibase от нуля; - проверяешь, что схема собралась и что новые миграции накатываются поверх старых.

Зачем гонять от нуля: ловишь миграцию, которая работает только на твоей локальной БД с ручными правками, но падает на чистой. Грубо говоря, CI воспроизводит то, что случится на свежем инстансе.

Грабли: тестировать только «вперёд». Если есть rollback-сценарий — проверяй и откат, иначе в инцидент откатиться будет нечем.


Chaos Engineering

Юнит и contract проверяют логику и совместимость. Но в проде сервис падает по другим причинам: инстанс умер, сеть подвисла, зависимость отвечает за 30 секунд вместо 30 миллисекунд. Это тестируют отдельно.

Chaos Engineering — это дисциплина, где намеренно вносишь реалистичные сбои, чтобы набрать уверенность, что система переживёт турбулентность в проде. Netflix запустил Chaos Monkey в 2011, принципы оформили в 2015.

Идея простая: не ждать, пока инстанс умрёт сам в 3 ночи, а убить его днём, когда команда смотрит. Переживает — хорошо. Нет — чинишь, пока не больно.

Что ломают: - убивают инстансы (Chaos Monkey в оригинале — именно это); - режут или замедляют сеть между сервисами; - роняют зависимость и смотрят, сработали ли circuit breaker, retry, timeout.

Важный нюанс: это не «рандомно ломать прод». Канонически — контролируемый эксперимент с гипотезой. Определяешь steady state (как система выглядит здоровой), формулируешь гипотезу («при смерти одного инстанса latency не вырастет»), ограничиваешь blast radius и проверяешь. Chaos Monkey — один инструмент, а не вся дисциплина.

Связь с устойчивостью: chaos проверяет ровно те паттерны, что описаны в Macro-Architecture — circuit breaker, bulkhead, retry. Без них chaos просто роняет систему. С ними — подтверждает, что защита работает.


Грабли тестирования распределёнки

Симптом Что не так Что делать
CI идёт полчаса, флака перевёрнутая пирамида, всё через e2e основную массу — в unit и slice, e2e только на критичные потоки
«у нас интеграция проверяется в проде» нет contract testing consumer-driven contracts в CI каждого сервиса
деплой падает на миграции миграции не гоняются в CI Flyway/Liquibase от нуля на чистой БД в каждом прогоне
моки БД зелёные, прод красный in-memory вместо реальной БД Testcontainers с реальным образом
инцидент от смерти инстанса устойчивость не проверена chaos-эксперимент с гипотезой и blast radius

Если коротко: основную массу проверок держи внизу пирамиды, интеграцию лови contract-тестами до прода, а устойчивость — chaos-экспериментами, а не в момент реального инцидента.


Анти-паттерны

Микросервисы — это не про много мелких сервисов. Это про автономию: independent deployability, своя БД, своя команда. Большинство анти-паттернов — места, где автономию незаметно потеряли. Снаружи топология выглядит как микросервисы, внутри связность как у монолита, но без его плюсов.

Дальше — частые грабли. Для каждого симптом и чем лечить.


Distributed Monolith

Сервисы нарезаны, но деплоятся и ломаются как единое целое. Худший из миров: сетевые задержки распределённой системы плюс жёсткая связность монолита. Детальный разбор и стратегия выхода — в Macro-Architecture.

Симптом: - релиз одного сервиса требует синхронного релиза трёх соседей, иначе ломается контракт. - общая БД, в которую несколько сервисов лезут напрямую. - любой пользовательский запрос идёт по sync-цепочке через половину сервисов. - падение одного сервиса роняет фичу, которая логически от него не зависит.

Чем лечить: - режь по business capability, не по таблицам (см. Macro-Architecture). - каждому сервису — своя приватная БД (Database per Service), доступ к чужим данным только через API. - разрывай sync-цепочки async-событиями там, где консистентность может быть eventual.


Shared database

Несколько сервисов пишут и читают одну схему. Это корень почти всех остальных проблем — из общей схемы distributed monolith вырастает сам собой.

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

Чем лечить: - Database per Service — приватная схема на сервис, чужой код в неё не ходит. - нужны чужие данные синхронно — спрашивай через API. Нужны асинхронно — подпишись на события. - разносить общую БД больно, но раньше начнёшь — дешевле. Часто помогает выделять по одной таблице за раз через Strangler Fig.

Note

Один физический инстанс БД на несколько сервисов — это ещё не shared database, если у каждого своя схема и никто не лезет в чужую. Database per Service — про логическую изоляцию владения, а не про обязательный отдельный сервер. Анти-паттерн — общая схема и общие таблицы.


Chatty communication и нано-сервисы

Сервисы нарезаны слишком мелко. Один бизнес-запрос разворачивается в десятки сетевых хопов, каждый сервис делает почти ничего.

Симптом: - чтобы собрать одну страницу, gateway дёргает 15 сервисов. - сервисы постоянно ходят друг к другу за мелкими кусочками данных в горячем пути. - latency складывается из сетевых round-trip'ов, а не из реальной работы.

плохо: один запрос = N синхронных хопов

client -> A -> B -> C -> D -> E   (latency = сумма всех, отказ любого = отказ всего)

хорошо: укрупнить границы + меньше хопов в горячем пути

client -> A ----> B
              \-> C   (A держит то, что нужно часто; остальное async)

Чем лечить: - укрупни границы: сервис владеет capability целиком, а не одним методом. - то, что нужно в горячем пути часто, держи локально (своя проекция данных, read model). - мелкие чтения через сеть заменяй на data ownership или кэш на стороне потребителя.

Note

Граница сервиса — про связность данных и команду-владельца, а не про размер кода. «Микро» — не про строки. Сервис на 50 тысяч строк с одним владельцем нормальнее, чем десять «нано» вокруг одной сущности.


Длинная sync-цепочка

Запрос идёт A -> B -> C -> D синхронно, каждый блокирующе ждёт следующего. Latency и вероятности отказа перемножаются — цепочка надёжна ровно настолько, насколько надёжно произведение звеньев.

Симптом: - p99 хвост одного звена раздувает p99 всей цепочки. - timeout или деградация в конце цепочки каскадом бьёт по всем выше. - доступность цепочки = произведение доступностей. Четыре звена по 99.9% дают уже ~99.6%.

Чем лечить: - где консистентность может быть eventual — заменяй синхронный вызов на async-событие или Saga. - обязательные синхронные вызовы оборачивай в Circuit Breaker, Timeout и bulkhead, чтобы отказ не каскадировал. - укрупняй границы, чтобы цепочка была короче. Детали протоколов sync-вызова — в Communication & API Style.


Entity services

Сервис построен вокруг таблицы или сущности (UserService, OrderRowService), а не вокруг бизнес-capability. CRUD-обёртка над строкой, у которой нет своего поведения.

Чем плохо: - любая реальная операция размазана по нескольким entity-сервисам и требует их оркестрации. - бизнес-логика утекает наружу — в gateway или в вызывающий код, потому что в сервисе её нет. - получается тот же chatty-антипаттерн: чтобы сделать заказ, дёргаешь пять сущностных сервисов.

Чем лечить: - режь по business capability: «оформление заказа», а не «строка в таблице orders». - сервис должен владеть и данными, и поведением над ними — инвариант живёт внутри. - если сервис умеет только CRUD и не содержит правил — это, скорее всего, не граница, а таблица.


God gateway

Gateway оброс бизнес-логикой: оркестрация бизнес-процессов, валидация доменных правил, трансформации, которые знают про домен. Получился монолит на входе, через который проходит и от которого зависит всё.

Симптом: - любая новая фича требует правки gateway, и его релизит отдельная команда-бутылочное горлышко. - gateway знает доменные правила сервисов и ломается, когда меняется их логика. - падение gateway = падение всего, и откатывать его страшно.

Чем лечить: - gateway держит только cross-cutting: routing, auth, rate limiting, агрегацию ответов. - бизнес-логику и оркестрацию верни в сервисы, которым она принадлежит. - много разных клиентов с разными нуждами — заводи Backends for Frontends, а не раздувай один gateway. - роль и границы gateway/BFF подробно — в Communication & API Style.


Shared доменная библиотека

Сервисы тащат общую библиотеку с доменными моделями и бизнес-логикой. Правишь её — и нужно передеплоить всех, кто её подключает. Independent deployability убита одним import.

Чем плохо: - общий доменный код связывает сервисы по compile-time. Это shared database, только в другом слое. - версионный ад: либо все на одной версии (передеплой пачкой), либо зоопарк несовместимых версий. - доменная модель одного сервиса протекает в другой — границы bounded context размываются.

Чем лечить: - доменную модель не шарь — у каждого сервиса своя, даже если поначалу похожи. Дублирование здесь дешевле связности. - общими делай только технические, доменно-нейтральные вещи: логирование, трейсинг, сериализация. - интеграцию строй через контракты на границе (события, API), а не через общий код. Чужую модель на входе изолируй Anti-Corruption Layer.

Note

Тест простой: можешь ли выкатить новую версию библиотеки, не трогая потребителей? Для технической утилиты — да. Для доменной модели — почти никогда, и поэтому её нельзя шарить.


Если коротко:

Симптом Анти-паттерн Чем лечить
Релиз пачкой, общая БД, sync-цепочки Distributed Monolith автономия: своя БД, async-границы (Macro-Architecture)
Несколько сервисов в одной схеме Shared database Database per Service, доступ через API
Один запрос = десятки хопов Chatty / нано-сервисы укрупнить границы, локальная read model
A->B->C->D блокирующе Длинная sync-цепочка async/Saga, Circuit Breaker, Timeout
Сервис на таблицу Entity services резать по business capability
Бизнес-логика в gateway God gateway только cross-cutting, логику в сервисы
Общий код с доменом Shared доменная либа шарить только технику, домен дублировать

Чек-лист и если коротко

Прежде чем дробить — проверяешь готовность. Не по хайпу, а по факту. Если на большинство пунктов честный ответ «нет» — рано. Сиди на modular monolith (Macro-Architecture), он дешевле в разы.

Прежде чем дробить

  • Знаешь bounded contexts? — Не папки в проекте, а границы модели, внутри которых термин значит одно (Bounded Context, Eric Evans, 2003). Если границы размыты — нарежешь сервисы по случайным линиям и получишь distributed monolith (Macro-Architecture). Сначала subdomains и context map, потом код.
  • БД развяжешь? — Database per Service означает приватное владение данными: чужой сервис лезет только через API, не в твою схему. Это логическая изоляция, не обязательно отдельный инстанс — допустимы schema-per-service или table-per-service. Развязка данных обычно больнее развязки кода.
  • Есть CI/CD на каждый сервис? — Независимый деплой — это и есть смысл микросервисов, а не размер. Если катишь всё одним релизом — дробление не дало ничего, только добавило сетевых вызовов.
  • Есть observability? — Distributed tracing, метрики, централизованные логи (OpenTelemetry). Без них упавший запрос через пять сервисов — слепой дебаг по логам наугад.
  • Есть on-call и runbooks? — Микросервисы превращают баг в инцидент: отказ одного бьёт по соседям. Нужен дежурный, алерты, процедуры. Если падение чинят «утром придёт автор» — рано.
  • Команд хватает? — Грубо говоря, нужна команда-владелец на сервис (или на кучку близких). Если одна команда тащит двадцать сервисов — это не автономия, это распределённый монолит на их плечах.

Эвристика: если на CI/CD, observability и владение данными ответ «нет» — дроби монолит на модули внутри одного процесса. Микросервисы — инструмент организационный, не технический. Окупаются, когда несколько команд начинают мешать друг другу в одном репозитории.


Must-know паттерны

Минимальный набор, который держишь в голове. Детали каждого — в основных секциях этого файла, здесь — карта, чтобы не забыть, что вообще существует.

Вход и маршрутизация: - API Gateway — единая логическая точка входа, маршрутизация, cross-cutting (auth, rate limiting). Точка входа логическая, не обязательно один физический инстанс. BFF — вариант: свой gateway под каждый тип клиента (mobile/web). - Service discovery — как клиент или роутер находит инстансы сервиса через service registry. Часто это уже даёт платформа (DNS в Kubernetes), отдельный registry не всегда нужен.

Данные и согласованность: - Saga — последовательность локальных транзакций с компенсациями вместо распределённой транзакции (Garcia-Molina и Salem, 1987). Даёт eventual consistency, не ACID, и не изолирует промежуточные состояния. Choreography (через события) или orchestration (явный координатор). - Transactional outbox + CDC — атомарно пишешь данные и событие в одну БД, relay (например Debezium) дочитывает outbox и публикует в broker. Лечит dual-write problem. Гарантия at-least-once, не exactly-once. - Idempotent consumer — обработка повтора даёт тот же результат. Обязательная пара к at-least-once: дедупликацию делает потребитель, а не магия брокера. - CQRS — раздельные write- и read-модели (Greg Young, ~2010). Не требует event sourcing и не обязан жить в двух БД. Берёшь, когда чтения и записи реально расходятся по нагрузке и форме.

Устойчивость: - Timeout — верхняя граница ожидания ответа. Фундамент, на котором держатся остальные паттерны. Дефолтные (часто бесконечные) таймауты библиотек — частые грабли. - Retry с jitter — повтор с экспоненциальной задержкой и рандомом. Без jitter получаешь синхронный thundering herd. Только для идемпотентных операций, с retry budget. - Circuit breaker — перестаёт долбить упавшую зависимость, fail fast на cooldown (Michael Nygard, 2007). Смысл — STOP звонить, а не повторять. Не путай с retry. - Bulkhead — изоляция ресурсов (отдельные пулы на зависимость), чтобы один отказ не утопил весь сервис. Про blast radius, не про детект сбоя.

Эволюция и релизы: - Contract testing — consumer-driven contracts (Ian Robinson, 2006; Pact — одна из реализаций). Ловит ломающие изменения API до прода, дешевле полного end-to-end. - Schema registry — версионирование и проверка совместимости схем событий (Avro/Protobuf). Отдельный компонент (изначально Confluent), не часть Kafka. - Strangler fig — постепенное обрастание легаси новым кодом с перехватом функций (Martin Fowler, 2004). Не big-bang. - Canary / blue-green — canary льёт долю трафика на новую версию и смотрит метрики; blue-green переключает весь трафик между двумя средами разом. Feature flags — отвязать релиз от деплоя.


Если коротко

Симптом или задача слева, паттерн справа. Не догма, а отправная точка — дальше думаешь по контексту.

Симптом / задача Паттерн
Клиент дёргает десяток сервисов напрямую, знает всю топологию API Gateway, при разных клиентах — BFF
Mobile и web тянут из одного API разное, мешают друг другу Backends for Frontends
Инстансы сервиса плавают по адресам, хардкодить IP нельзя Service discovery (часто DNS платформы)
Бизнес-операция трогает несколько сервисов, 2PC не вариант Saga (choreography или orchestration)
Надо атомарно сохранить данные и отправить событие Transactional outbox + CDC
Сообщения иногда приходят дважды Idempotent consumer
Сообщение не обработалось и теряется молча Dead letter queue
Тяжёлые чтения душат write-нагрузку, формы запросов расходятся CQRS, при join через сервисы — API composition
Зависимость тормозит и подвешивает твои потоки Timeout (сначала), потом circuit breaker
Падающий сервис тянет за собой соседей Circuit breaker + bulkhead
Ретраи синхронизируются и бьют волной Retry с exponential backoff и jitter
Один шумный клиент выжирает мощность сервиса Rate limiting (не путать с backpressure)
Быстрый producer заваливает медленного consumer Backpressure
Боишься сломать потребителей при смене API Consumer-driven contracts (Pact)
События меняют формат, нужна безопасная эволюция Schema registry + Avro/Protobuf
Пилишь легаси-монолит, big-bang страшен Strangler fig
Катишь рискованную версию, нужен быстрый откат Canary (доля трафика) или blue-green (переключение)
Релиз и деплой надо развязать Feature flags
Retry/mTLS/tracing хочется убрать из кода приложения Service mesh (sidecar или ambient)
Сетевые отказы в проде ловишь только постфактум Chaos engineering, контролируемо

Источники

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

Доменные границы и декомпозиция

  • Eric Evans — Domain-Driven Design (2003): откуда вообще bounded context и Anti-Corruption Layer. Bounded context — это граница модели в solution space, не синоним service и не синоним subdomain. ACL — защитный context-mapping слой, а не просто DTO-маппер.
  • James Lewis & Martin Fowler — Microservices: a definition of this new architectural term (martinfowler.com, март 2014): каноническое описание стиля. Главное в нём — independent deployability и организация вокруг business capability, а не «маленький размер». «Micro» не про строки кода. Термин обсуждали и раньше (workshop ~2011) — Lewis и Fowler его не изобрели.
  • Sam Newman — Building Microservices (O'Reilly, 2015) и Monolith to Microservices (2019): практика интеграции, разбиения монолита, BFF. Второй — целиком про то, как резать монолит постепенно.

Каталог паттернов

  • Chris Richardson — microservices.io и Microservices Patterns (Manning, 2018): рабочая лошадка. Отсюда database per service, saga, API composition, transactional outbox, idempotent consumer, API gateway, service discovery. Каталог живой — у части паттернов нет жёсткого «года».
  • Gregor Hohpe & Bobby Woolf — Enterprise Integration Patterns (Addison-Wesley, 2003): фундамент messaging. Dead letter channel, маршрутизация, паттерны обмена сообщениями. Старше микросервисов, но именно отсюда растут очереди и каналы, которыми они общаются.

Данные, транзакции, согласованность

  • Hector Garcia-Molina & Kenneth Salem — Sagas, ACM SIGMOD (1987): первоисточник saga. Важно: изначально решала long-lived transactions внутри одной СУБД, не распределёнку. Даёт eventual consistency без изоляции, а не ACID-атомарность — индустрия часто это забывает.
  • Greg Young — CQRS (~2010): разделение write-модели и read-модели. Строится на command-query separation Бертрана Мейера. CQRS не требует event sourcing и не обязан жить на двух базах — частое искажение.
  • Jim Gray — Notes on Data Base Operating Systems (1978): формализация two-phase commit. 2PC даёт атомарность решения о коммите, но блокирует: упал координатор после yes-голосов — участники висят с захваченными locks. Поэтому в микросервисах берут saga, а не потому что 2PC «неправильный».

Устойчивость и отказы

  • Michael Nygard — Release It! (Pragmatic Bookshelf, 2007): circuit breaker, bulkhead, timeout как stability patterns. Circuit breaker — про то, чтобы перестать звонить упавшей зависимости, а не про retry. Bulkhead — изоляция ресурсов, ограничение blast radius. Hystrix популяризировал позже — но придумал Nygard.
  • Marc Brooker (AWS) — Exponential Backoff And Jitter (2015): почему к backoff нужен ещё и jitter. Без jitter retry синхронизируются и бьют залпом (thundering herd). Сам backoff старше — растёт из Ethernet/CSMA.

Эволюция контрактов

  • Ian Robinson — Consumer-Driven Contracts: A Service Evolution Pattern (martinfowler.com, 2006): ожидания потребителя как контракт, который ограничивает provider. Pact — это одна реализация паттерна, а не сам паттерн. И это consumer-defined ожидания, не provider-defined спека.

Эксплуатация и развёртывание

  • Adam Wiggins — The Twelve-Factor App (12factor.net, 2011): методология для cloud-portable SaaS. Не про контейнеры и не про микросервисы как таковые — старше контейнерной эры, но по делу.
  • Martin Fowler — StranglerFigApplication (martinfowler.com, 2004): инкрементальная замена legacy. Новая система обрастает старую по краям, пока ту не выключишь. Не big-bang cutover — в этом весь смысл.

Топологию, выбор стиля и теорию sync-vs-async смотри в Macro-Architecture; детали протоколов — в Communication & API Style.


Главное правило: почти каждый паттерн здесь — это плата за сеть. Saga вместо ACID, circuit breaker вместо стектрейса, tracing вместо одного лога. Не бери микросервисы без причины (дефолт — modular monolith), а взяв — режь по bounded context, развязывай данные и проектируй под отказ, а не под happy path.