Init
This commit is contained in:
@@ -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.
|
||||
- Для нашей платформы безопасным внутренним ориентиром считается бюджет **50–100 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.
|
||||
Reference in New Issue
Block a user