Микросервисы¶
«Перепишем на микросервисы» — самая дорогая и долгая фраза в индустрии. Здесь разбираемся, когда это правильно, какие паттерны спасают от хаоса, и почему монолит — это не диагноз.
Монолит vs микросервисы¶
🧩 Простыми словами. Монолит — один сервис, одна БД, одна команда деплоит. Микросервисы — много мелких сервисов, у каждого своя БД, разные команды деплоят независимо.
⚙️ Главный миф. «Микросервисы быстрее и проще». Нет: они дешевле в большой команде, но дороже в маленькой.
| Монолит | Микросервисы | |
|---|---|---|
| Сложность дев-окружения | Низкая | Высокая (docker-compose с 20 сервисов) |
| Latency между модулями | Function call | Network call (в 1000x) |
| Транзакции | ACID-локально | Saga / eventual |
| Деплой | Одним куском | Независимо каждый |
| Команды | Одна или мало | Много автономных |
| Мониторинг | Простой | Сложный (distributed tracing) |
| Скалирование | Целиком | По компонентам |
📌 Правило большого пальца.
- < 10 человек в команде → монолит (или хорошо модуляризованный «модульный монолит»).
- 50+ человек, разные домены → микросервисы оправданы.
- 10–50 → надо думать. Часто компромисс: 2–3 сервиса по доменам, не 20.
Тайминги распилки монолита¶
Так распиливают монолит на сервисы (в годах для команды 30+):
- Y1: модуляризация внутри монолита. Чёткие границы пакетов, только через интерфейсы. Подготовка БД (отдельные схемы по доменам).
- Y2: первый strangler. Рядом с монолитом ставим новый сервис на одну функцию. Front пробрасывает запросы туда. Монолит остаётся источником истины, новый сервис — асинхронный consumer.
- Y3+: следующие сервисы. Каждый раз — новый домен с явными границами.
- «Никогда не закончим». Полностью пилить монолит обычно не нужно — сложные ядерные части остаются.
Service decomposition: где резать¶
🧩 Простыми словами. Резать по бизнес-границам, не по техническим.
✅ Хорошие сервисы:
users(auth, профили).orders(заказы, статусы).payments(платежи, refund'ы).inventory(склад, резервирование).notifications(email/SMS/push).
❌ Плохие сервисы:
database-service— технический срез, не бизнес.validation-service— то же.helpers— антипаттерн.
📐 Метрика хорошей границы. Команда сервиса может выкатить новую фичу без синхронизации с другими командами 80% времени. Если каждый раз нужно ждать 3 другие команды — границы плохие.
Паттерны микросервисов¶
API Gateway¶
🧩 Простыми словами. Один вход для клиентов. Разруливает auth, rate-limit, TLS, маршрутизацию. Клиент не знает про 20 внутренних сервисов.
graph LR
Mobile --> GW[API Gateway<br/>Kong/Tyk/envoy]
Web --> GW
GW --> Auth[users]
GW --> Cat[catalog]
GW --> Cart[cart]
GW --> Pay[payments]
✅ Централизованная auth, rate-limit, метрики. Скрывает внутреннюю топологию. ❌ SPOF (нужно 2+ инстанса). Может стать «толстым» — антипаттерн «smart pipes, dumb services» (наоборот, должно быть).
BFF (Backend For Frontend)¶
Отдельный API для каждого клиента (mobile / web / partner). Каждый BFF — свой «фасад» под конкретные нужды. Mobile хочет компактные ответы, web — больше данных. Не пытайся сделать «универсальный» эндпоинт.
graph LR
Mobile --> BFFm[Mobile BFF]
Web --> BFFw[Web BFF]
Partner --> BFFp[Partner API]
BFFm --> Users
BFFm --> Cat
BFFw --> Users
BFFw --> Cat
BFFw --> Reviews
BFFp --> Cat
Sidecar¶
Дополнительный контейнер рядом с основным, выполняет cross-cutting задачи: proxy (envoy в service mesh), log shipper, secrets fetcher.
✅ Не загрязняет код приложения. Можно обновлять независимо. ❌ Дополнительный network hop в localhost (обычно не больно).
Service mesh¶
Слой сетевой инфраструктуры между сервисами. Istio, Linkerd, Consul Connect. Делает: mTLS, retry, circuit breaker, traffic routing, observability — без изменений в коде сервисов.
graph LR
A[Service A] --> SCa[Sidecar A]
SCa -.mTLS.-> SCb[Sidecar B]
SCb --> B[Service B]
SCa --> CtrlPlane[Control plane:<br/>policy, certs]
SCb --> CtrlPlane
✅ Унифицированные cross-cutting concerns. mTLS из коробки. ❌ Сложно эксплуатировать. Latency overhead. Не для всех команд.
Saga (распределённые транзакции)¶
🧩 Простыми словами. Тебе нужно: создать заказ + списать деньги + списать со склада. Три сервиса, три БД. Двухфазный коммит — не вариант (медленный, хрупкий). Saga: серия локальных транзакций + компенсации при сбоях.
Orchestration¶
Центральный orchestrator знает весь бизнес-процесс и командует.
sequenceDiagram
Orch->>Order: createOrder
Order-->>Orch: ok
Orch->>Payment: charge
Payment-->>Orch: failed
Orch->>Order: cancelOrder (compensation)
✅ Логика в одном месте, легко рассуждать. ❌ Smart pipe / SPOF. Координатор должен быть надёжен.
Choreography¶
Сервисы реагируют на события через Kafka, нет центра.
sequenceDiagram
Order->>Bus: OrderCreated
Payment->>Bus: PaymentFailed
Order-->>Order: cancel (subscribed to PaymentFailed)
✅ Loose coupling, проще scale. ❌ Логика размазана по сервисам — сложно отследить «что происходит с заказом».
📌 Когда что. Простой 2–3 шага flow → orchestration. Длинные пайплайны с реакцией нескольких систем → choreography.
CQRS¶
🧩 Простыми словами. Командный (Write) путь и Read путь — разные. Запись идёт в нормализованную БД с транзакциями. Чтение — в отдельную read model (денормализованная, индексирована под запросы).
graph LR
Cmd[Commands<br/>POST /order] --> WriteSvc
WriteSvc --> WriteDB[(Postgres<br/>normalized)]
WriteSvc --> Bus[Event bus]
Bus --> Projector
Projector --> ReadDB[(Elastic / Mongo<br/>denormalized)]
Query[Queries<br/>GET /orders/me] --> ReadSvc
ReadSvc --> ReadDB
✅ Чтения масштабируются независимо. Сложные join'ы решены при записи. ❌ Eventual consistency для read model. Дублирование данных. Сложнее писать.
Event sourcing¶
🧩 Простыми словами. Не храним «текущее состояние», а храним поток событий. Состояние = свёртка всех событий.
events:
AccountOpened(id=42, owner=Bob)
Deposited(id=42, amount=100)
Deposited(id=42, amount=50)
Withdrawn(id=42, amount=30)
state at any time = fold all events
balance = 100 + 50 - 30 = 120
✅ Полный аудит, time-travel, легко строить новые проекции. ❌ Сложнее, чем кажется (миграция схемы событий, snapshotting). Не для всего.
Service discovery¶
🧩 Простыми словами. Когда у тебя 50 экземпляров payment-service в Kubernetes — как клиент узнаёт их IP'ы?
⚙️ Подходы.
- DNS-based. Kubernetes Service выдаёт стабильное имя (
payment.default.svc), внутри которого client-side resolution через kube-proxy / IPVS. Default. - Client-side discovery. Client сам спрашивает реестр (Consul / etcd), получает список инстансов, балансирует. Используется в gRPC + xDS.
- Server-side discovery. Через LB / ingress (см. load-balancing.md).
В современном K8s обычно: DNS + Service + Ingress, плюс service mesh для сложных случаев.
🛠 Применение в Go-проекте¶
// gRPC клиент с балансировкой через Consul (или K8s headless service)
conn, err := grpc.Dial(
"consul://consul:8500/payment-service",
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
grpc.WithInsecure(),
)
client := paymentpb.NewPaymentClient(conn)
resp, err := client.Charge(ctx, &paymentpb.ChargeRequest{...})
Сервисы пишут в service-registry (Consul/etcd) или live в Service Kubernetes
(headless для LB на стороне клиента).
🔥 Пример: миграция монолита на микросервисы (strangler)¶
graph TB
subgraph Year1
Mono[Monolith: всё]
end
subgraph Year2
FE[Frontend] --> R[Router]
R -->|/payments| NewPay[New: Payment service]
R -->|остальное| Mono2[Monolith]
NewPay --> Mono2
end
subgraph Year3
FE2[Frontend] --> R2[Router]
R2 -->|/payments| Pay2[Payment]
R2 -->|/orders| Ord[Orders]
R2 -->|/users| Mono3[Monolith: остаток]
end
Постепенная замена, не «большой переписывание». Каждый новый сервис — incremental value, можно отменить если упёрлись.
❌ Типичные ошибки¶
- Микросервисы под малую команду. 5 человек поддерживают 15 сервисов → никто ничего не успевает.
- Распределённый монолит. Сервисы есть, но они синхронно зовут друг друга цепочкой — падает один → падает всё. Хуже монолита.
- Shared database. Несколько сервисов в одну БД → они не сервисы, а модули с network call.
- Не подумали про observability. Без distributed tracing и логов с request_id — debug превращается в ад. См. observability.md.
- Слишком тонкие сервисы. «User-name-service», «User-avatar-service» — это не границы домена.
🤖 Что спрашивает AI-ментор¶
- Когда стоит пилить монолит на микросервисы, а когда нет?
- Чем отличается orchestration от choreography в saga?
- Что такое API Gateway и в чём его отличие от service mesh?
- Зачем нужны BFF (Backend For Frontend)?
- Что такое CQRS и какую проблему решает?
📊 Уровни глубины¶
L1. Знаешь термин «микросервисы». Видел монолит и видел Kubernetes. Понимаешь, что есть API gateway.
L2. Различаешь паттерны (BFF, sidecar, saga). Понимаешь tradeoff orchestration vs choreography. Использовал gRPC между сервисами, делал rolling deploy.
L3. Распиливал монолит strangler-методом в проде. Имплементировал saga с компенсациями. Использовал service mesh (Istio/Linkerd). Знаешь когда CQRS оправдан, а когда over-engineering. Спорил с командой про границы сервисов и победил.
Полезные материалы¶
📚 Статьи
- microservices.io — каталог паттернов (EN) — канонический справочник от Chris Richardson: Saga, CQRS, API Gateway, Sidecar, Database per Service. С плюсами, минусами и форс-мажорами. Если нужно вспомнить «как правильно называется паттерн X» — это сюда.
📝 Подумай¶
- У тебя монолит на 15 разработчиков, RPS 1000. Стоит ли начинать миграцию на микросервисы?
- Опиши saga для checkout (order + payment + inventory). Orchestration или choreography? Что компенсируешь?
- Зачем нужен BFF, если есть API gateway?
Ответ
- Зависит. Если команда не справляется с релизами и есть явные домены — можно начать модуляризировать монолит и выделить 1–2 сервиса (strangler). Но цель «каждой команде свой сервис» при 15 человек — преждевременно. Скорее «модульный монолит» с чёткими границами пакетов.
- Шаги: createOrder → reserveInventory → chargePayment → confirmOrder. Компенсации: refund (для payment), releaseInventory (для inventory), cancelOrder. Для 3-шагового последовательного процесса — orchestration проще. Если сервисов много и есть много reactive flows — choreography.
- API gateway — общий, для всех клиентов. BFF — специализированный API под конкретный клиент (mobile / web). Один gateway не способен дать идеальные ответы каждому клиенту: либо избыточно (mobile), либо мало (web). BFF разруливает per-client.
Дальше: resilience.md — как не падать, даже когда падают другие.