Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Микросервисы с Django
microservices

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

Границы сервисов, outbox, идемпотентность, события и Saga

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

Статус: материал архитектурный и актуален для Django 5.2/6.0. Конкретный broker, gateway, orchestrator и observability stack выбираются отдельно и меняются быстрее framework.

Микросервис — не маленький Django app и не контейнер. Это бизнес-возможность, которую команда может изменять, deploy и эксплуатировать относительно независимо. Сервис владеет своим контрактом и данными, имеет понятный SLO и не требует синхронного общего release всей системы.

Разделение покупает автономию ценой сети, eventual consistency, множества pipelines, on-call и сложной диагностики. Если нет команд и процессов, способных нести эту цену, modular monolith обычно надёжнее.

#Modular monolith first

Начать с монолита — не временный провал. В одном Django deployment доступны:

  • обычные database transactions;
  • refactoring с помощью IDE;
  • один integration test environment;
  • один набор telemetry и migrations;
  • прямой вызов Python без сетевого отказа.

Правильно разделённые business apps, public services и ownership позволяют извлечь сервис позже. «Распределённый монолит» возникает, когда processes уже разделены, но schema, release и call chain остаются настолько связанными, что один сервис нельзя изменить отдельно.

Синхронный вызов сам по себе не доказывает плохую границу. Проблема появляется, когда почти каждый request строит длинную обязательную цепь, contracts меняются lockstep, а сбой одного компонента останавливает всё.

#Когда extraction может окупиться

Сильные сигналы:

  • отдельная команда владеет capability end-to-end;
  • компонент имеет другой цикл release/compliance;
  • workload масштабируется или изолируется иначе;
  • bounded context стабилен и имеет узкий контракт;
  • blast radius монолита стал существенной проблемой;
  • организация уже умеет deploy, наблюдать и восстанавливать много services.

Слабые причины:

  • «так современнее»;
  • один Django model стал большим;
  • хочется использовать новый broker;
  • один query медленный;
  • надежда, что network boundary исправит отсутствие modularity.

Перед extraction измерьте coupling и составьте runbook. Самым первым часто выбирают capability с небольшим числом зависимостей и понятным asynchronous contract, а не центральный checkout или authentication.

#Границы по business capability

Sales owns orders and commercial terms Billing owns invoices, payment attempts and refunds Fulfillment owns reservations and shipments Messaging owns templates and delivery attempts

Context владеет правилами и public contract. Другой сервис не меняет его таблицы напрямую.

«Database per service» означает data ownership, а не обязательно отдельный физический PostgreSQL server. На старте сервисы могут иметь разные schemas/databases/roles на одном managed cluster, если:

  • runtime role не читает чужие tables;
  • migrations принадлежат одному сервису;
  • нет cross-service ForeignKey/JOIN;
  • backup/restore и noisy-neighbor risks понятны.

Физическое разделение усиливают по требованиям isolation, scale и compliance. Общая таблица users, которую обновляют пять services, отменяет независимость независимо от числа контейнеров.

#Django как реализация сервиса

Django подходит сервису с relational domain, admin, ORM и HTTP API. Это не означает «один Django project на каждую таблицу». Сервис должен быть достаточно крупным, чтобы оправдать отдельный deployment.

Не все компоненты обязаны использовать Django. Streaming processor или edge proxy может иметь другой runtime. Platform standards важнее одинакового framework любой ценой:

  • authentication/service identity;
  • logging/tracing/metrics;
  • health and graceful shutdown;
  • deployment/rollback;
  • secrets and dependency policy;
  • contract/version rules.

Общий Python package с DTO/helpers допустим, но если он содержит ORM models и требует одновременного обновления всех services, он создаёт lockstep coupling. Contract schema должна иметь совместимую эволюцию, а не одну общую ветку исходников.

#Синхронный контракт

HTTP/gRPC используют, когда caller не может продолжить без немедленного ответа: проверить quotation, получить policy decision, создать payment authorization.

import httpx class BillingClient: def __init__(self, *, base_url, token, timeout=2.0): self.base_url = base_url self.token = token self.timeout = timeout def authorize(self, *, order_id, amount_minor, idempotency_key): response = httpx.post( f"{self.base_url}/v1/authorizations", headers={ "Authorization": f"Bearer {self.token}", "Idempotency-Key": idempotency_key, }, json={ "order_id": str(order_id), "amount_minor": amount_minor, }, timeout=self.timeout, ) response.raise_for_status() return response.json()

Production client также использует connection pooling, trace propagation и ограниченные retries. Timeout должен укладываться в общий request budget. Если gateway даёт 3 секунды, три последовательных upstream по 2 секунды не образуют рабочий план.

