Как гарантированно отловить событие удаления справочника или документа в 1С для онлайн-синхронизации без задержек?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При построении распределенных информационных систем и интеграции платформы «1С:Предприятие 8» с внешними сервисами (такими как корпоративные порталы, CRM, интернет-магазины или шины данных) разработчики часто сталкиваются со сложной технической задачей. Необходимо обеспечить отправку данных об удалении объектов во внешнюю систему в режиме реального времени. При этом крайне важно гарантировать, что сигнал об удалении уйдет только тогда, когда объект действительно физически удален из базы данных.

Платформа 1С предоставляет стандартные обработчики событий, такие как событие ПередУдалением в модуле объекта или в подписках на события. Однако любой опытный разработчик знает, что данное событие срабатывает до фиксации транзакции в СУБД. Это значит, что если в процессе удаления произойдет какая-либо ошибка (например, сработает контроль ссылочной целостности, сбоит сеть или наложится блокировка базы данных), транзакция будет отменена, а объект останется в базе. Если мы отправим HTTP-запрос или сообщение в брокер непосредственно из обработчика ПередУдалением, внешняя система зафиксирует удаление, которого на самом деле не произошло. Это классическая архитектурная проблема рассинхронизации данных.

В этой статье мы подробно проанализируем, почему стандартные подходы не работают для сценариев «онлайн», разберем паттерн Transactional Outbox, научимся обходить задержки регламентных заданий и рассмотрим альтернативные методы отслеживания удалений с использованием СУБД и Журнала регистрации.

Проблема двойной записи (Dual Write) при интеграции

Выясним причину, почему прямая отправка сетевых запросов из транзакционных обработчиков 1С считается грубейшей архитектурной ошибкой. Представим классический сценарий синхронизации:

  1. Пользователь нажимает кнопку удаления элемента справочника Номенклатура.
  2. Платформа запускает транзакцию и вызывает процедуру ПередУдалением.
  3. Внутри этой процедуры разработчик пишет код отправки POST-запроса на внешний Web-сервис. Запрос успешно уходит, внешний сервис удаляет товар у себя.
  4. Платформа 1С продолжает выполнение транзакции и пытается выполнить физическое удаление в СУБД.
  5. СУБД возвращает ошибку: «На данный элемент ссылается документ Реализация товаров и услуг». Транзакция аварийно откатывается.
  6. Итог: товар остался в 1С, но на сайте или в CRM-системе он уже удален. Данные рассинхронизированы, бизнес-процесс нарушен.

Попытки решить эту проблему через вызовы интерактивных оповещений на клиенте также обречены на провал, поскольку методы вроде ОписаниеОповещения не могут работать в серверных транзакционных модулях. Следовательно, нам нужен надежный асинхронный механизм, гарантирующий согласованность данных в конечном итоге (Eventual Consistency).

Решение 1: Паттерн Transactional Outbox (Транзакционный исходящий ящик) на платформе 1С

Рассмотрим самый надежный способ интеграции, принятый в современных микросервисных архитектурах — паттерн Transactional Outbox. Его суть заключается в том, что запись о событии удаления производится в ту же самую базу данных и в рамках той же самой транзакции, что и удаление основного объекта. Платформа СУБД гарантирует атомарность: либо запишутся оба изменения (удаление объекта и регистрация события), либо ни одно из них.

Разберем по шагам, как реализовать этот паттерн в 1С:

  1. Создадим независимый регистр сведений ИсходящиеСобытияСинхронизации со следующей структурой:
    • Измерение ИдентификаторОбъекта (Тип: Строка или УникальныйИдентификатор)
    • Ресурс ТипОбъекта (Тип: Строка)
    • Ресурс ТелоСообщения (Тип: Строка, хранит XML или JSON с реквизитами объекта, которые понадобятся внешней системе, так как после удаления прочитать их из базы будет невозможно)
    • Реквизит ДатаСобытия (Тип: Дата)
  2. Напишем обработчик в подписке на событие ПередУдалением.

Посмотрим на пример реализации записи события в транзакционный ящик:


