Files
Дмитрий Соловьев fa357a78b4 улучшить observability и документацию outbox
- обновить README с текущим Kafka, DB, outbox и scaling flow
- расширить логи listener, ingestion, publisher и outbox worker
- не логировать raw payload и outbox payload
- закрыть test controller явным property
- добавить Micrometer counters для ingestion и outbox outcomes
- сохранить listener flow, payload contract и delivery semantics без изменений
2026-05-25 15:20:25 +03:00

93 lines
4.3 KiB
Markdown
Raw Permalink 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.
# 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.