Retry безопасен только для временной ошибки и идемпотентной операции. Timeout не сообщает, выполнил ли server side effect, поэтому idempotency key хранится получателем с unique constraint и прежним результатом.

Не ставьте retry на каждом proxy/library/service без бюджета: один client request превратится в геометрическое число вызовов.

#Асинхронный контракт

Event сообщает о свершившемся факте и не требует немедленного ответа publisher:

{ "event_id": "01J...", "event_type": "sales.order_placed.v1", "occurred_at": "2026-07-19T09:15:00Z", "order_id": "8e1c...", "customer_id": "5d9a...", "total_minor": 150000, "currency": "RUB" }

Событие содержит уникальный ID, тип/version, время и минимальный стабильный payload. Не публикуйте сериализованный ORM instance: consumer привяжется к внутренней schema и PII.

Команда в broker («зарезервируй товар») тоже допустима, но у неё один логический получатель и контракт результата/ошибки. Не называйте любую очередь event-driven architecture.

Асинхронность подходит уведомлениям, projections и workflow с промежуточным state. Она плохо подходит проверке, без которой текущий HTTP-ответ не имеет смысла.

#Transactional outbox и inbox

Классическая ошибка:

commit Order -> процесс падает -> OrderPlaced не опубликован

Outbox row записывается одной transaction с заказом. Dispatcher с retry публикует непереданные rows. Это даёт at-least-once, поэтому consumer хранит обработанный event_id/business idempotency key с unique constraint — inbox/deduplication.

Важные детали:

  • publish marker не должен потерять событие при падении между send и update;
  • replay старого события должен быть безопасен;
  • poison message получает bounded retries и quarantine/dead-letter workflow;
  • schema version поддерживается во время rolling upgrade;
  • ordering гарантируется только в выбранной scope, например partition order_id;
  • lag и возраст старейшего event наблюдаются метрикой.

Broker не создаёт exactly-once business side effect. Consumer проектируют идемпотентным так же, как Celery task.

#Чтение чужих данных

Без cross-service JOIN есть несколько вариантов:

  1. synchronous API composition, если нужна свежесть сейчас;
  2. локальная read model, обновляемая событиями;
  3. BFF/query service для экрана;
  4. аналитический warehouse/lake, не production DB JOIN.

Локальная копия customer_display_name является осознанной projection и может отставать. Она не даёт Billing право менять профиль. Определите источник истины, допустимый lag и способ rebuild/reconciliation.

Запрос списка, который делает N HTTP-вызовов к User Service, — распределённый N+1. Нужны batch API, projection или изменение contract.

#Saga — workflow, а не distributed rollback

Saga координирует локальные transactions:

Order pending -> payment authorized -> stock reserved -> order confirmed

При отказе выполняется compensation, например release authorization. Компенсация не является магическим rollback:

  • refund/release может сам временно упасть;
  • физически отправленный товар нельзя «откатить» SQL-командой;
  • пользователь успевает увидеть промежуточный state;
  • сообщения могут прийти повторно или не по порядку.

Saga — явная state machine с timeouts, idempotency, retry и manual reconciliation. Choreography проще для нескольких независимых реакций, но длинный workflow трудно увидеть. Orchestrator делает последовательность явной, но становится важным компонентом. Выбор зависит от процесса.

Не называйте любое компенсирующее действие transaction: ACID между services обычно отсутствует.

#Resilience patterns с бюджетом

Для каждого dependency задайте:

  • connect/read/total timeout;
  • максимальную concurrency (bulkhead/pool);
  • какие ошибки retryable;
  • max attempts и jitter;
  • fallback и его допустимую stale-семантику;
  • circuit breaker threshold/recovery, если он действительно нужен.

Circuit breaker быстро прекращает вызовы к явно нездоровой dependency и периодически пробует восстановление. Он не исправляет invalid request, не должен скрывать деньги/permissions за «успешным fallback» и требует общей/локальной семантики в зависимости от library/mesh.

Bulkhead часто важнее: отдельный connection pool/queue не позволяет медленному provider исчерпать все web threads. Rate limit защищает dependency, load shedding сохраняет критичные endpoints при saturation.

#Gateway и BFF

Gateway может завершать TLS, маршрутизировать, ограничивать request и проверять базовую identity. Но сервис всё равно проверяет:

  • подпись, issuer и audience token либо доверенную service identity;
  • authorization для своей business operation;
  • tenant/resource scope;
  • защиту от прямого обхода gateway на network level.