Процедура ПередУдалениемОбъекта(Источник, Отказ) Экспорт
    
    Если Отказ Тогда
        Возврат;
    КонецЕсли;
    
    // Получаем уникальный идентификатор удаляемого объекта
    СсылкаНаОбъект = Источник.Ссылка;
    ГУИД = Строка(СсылкаНаОбъект.УникальныйИдентификатор());
    ИмяМетаданных = Источник.Метаданные().ПолноеИмя();
    
    // Сериализуем данные, которые понадобятся внешней системе.
    // После физического удаления объекта мы не сможем прочитать его реквизиты!
    ДанныеОбъекта = Новый Структура;
    ДанныеОбъекта.Вставить("Код", Источник.Код);
    ДанныеОбъекта.Вставить("Наименование", Источник.Наименование);
    
    // Преобразуем структуру в формат JSON
    ЗаписьJSON = Новый ЗаписьJSON;
    ЗаписьJSON.УстановитьСтроку();
    ЗаписатьJSON(ЗаписьJSON, ДанныеОбъекта);
    СтрокаДанных = ЗаписьJSON.Закрыть();
    
    // Записываем событие в наш транзакционный исходящий ящик
    НаборЗаписей = РегистрыСведений.ИсходящиеСобытияСинхронизации.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.ИдентификаторОбъекта.Установить(ГУИД);
    
    НоваяЗапись = НаборЗаписей.Добавить();
    НоваяЗапись.ИдентификаторОбъекта = ГУИД;
    НоваяЗапись.ТипОбъекта = ИмяМетаданных;
    НоваяЗапись.ТелоСообщения = СтрокаДанных;
    НоваяЗапись.ДатаСобытия = ТекущаяДатаСеанса();
    
    // Запись происходит в рамках текущей транзакции удаления
    НаборЗаписей.Записать(Истина);
    
КонецПроцедуры

Если транзакция удаления объекта завершится успешно, запись в регистре ИсходящиеСобытияСинхронизации гарантированно появится. Если произойдет откат транзакции — СУБД автоматически откатит и запись в регистре сведений. Проблема ложных срабатываний полностью решена!

Мгновенная обработка исходящего ящика: обходим ограничения регламентных заданий

Теперь перед нами стоит задача: как отправить накопленные в регистре ИсходящиеСобытияСинхронизации события во внешнюю систему с минимальной задержкой? Стандартные регламентные задания имеют ограничение на частоту запуска (обычно не чаще, чем раз в несколько секунд), и их частый запуск создает высокую нагрузку на СУБД.

Для реализации «честного онлайн» обмена с минимальным временем отклика мы можем использовать паттерн Демон-пуллер (Daemon Puller). Это фоновое задание, которое запускается один раз при старте системы и работает непрерывно в бесконечном цикле.

Проанализируем пример кода такого фонового процесса:


Процедура ВыполнитьНепрерывнуюОтправку() Экспорт
    
    УстановитьПривилегированныйРежим(Истина);
    
    Пока Истина Цикл
        
        // Выбираем порцию событий из регистра
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ ПЕРВЫЕ 100
            |	События.ИдентификаторОбъекта КАК ИдентификаторОбъекта,
            |	События.ТипОбъекта КАК ТипОбъекта,
            |	События.ТелоСообщения КАК ТелоСообщения
            |ИЗ
            |	РегистрСведений.ИсходящиеСобытияСинхронизации КАК События
            |УПОРЯДОЧИТЬ ПО
            |	События.ДатаСобытия ВОЗР";
            
        РезультатЗапроса = Запрос.Выполнить();
        Если РезультатЗапроса.Пустой() Тогда
            // Если событий нет, приостанавливаем поток на 200 миллисекунд
            // Это предотвращает 100% загрузку процессора холостыми запросами
            ПриостановитьВыполнение(0.2);
            Продолжить;
        КонецЕсли;
        
        Выборка = РезультатЗапроса.Выбрать();
        Пока Выборка.Следующий() Цикл
            
            // Выполняем HTTP-запрос во внешнюю систему
            УспешнаяОтправка = ОтправитьЗапросВне(Выборка.ИдентификаторОбъекта, Выборка.ТипОбъекта, Выборка.ТелоСообщения);
            
            Если УспешнаяОтправка Тогда
                // Удаляем обработанное событие
                Набор = РегистрыСведений.ИсходящиеСобытияСинхронизации.СоздатьНаборЗаписей();
                Набор.Отбор.ИдентификаторОбъекта.Установить(Выборка.ИдентификаторОбъекта);
                Набор.Записать(Истина);
            КонецЕсли;
            
        КонецЦикла;
        
        // Небольшая пауза между итерациями обработки очереди
        ПриостановитьВыполнение(0.1);
        
    КонецЦикла;
    
КонецПроцедуры

Использование метода ПриостановитьВыполнение() (или аналогичных механизмов засыпания потока) позволяет удерживать фоновое задание в активном состоянии без ущерба для производительности сервера. Задержка при передаче событий удаления в таком случае составит не более 100–300 миллисекунд, что полностью удовлетворяет требованиям к системам реального времени.

Решение 2: Использование штатных планов обмена

Платформа 1С имеет мощный встроенный механизм отслеживания изменений — ПланыОбмена. При физическом удалении объекта платформа автоматически регистрирует изменения для всех узлов, входящих в план обмена, сохраняя ссылку на удаленный объект. Особенность заключается в том, что при попытке получить данные по этой ссылке платформа вернет значение ОбъектНеНайден, содержащее исходный GUID.

