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

Микросервисы

«Перепишем на микросервисы» — самая дорогая и долгая фраза в индустрии. Здесь разбираемся, когда это правильно, какие паттерны спасают от хаоса, и почему монолит — это не диагноз.

Монолит vs микросервисы

🧩 Простыми словами. Монолит — один сервис, одна БД, одна команда деплоит. Микросервисы — много мелких сервисов, у каждого своя БД, разные команды деплоят независимо.

⚙️ Главный миф. «Микросервисы быстрее и проще». Нет: они дешевле в большой команде, но дороже в маленькой.

Монолит Микросервисы
Сложность дев-окружения Низкая Высокая (docker-compose с 20 сервисов)
Latency между модулями Function call Network call (в 1000x)
Транзакции ACID-локально Saga / eventual
Деплой Одним куском Независимо каждый
Команды Одна или мало Много автономных
Мониторинг Простой Сложный (distributed tracing)
Скалирование Целиком По компонентам

📌 Правило большого пальца.

  • < 10 человек в команде → монолит (или хорошо модуляризованный «модульный монолит»).
  • 50+ человек, разные домены → микросервисы оправданы.
  • 10–50 → надо думать. Часто компромисс: 2–3 сервиса по доменам, не 20.

Тайминги распилки монолита

Так распиливают монолит на сервисы (в годах для команды 30+):

  1. Y1: модуляризация внутри монолита. Чёткие границы пакетов, только через интерфейсы. Подготовка БД (отдельные схемы по доменам).
  2. Y2: первый strangler. Рядом с монолитом ставим новый сервис на одну функцию. Front пробрасывает запросы туда. Монолит остаётся источником истины, новый сервис — асинхронный consumer.
  3. Y3+: следующие сервисы. Каждый раз — новый домен с явными границами.
  4. «Никогда не закончим». Полностью пилить монолит обычно не нужно — сложные ядерные части остаются.

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» — это сюда.

📝 Подумай

  1. У тебя монолит на 15 разработчиков, RPS 1000. Стоит ли начинать миграцию на микросервисы?
  2. Опиши saga для checkout (order + payment + inventory). Orchestration или choreography? Что компенсируешь?
  3. Зачем нужен BFF, если есть API gateway?
Ответ
  1. Зависит. Если команда не справляется с релизами и есть явные домены — можно начать модуляризировать монолит и выделить 1–2 сервиса (strangler). Но цель «каждой команде свой сервис» при 15 человек — преждевременно. Скорее «модульный монолит» с чёткими границами пакетов.
  2. Шаги: createOrder → reserveInventory → chargePayment → confirmOrder. Компенсации: refund (для payment), releaseInventory (для inventory), cancelOrder. Для 3-шагового последовательного процесса — orchestration проще. Если сервисов много и есть много reactive flows — choreography.
  3. API gateway — общий, для всех клиентов. BFF — специализированный API под конкретный клиент (mobile / web). Один gateway не способен дать идеальные ответы каждому клиенту: либо избыточно (mobile), либо мало (web). BFF разруливает per-client.

Дальше: resilience.md — как не падать, даже когда падают другие.