This commit is contained in:
Дмитрий Соловьев
2026-05-25 14:23:52 +03:00
parent b3a6012ebb
commit d48ddd2657
1066 changed files with 104601 additions and 3 deletions
+303
View File
@@ -0,0 +1,303 @@
# PLATFORM_STANDARD.md
## Назначение
Этот документ задаёт платформенные требования для сервисов, работающих в BPM / Kafka / Zeebe-среде.
Документ определяет:
- архитектурные инварианты;
- требования к идемпотентности и надёжности;
- правила BPM / Kafka интеграции;
- требования к observability, compatibility, retry / DLQ, compensation и rollout safety.
Это **внутренний платформенный стандарт**. Некоторые правила ниже являются осознанной инженерной политикой команды и не должны трактоваться как универсальная истина для всей отрасли.
---
## Архитектурные инварианты
- BPM — только оркестратор процесса.
- Доменное состояние и бизнес-правила принадлежат сервису, а не BPM.
- BPM не должен использоваться как master data store для доменных данных.
- BPM может хранить только идентификаторы, ссылки и технические метаданные, необходимые для оркестрации.
- Везде предполагается at-least-once доставка. Повторная доставка — норма, а не исключение.
- Exactly-once не предполагается; корректность достигается через идемпотентность, dedup, Outbox и управляемые retries.
- Каждый обработчик обязан быть идемпотентным.
---
## Правила использования BPM
- BPM использовать только для операторских / регламентных процессов и long-running orchestration.
- Не использовать BPM для высокочастотных near-real-time streaming-потоков.
- Каждый BPM service task должен представлять ровно одну атомарную доменную операцию.
- Логика ожидания, таймаутов, ретраев, эскалаций и компенсаций должна моделироваться явно в BPM, а не быть скрыта внутри сервиса.
- BPMN flow не должен становиться местом владения бизнес-логикой.
---
## Camunda 8 / Zeebe
### Граница интеграции
- BPM-to-service-task интеграция должна идти через Zeebe gRPC Job Worker.
- Для нашей платформы использование REST connector для service task считается **не рекомендуемым и по умолчанию запрещённым**, если нет отдельного архитектурного согласования.
- Нельзя изменять `jobType`, пока существуют активные инстансы процесса.
### Обязательные поля корреляции
Каждый service task обязан принимать / обрабатывать:
- `businessKey`
- `operationId`
- `correlationId`
- `processInstanceKey`
- `traceId` или `traceparent`
### Контракт воркера
Для каждого Zeebe worker:
- читать входные данные только из process / job variables;
- валидировать обязательные поля корреляции до начала обработки;
- проверять dedup store по `operationId` до выполнения side effects;
- возвращать семантические результаты:
- `SUCCESS` -> `CompleteJob`
- `RETRYABLE_ERROR` -> `FailJob` с retry / backoff
- `BUSINESS_ERROR` -> `ThrowError`
- `FATAL_ERROR` -> ветка инцидента или обработка через исчерпание retries
- никогда не генерировать `operationId` внутри сервиса для входящих оркестрируемых вызовов;
- вызывать `CompleteJob` только после commit доменной транзакции.
---
## Идемпотентность
Все операции, вызываемые из BPM, и все Kafka consumers обязаны быть идемпотентными.
Обязательные правила:
- `operationId` должен приходить от вызывающей стороны / оркестратора;
- dedup storage должен обеспечивать atomic check-and-write semantics;
- dedup storage должен защищать от race conditions;
- повторный вызов с тем же `operationId` должен возвращать тот же семантический результат и не должен повторять side effects;
- для записей dedup должен быть документирован TTL в соответствии с бизнес-окном возможного повтора;
- non-atomic реализация идемпотентности запрещена.
Дополнительное обязательное правило:
- повторный вызов с тем же `operationId`, но с другим `businessKey` или семантически иным payload / параметрами, считается конфликтом контракта, а не валидным retry; такой случай должен приводить к явной ошибке / инциденту и быть достаточно залогирован для расследования.
Рекомендуемая реализация:
- таблица в доменной БД, например `idempotency_keys(operation_id, business_key, status, result_hash, created_at)`.
---
## Порядок транзакции и публикации событий
Когда операция изменяет доменное состояние и публикует события:
1. Выполнить доменную операцию.
2. Записать событие(я) в outbox в рамках той же транзакции.
3. Сделать commit транзакции.
4. Только после этого подтверждать BPM (`CompleteJob`) или внешний success.
Публикация событий вне Outbox запрещена.
Это правило одинаково применяется к:
- синхронным сервисным операциям;
- Zeebe workers;
- Kafka consumers, которые меняют состояние и публикуют новые события.
---
## Process variables
- Process variables должны быть минимальными.
- Хранить только ID, ссылки, routing data и небольшие технические метаданные.
- Не хранить файлы, большие JSON payload, большие массивы в process variables.
- Большие payload должны храниться в доменной БД или object storage; в process variables должны храниться только ссылки.
- Process variables должны быть версионированы и документированы.
- Для long-running процессов изменения набора или формы variables должны оставаться совместимыми с уже запущенными инстансами, либо сопровождаться явной migration strategy.
- Для нашей платформы безопасным внутренним ориентиром считается бюджет **50100 KB на один process instance**. Это не технический максимум платформы, а engineering guardrail для управляемости и надёжности процессов.
---
## Семантика результатов
Каждая операция должна возвращать семантическую классификацию результата, а не только transport status codes.
Допустимые классы результатов:
- `SUCCESS`
- `RETRYABLE_ERROR`
- `BUSINESS_ERROR`
- `FATAL_ERROR`
Каждая long-running операция должна иметь:
- явно заданный SLA / timeout;
- async processing model, где это нужно;
- status lookup по `operationId`;
- определённое поведение cancel, если отмена поддерживается.
---
## Kafka / event-driven
### Базовые правила
- Базовое предположение по доставке — at-least-once.
- Consumers должны быть идемпотентными по `event_id` и / или `operation_id`.
- Нельзя размещать бизнес-логику напрямую в consumer / listener классах.
- Consumers / listeners должны делегировать в application / domain services.
- Сохранять текущую retry / DLQ-стратегию, если явно не запрошено её изменение.
### Семантика `event_id` и `operation_id`
- `event_id` используется для dedup повторной доставки **одного и того же опубликованного события**.
- `operation_id` используется для dedup повторного выполнения **одной и той же бизнес-операции**, если этот идентификатор протягивается через события и обработчики.
- Если в сообщении присутствуют оба идентификатора, обработчик обязан явно понимать и документировать, какой из них используется для какой семантики dedup.
### Контракты событий
Требования ниже относятся прежде всего к **domain events**. Для технических, retry, DLQ, compacted state и других служебных топиков может использоваться отдельная документированная конвенция.
Для domain events:
- именование топиков должно следовать шаблону `<domain>.<entity>.<event>.v<major>`;
- partition key должен сохранять порядок событий по одной сущности через `business_key`;
- каждое событие должно содержать как минимум:
- `event_id`
- `event_type`
- `event_version`
- `occurred_at`
- `producer`
- `business_key`
- `correlation_id`
- `trace_id`, если доступен
- `process_instance_id`, если применимо
- предпочтительны Avro / Protobuf со Schema Registry;
- строго валидируемая JSON Schema допустима только если это согласовано платформой.
### Что нельзя менять без явного запроса
- topic names
- event schema
- key semantics
- partition assumptions
- serialization format
- retry topology
- DLQ routing rules
---
## Schema evolution и совместимость
- Breaking changes требуют новой major-версии.
- Добавление optional fields с default значениями допустимо.
- Удаление или переименование полей требует новой major-версии.
- По умолчанию consumers должны оставаться совместимыми как минимум с текущей схемой и согласованной политикой совместимости платформы. Если для конкретного домена зафиксировано правило поддержки текущей и предыдущей minor-версии, оно должно быть явно указано в контракте этого домена.
Для BPM, messaging и public APIs:
- сохранять backward compatibility, если явно не запрошен breaking change;
- учитывать rolling deployment compatibility;
- учитывать coexistence старых и новых consumers;
- избегать изменений, требующих lockstep deployment, если это явно не запрошено.
---
## Retry / DLQ
Retries должны быть управляемыми и конечными.
Рекомендуемая политика:
- `RETRYABLE_ERROR`: ограниченные retries с exponential backoff и jitter
- `BUSINESS_ERROR`: никаких слепых retries; отправка в operator handling / parking lot / DLQ
- `FATAL_ERROR`: никаких слепых retries; создание инцидента и маршрутизация в DLQ, где применимо
Обязательные правила:
- бесконечные retries запрещены;
- отключение DLQ для критичных consumer flows запрещено;
- replay из DLQ без установленной причины запрещён;
- Zeebe retries и Kafka retries должны рассматриваться как два независимых контура управления.
---
## Compensation
Если операция имеет side effects, должно быть явно определено одно из следующего:
- compensating operation;
- явное указание, что компенсация невозможна;
- ветка operator / manual remediation.
Компенсация должна быть:
- идемпотентной;
- детерминированной;
- семантически классифицированной;
- залогированной с `businessKey`, `operationId`, причиной и результатом.
---
## Observability
Структурированные JSON-логи обязательны.
Каждый log / event / span должен содержать, где применимо:
- `correlation_id`
- `trace_id`
- `process_instance_id`
- `business_key`
- `operation_id`
- `service_name`
- `env`
Обязательная телеметрия:
- latency (`p50/p95/p99`)
- error rate по семантическим классам
- retry rate / attempts histogram
- active process instances / stuck instances
- consumer lag / DLQ rate
- compensation rate
Трассировка:
- OpenTelemetry обязателен
- каждый шаг процесса должен создавать span
- span attributes должны содержать `process_instance_id`, `business_key`, `operation_id`, `step_name`
Если меняется Kafka / BPM / external integration logic:
- сохранить или добавить correlation identifiers;
- сохранить или добавить `operation_id`, где это релевантно;
- логировать достаточно для дебага retries, duplicates и compensations;
- никогда не логировать секреты или sensitive payloads.
---
## Backpressure и runtime load
- Воркеры должны явно ограничивать `maxJobsActive` и concurrency.
- Нельзя использовать рост timeout или retry как механизм масштабирования.
- Backpressure должен управляться через масштабирование воркеров и управление пропускной способностью.
- Предположения о throughput воркеров должны быть документированы.
- CPU / RAM saturation должна наблюдаться и иметь алерты.
---
## Безопасность и аудит
- Сервисы должны поддерживать JWT / OAuth2 там, где это применимо.
- Административные операции должны быть защищены role-based или attribute-based access control.
- Секреты нельзя хранить в репозитории.
- Административные действия, такие как retry / cancel / compensate / migrate, должны быть аудитируемыми.
---
## CI / CD и production safety
Минимально обязательные гейты **для изменений, где это релевантно**:
- unit tests
- coverage threshold
- static analysis
- API compatibility checks
- schema compatibility checks
- BPM + worker integration tests
- resilience tests для поведения timeout / retry / DLQ
Правила деплоя:
- деплой BPMN только через pipeline;
- деплой воркеров только через pipeline;
- ручной деплой BPMN в production запрещён;
- критичные изменения retry / timeout / jobType не должны вноситься только через UI без трассируемости в VCS.