«Gateway уже проверил login» не означает, что Billing может пропустить object permissions. Gateway не должен превращаться в единственное место всей бизнес-логики.

BFF адаптирует API под web/mobile и агрегирует ответы с ограниченным fan-out. Он не получает права обходить contracts и читать databases services.

#Identity между сервисами

Различайте end-user identity и service identity. Внутренний вызов должен отвечать:

  • какой service вызывает;
  • от имени какого пользователя/tenant, если delegation разрешена;
  • какой scope/audience предоставлен;
  • можно ли доверять forwarded headers.

Нельзя принимать X-User-Id от публичного клиента как доказательство identity. Gateway/proxy должен удалить spoofed headers, а services — доверять только аутентифицированной внутренней цепочке или проверяемому token.

Центральный identity provider часто лучше самодельного «User microservice», который синхронно вызывается каждым request. Profile data и authentication credentials могут иметь разные boundaries.

#Contracts и совместимый deployment

OpenAPI/JSON Schema, Protobuf или AsyncAPI фиксируют contract. Правила эволюции:

  • добавлять optional fields безопаснее удаления/переименования;
  • consumer игнорирует неизвестные поля;
  • enum evolution требует fallback для нового значения;
  • producer не удаляет старое, пока consumers не обновлены;
  • breaking change получает новую версию/route/topic.

Consumer-driven contract tests ловят несовместимость до deployment, но не заменяют integration test с auth, timeouts и serialization. Schema registry полезен событиям, если правила compatibility реально включены.

Не обещайте синхронный coordinated release как постоянную стратегию — это признак потерянной независимости.

#Service discovery и deployment

В Kubernetes Service DNS уже является service discovery. В managed platform эту роль выполняет platform registry/DNS. Отдельный Consul нужен не автоматически.

Микросервис не требует Kubernetes или даже containers: PaaS/serverless/VM подходят при нужных SLO. Оркестратор полезен, когда команда готова управлять:

  • readiness/liveness/graceful shutdown;
  • rollout/canary/rollback;
  • autoscaling и quotas;
  • secrets и network policies;
  • service identities;
  • telemetry collectors.

Один cluster не делает services независимыми, а отдельный pipeline не гарантирует совместимый contract.

#Observability и debugging

Каждый request/event несёт стандартный trace context. Система собирает:

  • RED metrics по service/route;
  • dependency latency/error/saturation;
  • broker consumer lag, retries и dead letters;
  • saga state age и stuck workflows;
  • structured logs с trace ID и business ID;
  • deployment version в каждом span/event.

Correlation ID не «единственный способ» диагностики и не заменяет trace propagation. Metric labels не должны содержать request/event IDs.

Для инцидента нужен service catalog: owner, SLO, dependencies, dashboards и runbook. Без ownership десять services означают десять бесхозных процессов.

#Стратегия extraction

Безопасный strangler-путь:

  1. обозначить модуль и запретить прямые cross-imports к его internals;
  2. создать стабильный in-process public API;
  3. собрать метрики/trace и contract tests;
  4. отделить data ownership и backfill/reconciliation;
  5. заменить часть вызовов network/event adapter;
  6. переключить traffic постепенно;
  7. удалить старый путь только после периода наблюдения.

Dual write в старую и новую базы без outbox/repair почти неизбежно расходится. Выберите один source of truth на каждом этапе и измеряйте расхождения.

#Проверка готовности организации

  • у каждого сервиса есть owner и on-call;
  • platform даёт шаблон безопасного deployment и telemetry;
  • contracts имеют compatibility policy;
  • broker delivery/replay понятны;
  • команды умеют расследовать distributed trace;
  • cost множества environments/pipelines принят;
  • разработчик может поднять/заменить dependencies локально;
  • есть plan восстановления данных и stuck sagas.

Если половина пунктов отсутствует, сначала улучшите modular monolith/platform. Сетевое разделение раньше организационной автономии обычно увеличивает lead time.

#Legacy и исправления

  • «Своя БД» уточнена как эксклюзивное владение schema/data, а не обязательный отдельный server.
  • Совет «предпочитать async события» заменён выбором по семантике ответа и согласованности.
  • Gateway больше не считается единственным слоем authentication/authorization.
  • Redis Pub/Sub не предлагается как взаимозаменяемая durable event queue без анализа потери/replay.
  • Kubernetes/Docker не объявляются обязательным способом deployment микросервисов.
  • Saga описана как stateful workflow с несовершенной compensation, а не distributed ACID rollback.

#Материалы

  • Martin Fowler: Microservices
  • Martin Fowler: Monolith First
  • OpenTelemetry documentation

Далее: GraphQL со Strawberry