Files
dc-observatio/docs/standards/PLATFORM_STANDARD.md
T
Дмитрий Соловьев d48ddd2657 Init
2026-05-25 14:23:52 +03:00

304 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.