Как настроить отложенную обработку только для реально измененных объектов?

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

В разработке на платформе 1С часто возникает задача выполнять ресурсоемкие операции с объектами в фоновом режиме, например, пересчеты, интеграции или сложные проверки. Для этого обычно используют механизм планов обмена, который регистрирует измененные объекты. Однако стандартная авторегистрация имеет недостаток: она срабатывает при любой записи объекта, даже если пользователь просто открыл форму, ничего не поменял и нажал "Записать". В результате в очередь на обработку попадают "пустые" изменения, что приводит к лишней нагрузке на систему.

Рассмотрим, как решить эту проблему и регистрировать для отложенной обработки только те объекты, в которых действительно произошли изменения данных. Разберем два основных подхода: с использованием механизма "История данных" и путем прямого сравнения состояний объекта.

Решение 1: Использование механизма "История данных"

Это наиболее современный и рекомендуемый подход, который перекладывает задачу сравнения версий на саму платформу. Механизм "История данных" (версионирование) умеет не только хранить версии, но и определять различия между ними. Мы можем использовать эту возможность для нашей цели.

Суть метода: мы вручную управляем записью версий и анализируем результат. Если платформа обнаружила изменения между новой и предыдущей версией, мы регистрируем объект в плане обмена. Если изменений нет, мы ничего не делаем и даже можем удалить "пустую" версию, чтобы не засорять историю.

Разберем реализацию по шагам:

  1. Настройка объектов метаданных

    В конфигураторе необходимо выполнить несколько настроек для нужного нам справочника (или другого объекта):

    • В свойствах справочника включите использование "Истории данных".
    • Установите флаг "Выполнять обработку после записи версий истории". Это позволит нам перехватить событие после того, как платформа создаст и сравнит версии.
    • Создайте специальный план обмена для нашей задачи (например, РегистрацияДляПостобработки). В его составе укажите наш справочник и отключите для него авторегистрацию. Мы будем регистрировать изменения программно.
  2. Программный код в модуле объекта

    В модуле объекта справочника нам нужно перехватить событие ПриЗаписи, чтобы инициировать создание новой версии и отменить стандартную запись в историю, которая может произойти после нашей.

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

    Важный момент: Мы передаем параметры в метод ЗаписатьВерсию, такие как пользователь и комментарий. Платформа использует эти данные для создания записи в истории, а главное — в этот момент происходит сравнение текущего состояния объекта с последней сохраненной версией.

  3. Программный код в модуле менеджера

    Самая важная логика будет находиться в модуле менеджера справочника, в обработчике события ОбработкаПослеЗаписиВерсийИсторииДанных. Этот обработчик вызывается после того, как версия была записана, и в его параметрах содержится информация о том, были ли найдены различия.

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

В результате мы получаем элегантную систему: в план обмена попадают только объекты с реальными изменениями, а история версий остается чистой от "пустых" сохранений.

Решение 2: Сравнение состояний объекта через хеш-сумму

Если по каким-то причинам использование "Истории данных" невозможно (например, она отключена или используется для других целей), можно реализовать сравнение вручную. Наиболее надежный способ — вычисление и сравнение хеш-сумм состояний объекта до и после записи.

Суть метода: в обработчике ПриЗаписи мы получаем данные объекта до изменения (из базы данных) и данные записываемого объекта. Преобразуем их в строки, вычисляем для каждой хеш-сумму (например, MD5) и сравниваем. Если хеши разные — значит, данные изменились.

  1. Отключаем авторегистрацию

    Как и в первом решении, для нужного объекта в плане обмена отключаем авторегистрацию.

  2. Реализуем функцию вычисления хеша

    Нам понадобится общая функция, которая может вычислить хеш-сумму для любого значения. Часто для этого данные сериализуют в XML или JSON.

    
    Функция ВычислитьХешОбъекта(Данные) Экспорт
    
        // Для надежности лучше не сериализовать весь объект,
        // а собрать строку только из значимых реквизитов,
        // чтобы исключить влияние служебных полей.
        // Но для примера используем сериализацию.
    
        СтрокаДляХеширования = ЗначениеВСтрокуВнутр(Данные);
        
        Хеширование = Новый ХешированиеДанных(ХешФункция.MD5);
        Хеширование.Добавить(СтрокаДляХеширования);
        
        Возврат Хеширование.ХешСумма;
    
    КонецФункции
    

    Важный нюанс: Сериализация всего объекта (например, через ЗначениеВСтрокуXML) может давать ложные срабатывания, так как в строку могут попадать системные атрибуты. Более надежный подход — собрать строку для хеширования вручную, конкатенируя значения только тех реквизитов, которые для нас важны.

  3. Программный код в модуле объекта

    В событии ПриЗаписи реализуем логику сравнения.

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

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

Заключение

Оба подхода позволяют эффективно решить задачу отложенной обработки только реально измененных объектов.

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

← На главную