Service decomposition, API gateway, event-driven architecture, messaging
Микросервисы — это разбиение системы на независимо разворачиваемые сервисы, у каждого своя зона ответственности и своя база данных. Решение это в первую очередь организационное и операционное, а не способ сделать код быстрее. Вы обмениваете сложность внутри кода на сложность между сервисами: сеть, согласованность данных, распределённые транзакции, наблюдаемость.
💡 Правило (Monolith First): почти всегда начинайте с монолита. Микросервисы окупаются, когда команда выросла настолько, что мешает друг другу в одном коде, или когда части системы нужно масштабировать и деплоить независимо. Преждевременное дробление — самая дорогая архитектурная ошибка.
| Аспект | Монолит | Микросервисы |
|---|---|---|
| Деплой | Один артефакт | Много независимых |
| База данных | Одна, транзакции из коробки | По БД на сервис, нет общих транзакций |
| Согласованность | Сильная (ACID) | Итоговая (eventual) |
| Масштабирование | Целиком | Точечно, по сервису |
| Командная работа | Конфликты в общем коде | Автономия команд |
| Операционная сложность | Низкая | Высокая (сеть, трейсинг, оркестрация) |
| Отладка | Простая (один процесс) | Распределённая, сложная |
⚠️ Распределённый монолит — худший из миров: сервисы разделены физически, но так связаны, что их нельзя деплоить и менять независимо. Возникает при неправильных границах. Если сервисы постоянно ходят друг к другу синхронно за данными — границы проведены неверно.
Сервисы режут не по техническим слоям («сервис БД», «сервис логики»), а по бизнес-способностям (bounded contexts из DDD). Каждый сервис владеет своими данными монопольно.
User Service → своя БД (профили, аутентификация)
Order Service → своя БД (заказы, корзины)
Payment Service → своя БД (транзакции, балансы)
Notification Service → своя БД (шаблоны, история отправок)
⚠️ Золотое правило: никаких общих баз данных и никаких прямых JOIN между сервисами. Если Order-сервис лезет в таблицу users напрямую — это не микросервисы, а монолит с лишней сетью. Данные другого сервиса получают только через его API или события.
Клиенту неудобно знать про десяток адресов сервисов. Gateway — единая точка входа: маршрутизирует запросы, централизует аутентификацию, rate limiting, TLS.
# Kong / Nginx / AWS API Gateway / Traefik
routes:
- path: /api/users/* → user-service:8000
- path: /api/orders/* → order-service:8000
- path: /api/payments/* → payment-service:8000| Функция Gateway | Зачем |
|---|---|
| Маршрутизация | Один внешний адрес → много внутренних сервисов |
| Аутентификация | Проверка токена в одном месте, а не в каждом сервисе |
| Rate limiting | Защита всех сервисов разом |
| Агрегация | Собрать ответ из нескольких сервисов (иногда — отдельный BFF) |
| Способ | Когда | Минусы |
|---|---|---|
| Синхронный (REST/gRPC) | Нужен немедленный ответ | Связанность, каскадные отказы |
| Асинхронный (события через брокер) | Уведомить о факте, не ждать | Сложнее, итоговая согласованность |
💡 Предпочитайте асинхронные события для связи между сервисами. Синхронные цепочки вызовов создают связанность и каскадные сбои: упал Payment — лёг и Order, который его синхронно ждал.
Сервис публикует факт («заказ создан»), не зная, кто его обработает. Подписчики реагируют независимо. Это разрывает связанность.
# Order Service — публикует событие и забывает
producer.send('orders', {
'event': 'OrderCreated',
'order_id': 123,
'user_id': 42,
'amount': 1500,
})
# Payment Service — подписан, реагирует асинхронно
for message in consumer: # topic 'orders'
event = message.value
if event['event'] == 'OrderCreated':
process_payment(event['order_id'], event['amount'])В монолите перевод «создать заказ → списать деньги → зарезервировать товар» — одна ACID-транзакция. В микросервисах это три разные БД, общей транзакции нет. Согласованность достигают паттерном Saga: цепочка локальных транзакций с компенсирующими действиями на случай сбоя.
OrderCreated → PaymentCharged → StockReserved → OrderConfirmed
│
(сбой на StockReserved)
▼
компенсация: RefundPayment → CancelOrder
⚠️ Забудьте про мгновенную согласованность между сервисами. Система живёт в eventual consistency: какое-то время заказ оплачен, но товар ещё не зарезервирован. Бизнес-логику и UI нужно проектировать с учётом этих промежуточных состояний.
В распределённой системе сбой части сервисов — норма, а не исключение. Нужны паттерны защиты.
Если сервис недоступен, бессмысленно долбить его запросами — это лишь усугубляет аварию. «Предохранитель» после серии ошибок размыкает цепь и быстро отдаёт fallback, давая сервису восстановиться.
import pybreaker
payment_breaker = pybreaker.CircuitBreaker(fail_max=5, reset_timeout=60)
@payment_breaker
def call_payment_service(order_id):
resp = requests.post(f'{PAYMENT_URL}/charge', json={'order_id': order_id}, timeout=3)
resp.raise_for_status()
return resp.json()| Паттерн | Защищает от |
|---|---|
| Timeout | Зависшего сервиса, держащего ваш поток |
| Retry + backoff | Кратковременных сбоев сети |
| Circuit Breaker | Каскадных отказов, добивания упавшего сервиса |
| Bulkhead | Исчерпания ресурсов одним медленным зависимым сервисом |
| Idempotency | Дублей при повторах |
Один запрос пользователя проходит через пять сервисов — без сквозного трейсинга найти, где он замедлился или упал, почти нереально.
trace_id через все сервисы — видно весь путь запроса и где он замедлился.correlation_id.💡 Передавайте
trace_id/correlation_idсквозь все вызовы и события — это единственный способ собрать историю одного запроса из логов десятка сервисов.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.
Далее: GraphQL с Strawberry/Graphene