# 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: - именование топиков должно следовать шаблону `...v`; - 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.