Движение данных: очереди, идемпотентность, корреляция, статусы
Зачем отдельно разбирать «движение данных»
В предыдущих статьях курса мы закрепили три опоры интеграции с ГИС ЖКХ:
обмен почти всегда идёт через SOAP/XML и строго по WSDL/XSD
многие операции асинхронные: «принято» не равно «успешно обработано»
интеграция требует отдельной структуры проекта: клиенты, маппинг, журнал, хранилище статусовЭта статья отвечает на практический вопрос: как сделать так, чтобы данные «доезжали» управляемо, даже если:
сервис временно недоступен
вы не получили ответ из-за таймаута
обработка в ГИС ЖКХ занимает минуты или часы
один и тот же пакет случайно отправили дваждыДля этого в интеграционном проекте обычно вводят четыре конструкта, которые работают вместе:
очереди (и планировщик) для управляемой доставки и повторов
идемпотентность для защиты от дублей
корреляция для связывания всех технических событий с одной бизнес-операцией
статусы для прозрачного жизненного цикла операции и её элементов!Общая схема движения операции от внутренней системы до итогового результата в ГИС ЖКХ
Очереди и планирование: почему «просто вызвать SOAP» недостаточно
Что такое очередь в контексте интеграции
Очередь здесь — это механизм, который хранит задачи на выполнение и отдаёт их воркерам (исполнителям) в нужный момент. Очередь может быть:
брокером сообщений (например, RabbitMQ/ActiveMQ)
таблицей в базе данных (часто достаточно для старта)
специализированным планировщиком фоновых задачСмысл один: интеграция перестаёт зависеть от того, жив ли сейчас внешний сервис, и получает управляемую доставку.
Зачем очереди именно в ГИС ЖКХ
Очереди встраиваются почти в каждый сценарий:
исходящая отправка: «начисления готовы» → задача на публикацию
разбор пакетной загрузки: один батч → много элементарных результатов
опрос статусов: «принято» → задача на повторный опрос через интервал
повторные попытки: сетевой сбой → задача на повтор по стратегии backoffЕсли этого нет, появляются две типовые проблемы:
потеря операций: приложение упало между «сформировали XML» и «сохранили факт отправки»
шторм повторов: при сбое сеть/сервис начинает получать неконтролируемые повторы, а вы — дубли и конфликтыМинимальный паттерн: Outbox + воркеры
На практике удобно мыслить так:
Outbox: надёжное хранилище «что нужно отправить» (обычно в БД)
Воркер отправки: забирает запись, формирует XML, валидирует, подписывает, отправляет
Воркер статусов: периодически опрашивает результат по корреляционным идентификаторамКритичный момент: запись в Outbox создаётся до попытки взаимодействия с ГИС ЖКХ и хранится независимо от жизни процесса.
Идемпотентность: защита от дублей при повторах
Что такое идемпотентность простыми словами
Идемпотентность — это свойство операции, при котором повтор выполнения не меняет результат сверх первого применения.
В интеграции с ГИС ЖКХ повторы неизбежны, например:
вы отправили запрос, но не получили ответ из-за таймаута
сервис вернул временную ошибку
приложение перезапустилось, а операция была «на границе» отправкиЕсли операция не идемпотентна, повтор превращается в:
создание дублей сущностей
конфликт «уже существует»
несогласованность статусов между вашей системой и ГИС ЖКХКлюч идемпотентности
Чтобы повторы стали управляемыми, вводят ключ идемпотентности — стабильный идентификатор «что именно мы хотели сделать».
Требования к ключу:
однозначно описывает бизнес-действие
одинаковый при повторной отправке
не зависит от технических деталей (время формирования, случайные GUID попытки)Примеры (логика, не стандарт):
начисления: orgId + period + batchNumber + internalDocumentId
показания: meterId + readingDate
обновление объекта: entityType + internalId + version (если вы ведёте версионность)Где реализуется идемпотентность
Обычно на двух уровнях.
На уровне вашей системы (обязательно)
одна бизнес-операция создаёт одну запись операции в хранилище
параллельные исполнители не должны отправлять одно и то же одновременно
повтор должен «приклеиваться» к той же операции, а не создавать новуюНа уровне результата (желательно)
если ГИС ЖКХ отвечает «объект уже существует» или «операция уже обработана», вы должны уметь:
- распознать это как безопасный повтор
- перейти к синхронизации идентификаторов/статуса, а не к бесконечным ретраям
Практический вывод: идемпотентность — это не «ретраи», а дисциплина хранения факта операции и её ключа.
Корреляция: как связать запрос, ответ, статус и протокол
Что такое корреляция
Корреляция — это связывание всех технических событий вокруг одной операции:
ваш внутренний документ или событие
сформированный XML
попытки отправки
первичный SOAP-ответ
последующие запросы статуса
итоговый протокол обработкиБез корреляции вы не сможете ответить на вопросы поддержки и бизнеса:
«что именно мы отправили?»
«почему в ЛК ГИС ЖКХ не появилось?»
«это ошибка сети или ошибка данных?»Практическая модель идентификаторов
Удобно разделять идентификаторы на три группы.
Идентификатор бизнес-операции: стабильный ключ у вас (например, billingDocId, jobId, operationId)
Идентификатор попытки: уникален для каждой отправки (например, attemptId)
Идентификаторы внешнего контура: то, что возвращает ГИС ЖКХ для отслеживания асинхронной обработки (например, идентификатор сообщения/заявки/квитанции обработки — конкретное имя зависит от сервиса)Важно: даже если сервис возвращает внешний идентификатор, основной якорь корреляции должен быть ваш operationId, потому что он живёт дольше контрактов и версий схем.
Корреляция в логах
Для сквозной трассировки (особенно если несколько сервисов и воркеров) полезно внедрить единый correlationId в логи. Если у вас распределённая система, можно ориентироваться на стандарт W3C:
W3C Trace ContextДаже если вы не внедряете полный distributed tracing, сам принцип полезен: одна операция — один идентификатор в логах, журнале и статусах.
Статусы: как описать жизненный цикл операции и не потерять «середину»
Почему статусы нужны всегда
SOAP-вызов — это момент времени, а интеграционная операция — процесс.
Процесс может длиться долго и переживать:
повторные попытки
ожидание асинхронной обработки
частичные ошибки внутри пакета
ручную корректировку данных и повторПоэтому статусы — это не «красота», а механизм восстановления и контроля.
Два уровня статусов: операция и элементы пакета
В ГИС ЖКХ часто отправляют батчи (списки сущностей). Отсюда возникают два уровня состояния.
Статус операции (батча): отправлен ли пакет, принят ли в обработку, получен ли итоговый протокол
Статус элемента: успешен ли конкретный дом/ПУ/начисление внутри пакетаЕсли хранить только статус батча, вы теряете управляемость при частичных ошибках: придётся отправлять заново всё или разбирать вручную.
Пример внутренней машины состояний
Ниже пример статусов, которые удобно держать у себя. Они не обязаны совпадать с терминологией ГИС ЖКХ, их цель — управляемый процесс.
| Статус | Что означает | Что должно быть сохранено | Типичная следующая стадия |
|---|---|---|---|
| Draft | операция создана, но ещё не готова к отправке | ключ идемпотентности, входные данные/ссылки | Validated |
| Validated | XML собран и прошёл локальную XSD-валидацию | версия контракта, результат валидации | Signed |
| Signed | сообщение подписано | отпечаток сертификата, факт подписи | Sent |
| Sent | запрос отправлен | сырой request/response, attemptId | Accepted или RetryScheduled |
| Accepted | получен первичный ответ «принято» | внешний идентификатор для статуса | Processing |
| Processing | ждём итог обработки | расписание следующего опроса | Success или Failed |
| Success | обработано успешно | протокол, внешние идентификаторы сущностей | завершение |
| Failed | обработано с ошибками | протокол с ошибками, привязка ошибок к элементам | исправление и повтор |
!Машина состояний интеграционной операции и места, где возникают повторы и ошибки
Как статусы связаны с типами ошибок
Статусы помогают не только «где мы», но и что делать дальше. Типовая связка (по смыслу из предыдущих статей):
транспортная ошибка → планировать повтор (RetryScheduled)
ошибка подписи → остановить и требовать вмешательства по сертификатам (Failed с типом Security)
XSD-ошибка → не повторять, исправлять маппинг/данные (Failed с типом Contract)
бизнес-ошибка в протоколе → исправлять предметные данные/сопоставления (Failed с типом Business)Главное правило: ретраи лечат только то, что может исчезнуть само (сеть, временная недоступность), но не лечат структуру XML и бизнес-правила.
Практическая «минимальная модель данных» для журнала и статусов
Ниже — минимальный набор сущностей, который обычно нужен, чтобы очереди, идемпотентность и статусы работали вместе.
Что хранить по операции
operationId: внутренний идентификатор
idempotencyKey: ключ идемпотентности
service и method: какой сервис и операция
contractVersion: версия WSDL/XSD
status: текущий статус
nextRunAt: когда можно/нужно продолжить (для очереди/планировщика)
externalTrackingId: идентификатор для опроса статуса (если выдан)Что хранить по попытке отправки
attemptId: идентификатор попытки
operationId: ссылка на операцию
createdAt: время попытки
requestXmlRef и responseXmlRef: ссылки на сохранённые request/response (или защищённое хранилище)
transportResult: результат транспорта (успешно/таймаут/ошибка TLS)
soapFault: если был SOAP FaultЧто хранить по элементам батча (если пакетный метод)
itemId: элемент внутри пакета
operationId: к какому батчу относится
sourceEntityId: ваш идентификатор сущности
status: успех/ошибка/не обработан
errorCode и errorText: если ошибка пришла по элементуПрактический эффект: вы можете повторно отправить только элементы со статусом ошибки, а не весь батч.
Типовой алгоритм движения операции по очередям
Ниже — базовый сценарий, который удобно реализовывать в Application слое (из статьи про структуру проекта), опираясь на Infrastructure (транспорт/подпись/хранилище).
Создать операцию в статусе Draft с idempotencyKey.
Поставить операцию в очередь на обработку.
Воркер берёт операцию (с блокировкой от параллельного выполнения).
Собирает контрактный DTO и сериализует XML.
Валидирует XML по XSD.
Подписывает сообщение.
Отправляет SOAP-запрос.
Сохраняет первичный ответ и переводит статус:
- в
Accepted, если принят и выдан внешний идентификатор
- в
RetryScheduled, если временная ошибка
- в
Failed, если неустранимая ошибка
Воркер статусов по externalTrackingId запрашивает итог.
Разбирает протокол:
- обновляет статус операции
- при батчах обновляет статусы элементов
- формирует задачи на исправление данных при бизнес-ошибках
Повторы и backoff: как не устроить «само-DDoS»
При временных сбоях важны два правила.
Повторы должны быть ограничены по числу попыток и по времени жизни операции.
Интервалы повторов должны расти, если ошибка повторяется (backoff).Пример стратегии без формул:
1-я ошибка: повтор через 30 секунд
2-я ошибка: повтор через 2 минуты
3-я ошибка: повтор через 10 минут
далее: перевод в Failed и эскалация, если лимит исчерпанОтдельно стоит разделять:
повторы отправки (когда мы не уверены, что запрос дошёл)
повторы опроса статуса (когда запрос дошёл и принят, но итог ещё не готов)Это разные очереди/типы задач и разные интервалы.
Антипаттерны, которые ломают управляемость
Нет единого operationId и журнала: разбор инцидентов становится невозможным.
Ретраи на XSD-ошибках: система без пользы создаёт нагрузку и шум в логах.
Нет блокировки/дедупликации: два воркера отправляют одно и то же, появляются дубли и конфликтные статусы.
Храните только статус батча: частичные ошибки превращаются в ручной ад.
Не сохраняете первичный ответ: теряете внешний идентификатор и не можете корректно опросить итог.Связь с следующими темами курса
В этой статье мы описали механику движения интеграционной операции.
Дальше по курсу логично углубиться в две практические области:
подписание SOAP/XML и управление сертификатами как отдельный этап жизненного цикла (и отдельный класс ошибок)
обработка ошибок: как классифицировать SOAP Fault, контрактные ошибки, бизнес-валидации и статусные конфликты, и как связывать их со статусами и стратегиями повторов