# pcp-route-processing-service ## Назначение сервиса `pcp-route-processing-service` принимает route passport из Kafka, парсит входной JSON, нормализует GeoJSON geometry в WKT, сохраняет паспорт маршрута в БД и создаёт downstream events в outbox. Публикация downstream events выполняется не из Kafka listener, а через scheduled outbox worker: 1. `RoutePassportMessageListener` читает input Kafka record. 2. `RoutePassportJsonParserService` валидирует и парсит payload. 3. `RoutePassportIngestionService` в одной DB transaction сохраняет `route_passports` и создаёт `route_passport_outbox`. 4. Listener подтверждает offset только после успешного ingestion. 5. `RoutePassportOutboxWorker` claim'ит due outbox rows и публикует их через `RoutePassportKafkaPublisherService`. ## Kafka topics Input topic: - `pcp.request.survey-georeference.v1` DLQ topic: - `pcp.request.survey-georeference-dlq.v1` Output topics: - `pcp.route.in.v1` - событие для mission-planning flow. - `pcp.route.georeference.v1` - событие для request-service matching flow. ## DB tables - `route_passports` - сохранённые паспорта маршрутов и source metadata входного Kafka record. - `route_passport_outbox` - pending/published/failed downstream events для Kafka publication. ## Horizontal scaling Несколько pod'ов сервиса могут безопасно запускать outbox worker одновременно. Outbox claim использует DB-level concurrency: - `SELECT ... FOR UPDATE SKIP LOCKED` выбирает due `PENDING` rows. - `lockedUntil` выставляется в той же transaction, что и claim. - После commit другие instances не claim'ят row, пока `lockedUntil` в будущем. - Если instance умер после claim, abandoned row снова станет доступна после истечения `lockedUntil`. - In-memory locks, leader election и single-instance assumptions не используются. ## Delivery semantics Delivery semantics: at-least-once. Exactly-once не обещается. Kafka transactions не используются. Downstream consumers должны быть идемпотентны к повторной доставке одного и того же события. ## Error handling Input Kafka flow: - contract/parsing errors не подтверждают offset и обрабатываются configured retry/DLQ policy; - после исчерпания retry record публикуется в `pcp.request.survey-georeference-dlq.v1`; - временные ошибки ingestion retry'ятся bounded policy. Output Kafka flow: - downstream events сначала сохраняются в `route_passport_outbox`; - output Kafka errors не теряются, а переводят row обратно в `PENDING` с bounded exponential backoff; - после исчерпания `maxAttempts` из outbox row событие получает статус `FAILED`. ## Observability Логи содержат operational identifiers без полного payload: - source topic, partition, offset, key и payload size для input ingestion; - `routeId`, `routeNameFull`, `traceId`, `correlationId`; - outbox id, event type, target topic, message key, attempts, status, `nextAttemptAt`, `lockedUntil`, `publishedAt`; - exception class/message для failure path. Доступные Micrometer counters: - `route_passport_ingest_success_total` - `route_passport_ingest_failure_total` - `route_passport_outbox_published_total` - `route_passport_outbox_retry_total` - `route_passport_outbox_failed_total` ## Operational notes Test controller is disabled by default and must not be exposed in production unless explicitly enabled with: ```yaml app: route-processing: test-controller: enabled: true ``` ## Current business limitations - `routeStatus` сейчас остаётся `PROCESSED`. - Сервис не делает request matching. - Сервис не считает coverage. - Сервис не вызывает request-service или mission-service по HTTP.