Преимущество этого подхода в том, что нам не нужно вручную прописывать код в обработчиках ПередУдалением для каждого справочника или документа. Достаточно включить нужные объекты в состав плана обмена.

Разберем по шагам, как организовать отправку через планы обмена:

  1. Создаем специализированный план обмена, например, ОнлайнИнтеграция.
  2. Включаем в его состав нужные справочники и документы, установив авторегистрацию.
  3. В нашем непрерывном фоновом процессе опрашиваем таблицу изменений этого плана обмена.

Посмотрим на пример обработки зарегистрированных изменений удаления:


Процедура ОбработатьИзмененияПланаОбмена(УзелСсылка) Экспорт
    
    // Получаем выборку изменений для конкретного узла
    ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(УзелСсылка, 100);
    
    Пока ВыборкаИзменений.Следующий() Цикл
        
        ДанныеИзменения = ВыборкаИзменений.Получить();
        ТипОбъекта = Метаданные.НайтиПоТипу(ТипЗнч(ДанныеИзменения)).ПолноеИмя();
        
        // Проверяем, удален ли объект из базы данных
        Если ДанныеИзменения.ПолучитьОбъект() = Неопределено Тогда
            
            // Объект удален физически, у нас есть только битая ссылка с GUID
            Идентификатор = Строка(ДанныеИзменения.УникальныйИдентификатор());
            
            // Отправляем сигнал об удалении во внешнюю систему
            Результат = ОтправитьУведомлениеОбУдалении(Идентификатор, ТипОбъекта);
            
            Если Результат Тогда
                // Подтверждаем получение изменения для узла
                ПланыОбмена.УдалитьРегистрациюИзменений(УзелСсылка, ДанныеИзменения);
            КонецЕсли;
            
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

Этот метод чрезвычайно надежен, поддерживается платформой на уровне ядра и не требует дублирования данных в промежуточные регистры сведений.

Решение 3: Анализ Журнала регистрации как альтернативный асинхронный канал

Если внесение изменений в конфигурацию крайне нежелательно или заблокировано (например, конфигурация находится на поддержке без возможности изменения), мы можем использовать Журнал регистрации платформы 1С.

При успешном удалении любого объекта платформа гарантированно делает запись в Журнале регистрации с событием _$Data$_.Delete. Запись происходит только после успешной фиксации транзакции СУБД, что автоматически исключает проблему ложных срабатываний.

Для реализации этого сценария мы можем написать фоновое задание в 1С, которое периодически считывает новые записи из журнала регистрации с помощью метода ВыгрузитьЖурналРегистрации(). Пример настройки отбора для чтения событий удаления:


ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Событие", "_$Data$_.Delete");
ПараметрыОтбора.Вставить("ДатаНачала", ТекущаяДата() - 60); // за последнюю минуту

ТаблицаЖурнала = Новый ТаблицаЗначений;
ВыгрузитьЖурналРегистрации(ТаблицаЖурнала, ПараметрыОтбора, "Дата, Данные, ДанныеПредставление, Метаданные");

Для Каждого СтрокаЛога Из ТаблицаЖурнала Цикл
    // СтрокаЛога.Данные содержит UUID удаленного объекта
    // СтрокаЛога.Метаданные содержит имя метаданных объекта
    ОтправитьСигналУдаления(СтрокаЛога.Данные, СтрокаЛога.Метаданные.ПолноеИмя());
КонецЦикла;

Этот подход избавляет конфигурацию от лишнего транзакционного кода, но требует регулярного чтения журнала регистрации, что при высокой интенсивности записи может создавать дополнительную нагрузку на дисковую подсистему сервера приложений 1С.

Решение 4: Использование внешних CDC-инструментов на уровне СУБД

Для высоконагруженных систем корпоративного уровня (Enterprise) изменение кода 1С для целей интеграции может быть узким местом из-за блокировок базы данных. В таких случаях применяется подход Change Data Capture (CDC) на уровне СУБД (например, MS SQL Server, PostgreSQL).

Инструменты класса CDC (такие как Debezium) подключаются непосредственно к бинарным логам транзакций СУБД. Они перехватывают операции DELETE над таблицами справочников и документов 1С. Поскольку СУБД записывает эти операции в логи только после успешной команды COMMIT, интеграционная шина (например, Kafka) мгновенно получает гарантированно актуальные данные о физическом удалении записи.

Этот вариант требует глубокого понимания структуры таблиц базы данных 1С (так как имена таблиц в СУБД генерируются платформой динамически, например, _Reference45) и настройки дополнительного системного ПО, но он обеспечивает максимальное быстродействие и абсолютную независимость от кода платформы 1С.

← На главную