При разработке механизмов интеграции часто возникает задача: сразу после записи документа в 1С необходимо отправить данные во внешнюю систему через HTTP-запрос. Самый логичный на первый взгляд путь — запустить ФоновыеЗадания.Выполнить() в процедуре ПриЗаписи или ПередЗаписью. Однако разработчики часто сталкиваются с тем, что фоновое задание запускается, но «не видит» созданный документ или его номер. Рассмотрим подробнее, почему это происходит и как правильно организовать отложенный запуск.
Проанализируем ситуацию с точки зрения работы СУБД. Когда мы записываем документ, 1С открывает транзакцию. Все действия внутри процедур ПередЗаписью и ПриЗаписи модуля объекта происходят внутри этой транзакции. Пока транзакция не завершена (не произошел COMMIT), данные физически не зафиксированы в базе данных для других сеансов.
Фоновое задание — это отдельный сеанс. Согласно уровню изоляции транзакций (обычно Read Committed), фоновое задание, запущенное мгновенно, пытается прочитать документ, который еще «заблокирован» основной транзакцией или вовсе не существует для внешнего наблюдателя. Именно поэтому возникает ситуация: фоновое задание стартовало, обратилось к документу по ссылке, а номера нет или объект не найден. Повторная запись обычно «помогает», так как данные уже были зафиксированы в прошлый раз.
Если нам нужно быстрое и простое решение без создания дополнительных объектов метаданных, мы можем использовать искусственную задержку внутри самого фонового задания. Разберем этот метод по шагам.
В коде фонового задания, которое выполняет отправку, добавим ожидание. Это позволит основной транзакции записи завершиться до того, как задание начнет искать данные.
// Процедура, выполняемая в фоновом задании
Процедура ВыполнитьОтправкуДанных(ПараметрыОтправки) Экспорт
// Приостанавливаем выполнение на 2 секунды (2000 миллисекунд)
// Этого обычно достаточно для фиксации транзакции в базе
ВызватьПаузу(2000);
ДокументОбъект = ПараметрыОтправки.Ссылка.ПолучитьОбъект();
Если ДокументОбъект = Неопределено Тогда
// Обработка ошибки: объект еще не виден или удален
Возврат;
КонецЕсли;
// Логика формирования HTTP-запроса...
КонецПроцедуры
Важный момент: метод ВызватьПаузу() доступен только на сервере в неинтерактивных сеансах (фоновые задания, HTTP-сервисы). Попытка вызвать его на клиенте приведет к исключению.
Как показало обсуждение, наиболее надежным и профессиональным подходом является использование «очереди». Вместо того чтобы пытаться угадать время задержки, мы разделяем процесс записи документа и процесс передачи данных.
Проанализируем алгоритм работы через регистр сведений:
РегистрСведений.ОчередьОтправкиДанных.ПриЗаписи добавляем запись в этот регистр (только ссылку на документ и статус "К отправке").Рассмотрим пример кода для записи в очередь:
Процедура ПриЗаписи(Отказ)
Если Не Отказ Тогда
МенеджерЗаписи = РегистрыСведений.ОчередьОтправкиДанных.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Объект = ЭтотОбъект.Ссылка;
МенеджерЗаписи.ДатаДобавления = ТекущаяДатаСеанса();
МенеджерЗаписи.Состояние = Перечисления.СостоянияОтправки.ВТюрьме; // Ожидает
МенеджерЗаписи.Записать();
КонецЕсли;
КонецПроцедуры
Такой подход гарантирует, что даже если сервер упадет, интернет пропадет или внешняя система будет недоступна, данные не потеряются — они останутся в регистре и будут обработаны при следующей итерации регламентного задания.
Если ваша платформа версии 8.3.23 и выше, мы можем воспользоваться штатным механизмом завершения транзакций. Это позволяет выполнить код гарантированно после того, как данные попали в базу.
Разберем, как это реализовать:
// В модуле объекта документа
Процедура ПриЗаписи(Отказ)
Если Не Отказ Тогда
// Регистрируем процедуру, которая запустится ПОСЛЕ фиксации данных
ДобавитьОбработчикПослеТранзакции("МодульИнтеграции.ЗапуститьФоновоеЗаданиеОтправки", ЭтотОбъект.Ссылка);
КонецЕсли;
КонецПроцедуры
// В общем модуле (с флагом "Сервер")
Процедура ЗапуститьФоновоеЗаданиеОтправки(Ссылка) Экспорт
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(Ссылка);
ФоновыеЗадания.Выполнить("МодульИнтеграции.ВыполнитьHTTPЗапрос", ПараметрыЗадания);
КонецПроцедуры
Этот метод является самым «чистым», так как он не требует лишних пауз и не создает нагрузку на сервер постоянными опросами регистра (как в случае с регламентными заданиями).
Проанализируем еще одну стратегию: если нам нужно отправить данные в реальном времени и мы не хотим зависеть от состояния базы данных, мы можем передать всю необходимую информацию для запроса прямо в параметрах фонового задания. В этом случае заданию не нужно будет читать документ из базы.
Посмотрим на пример подготовки данных:
// В модуле объекта
Процедура ПослеЗаписи(Отказ)
// Подготовим структуру со всеми данными документа
ДанныеДляОтправки = Новый Структура;
ДанныеДляОтправки.Вставить("Номер", ЭтотОбъект.Номер);
ДанныеДляОтправки.Вставить("Дата", ЭтотОбъект.Дата);
ДанныеДляОтправки.Вставить("Сумма", ЭтотОбъект.СуммаДокумента);
ПараметрыЗапуска = Новый Массив;
ПараметрыЗапуска.Добавить(ДанныеДляОтправки);
ФоновыеЗадания.Выполнить("ОбщийМодуль.ОтправитьДанныеБезОбращенияКБД", ПараметрыЗапуска);
КонецПроцедуры
В этом сценарии процедура ОтправитьДанныеБезОбращенияКБД сразу имеет на входе все значения. Ей не важно, завершилась ли транзакция записи, так как она работает с копией данных в оперативной памяти.
Мы рассмотрели несколько способов обхода ограничений транзакционной модели 1С. Подведем итоги:
ВызватьПаузу() внутри фонового задания.Регистре Сведений и регламентное задание.ДобавитьОбработчикПослеТранзакции.Помните, что сетевые ошибки неизбежны. Проанализируем ситуацию: что будет, если HTTP-сервис приемника будет недоступен 5 минут? Если вы используете простое фоновое задание с паузой, вам придется реализовывать механизм повторов самостоятельно. В случае с очередью в Регистре Сведений, система сама будет пытаться отправить данные, пока не добьется успеха, что делает архитектуру вашего решения гораздо более устойчивой.