Прямой ответ
Микросервисы решают организационные проблемы (независимые деплои команд, изоляция отказов, разный масштаб компонентов) ценой распределённости — и эта цена реальна: сеть ненадёжна, консистентность в итоге eventual, отладка требует трейсинга. Рабочие принципы: границы — по доменным контекстам («заказы», «платежи» — со своими данными), а не по техническим слоям или таблицам; взаимодействия — асинхронные события там, где можно (устойчивость к недоступности), синхронные только где нужен немедленный ответ; никакой общей базы между сервисами (иначе это распределённый монолит с худшим из двух миров). И честное правило: монолит с чистыми модулями почти всегда правильный старт — микросервисы из него вырастают по измеренной боли, а не по статье в блоге.
Границы: самый трудный и самый важный выбор
Критерии хорошей границы: данные принадлежат сервису (чужие таблицы не читаются напрямую — только через API/события); изменения внутри домена не требуют деплоя соседей; команда владеет сервисом целиком (you build it, you run it). Признаки неверного разреза: сервисы постоянно деплоятся парами (граница прошла поперёк связи), половина вызовов — «дай три поля из твоей таблицы» (данные живут не там), distributed monolith (отказ одного роняет всех, деплой — общий поезд). Инструмент мышления — bounded contexts из DDD: «заказ» в продажах и «заказ» в логистике — разные модели, и насильно общий сервис «Всё про заказ» воспроизводит монолит внутри одного сервиса. Резать легче от событий: что происходит в домене (OrderPaid, ShipmentCreated) — то и границы обмена.
Взаимодействия: sync, async и событийность
Синхронные (REST/gRPC — gRPC-практика, дизайн REST): нужны для немедленного ответа пользователю; дисциплина — дедлайны, ретраи идемпотентного, circuit breaker (недолбящий упавший сосед). Асинхронные события (брокер — очередь/топики; в Python-мире — Redis/Kafka/Celery-очереди): источник публикует факт («оплата прошла»), заинтересованные реагируют — связь по времени разорвана, отказ потребителя не блокирует источник. Паттерны поверх событий: event-driven интеграция вместо оркестрации-звонка; outbox (событие пишется в таблицу рядом с транзакцией и публикуется надёжно — иначе «данные записаны, событие потерялось»); идемпотентные потребители (доставка «хотя бы раз» — обработка по event_id с дедупликацией).
Данные и консистентность: без иллюзий
Распределённых транзакций «как в БД» у вас не будет (2PC — не вариант в продуктовых системах); модель — saga: последовательность локальных транзакций с компенсирующими действиями («платёж не прошёл — отменить резерв»), оформленная явно (статусы процесса, таймауты, ручная разборка хвостов). Следствия для дизайна: денормализация разрешена и полезна (копия адреса в заказе — не «дублирование», а изоляция от сервиса адресов); консистентность в итоге — проектируется как норма («статус обновится в течение секунд», а не баг); запросы «список из данных трёх сервисов» — решаются read-моделью (проекция/поисковый индекс, собираемый из событий), а не джойном вызовами в рантайме. «Сначала сделаем общую базу, потом распилим» — почти никогда не «потом»: общая схема цементирует связность намертво.
Observability: цена распределённости
В монолите стектрейс — вся история запроса; в микросервисах запрос живёт в пяти сервисах, и без инструментария отладка невозможна. Минимум с первого дня: корреляционный id (request/trace id проходит все сервисы и попадает в каждый лог — структурные логи: практика); метрики по сервисам (RED: rate/errors/duration — стек Prometheus/Grafana: настройка); трейсинг (OpenTelemetry — спаны по цепочке вызовов); health/liveness/readiness для оркестратора. Дисциплина деплоя: независимость сервисов иллюзорна без feature flags и обратной совместимости контрактов (старый клиент работает с новым сервисом во время раскатки — версионирование и additive-изменения обязательны).
Когда микросервисы — ошибка
Честные критерии «не надо»: одна команда (<10 человек — координация дороже независимости); продукт на стадии поиска формы (границы неизвестны — резать нечего, каждый разрез придётся перекраивать); нагрузка без экстремумов (нечего масштабировать независимо); слабая инфраструктурная база (нет CI/CD, контейнеров, мониторинга — распределённая система без observability неэксплуатируема). Модульный монолит (чёткие модули с интерфейсами, своя схема на модуль при желании) даёт 70% порядка за 10% сложности и распиливается позже по фактическим линиям напряжения — это нормальный эволюционный путь, а не отставание. Антипаттерн-резюме: «микросервисы ради микросервисов» — нанотехнологический distributed monolith с трейсингом, сетевыми задержками и eventual consistency в одном флаконе.
Практический сценарий: выносим первый сервис из монолита
Монолит интернет-магазина: команда из двадцати человек, деплой раз в неделю общим поездом, модуль уведомлений деплоят чаще всех и он чаще всех ломает релизы. Решение не «переписать на микросервисы», а вырезать один домен по измеренной боли. Граница — уведомления (email/SMS/push): у домена свои данные (шаблоны, история отправок), свой профиль нагрузки (пики в маркетинговые рассылки), своя команда из трёх человек. Порядок: сначала внутри монолита модуль приводится в порядок — свои таблицы без внешних FK, общение с остальным кодом только через интерфейс и события; затем события («заказ оплачен») выносятся в брокер — монолит публикует, модуль-пока-ещё-внутри подписывается; и только когда seam проверен событиями, модуль уезжает в отдельный сервис со своей БД (данные мигрируются дампом, старая таблица остаётся read-only на переходный период). Контракт — версия схемы события в брокере, идемпотентные потребители с дедупликацией по event_id. С первого дня — request-id сквозь события, метрики RED, алерт на лаг очереди. Итог: уведомления деплоятся ежедневно независимо, сбой SMTP больше не блокирует релиз заказов, а линия разреза прошла по фактическому шву, найденному за два месяца наблюдения, — не по картинке из статьи про «идеальную архитектуру».
FAQ
С какого числа сервисов начинать? С одного. Второй появляется, когда измерена боль: компонент деплоится/масштабируется/падает не так, как остальное. «Сразу пятнадцать сервисов по числу команд» — ставка на угадывание границ, которая редко выигрывает.
REST или gRPC или события между сервисами? События — для «уведомить о факте» (слабая связность); gRPC — для частых внутренних вызовов со строгим контрактом; REST — для внешнего края и редких вызовов. В зрелой системе сосуществуют все три по ролям.
Как тестировать распределённую систему? Пирамида сохраняется: юнит внутри сервиса; контрактные тесты между сервисами (consumer-driven — pact-подход) вместо хрупких сквозных; сквозные — несколько критичных путей, не все. Локальный подъём соседей — docker-compose/testcontainers (практика).
Общая библиотека на все сервисы — зло? Утилитарная (логирование, метрики, клиенты) — полезна; доменная (модели «общих сущностей») — цементирует связность и превращает деплой библиотеки в координацию всех команд. Доменные модели у каждого сервиса свои.
Источники: Martin Fowler (Microservices, Bounded Context), 12factor.net — ссылки в шапке.