При разработке в распределенных информационных базах (РИБ) программисты часто сталкиваются с необходимостью разграничить выполнение бизнес-логики. Типичный сценарий: в системе настроена подписка на событие ПриЗаписи для набора записей регистра сведений, которая выполняет сложные расчеты, проверки или заполнение связанных данных. Однако при синхронизации (загрузке данных из другого узла) эту логику необходимо отключать. В противном случае мы рискуем получить критическое падение производительности, искажение данных (так как на узле-источнике они уже были проверены и рассчитаны) или зацикливание регистрации изменений.
В этой статье мы подробно рассмотрим, как технически определить, что запись набора записей регистра (или любого другого объекта базы данных) происходит в контексте выполнения обмена РИБ, и разберем лучшие практики обхода этой логики.
Это самый простой, надежный и рекомендуемый фирмой "1С" способ для определения того, что данные записываются в процессе приема (загрузки) сообщения обмена. Платформа 1С:Предприятие спроектирована таким образом, что у каждого регистрируемого в базе данных объекта (справочника, документа, набора записей регистра) существует специальное свойство-флаг.
Давайте проанализируем, как это работает. При получении сообщения обмена и вызове метода ПланыОбмена.ПрочитатьИзменения() платформа самостоятельно считывает сериализованные данные из XML и записывает их в базу данных текущего узла. В момент этой записи свойству ОбменДанными.Загрузка автоматически присваивается значение Истина.
Для реализации обхода подписки нам достаточно добавить простую проверку в самом начале обработчика подписки на событие. Рассмотрим практический пример кода:
Процедура ПриЗаписиНабораЗаписейРегистра(Источник, Отказ, Замещение) Экспорт
// Обязательная проверка на загрузку данных в процессе обмена
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Здесь располагается основная логика, которая должна выполняться
// только при интерактивной или программной работе пользователя в текущей базе
ВыполнитьПолезнуюЛогику(Источник);
КонецПроцедуры
Важное правило разработки: согласно стандартам разработки фирмы "1С", проверка свойства ОбменДанными.Загрузка должна присутствовать во всех подписках и обработчиках событий ПередЗаписью и ПриЗаписи, если только логика внутри них не предназначена специально для обработки загружаемых данных.
Понимание внутренних механизмов платформы помогает избежать неочевидных ошибок. Свойство ОбменДанными.Загрузка = Истина не просто сигнализирует коду о том, что идет обмен. Оно меняет поведение самой СУБД и платформы при записи объекта:
Если мы не прервем выполнение нашего кастомного обработчика при загрузке, код может попытаться обратиться к еще не загруженным связанным объектам, что вызовет фатальную ошибку и аварийно прервет весь процесс обмена данными.
Иногда логика записи регистра сведений зависит от документа-регистратора. На практике часто возникает ситуация, когда запись набора движений выполняется не напрямую, а через коллекцию движений документа при его проведении. В таких случаях программисты пытаются использовать механизм дополнительных свойств (ДополнительныеСвойства) для передачи флагов-исключений.
Но как быть, если подписка назначена на набор записей регистра, а флаг устанавливается в документе? Напрямую прочитать ДополнительныеСвойства набора записей не получится, так как при проведении документа платформа формирует набор и записывает его обособленно. Выясним, как элегантно обойти это ограничение.
Мы можем обратиться к регистратору набора записей напрямую из подписки и проанализировать его свойства. Разберем этот алгоритм по шагам:
// В модуле проведения документа или обработчике перед записью
ЭтотОбъект.ДополнительныеСвойства.Вставить("ОтключитьЛогикуВПодписке", Истина);
ПриЗаписи набора записей регистра сведений мы извлекаем этот флаг через отбор по регистратору:
Процедура ПриЗаписиНабораЗаписей(Источник, Отказ, Замещение) Экспорт
// Проверяем классический флаг загрузки
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Анализируем отбор по регистратору
ОтборРегистратор = Источник.Отбор.Найти("Регистратор");
Если ОтборРегистратор <> Неопределено И ОтборРегистратор.Использование Тогда
СсылкаНаРегистратор = ОтборРегистратор.Значение;
// Проверяем, что ссылка заполнена и имеет корректный тип
Если ЗначениеЗаполнено(СсылкаНаРегистратор) Тогда
Попытка
РегистраторОбъект = СсылкаНаРегистратор.ПолучитьОбъект();
Если РегистраторОбъект <> Неопределено
И РегистраторОбъект.ДополнительныеСвойства.Свойство("ОтключитьЛогикуВПодписке") Тогда
// Если флаг найден, прекращаем выполнение подписки
Возврат;
КонецЕсли;
Исключение
// Обработка исключений при чтении объекта (например, при нехватке прав у сеанса обмена)
ЗаписьЖурналаРегистрации("ОбменДанными.Подписки",
УровеньЖурналаРегистрации.Предупреждение,,,
ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Бывают обратные задачи, когда тяжелую логику в подписке нужно отключить не при приеме данных, а в процессе выгрузки (экспорта). Например, когда информационная база обходит зарегистрированные изменения для сериализации в XML-сообщение обмена. В этот момент свойство ОбменДанными.Загрузка равно Ложь, так как данные не записываются в базу из внешнего источника, но нагрузка на систему все равно возрастает. Посмотрим на альтернативные варианты решения этой задачи:
Вариант А. Анализ фонового задания обмена
В типовых конфигурациях на базе БСП (Библиотеки Стандартных Подсистем) сеансы обмена данными запускаются в фоне по расписанию. Мы можем программно проверить наименование текущего фонового задания и, если оно связано с обменом или синхронизацией, пропустить выполнение кода подписки.
ТекущееЗадание = ФоновыеЗадания.ТекущиеДанные();
Если ТекущееЗадание <> Неопределено Тогда
НаименованиеЗадания = НРег(ТекущееЗадание.Наименование);
// Проверяем вхождение ключевых слов синхронизации БСП
Если Найти(НаименованиеЗадания, "обмен") > 0
Или Найти(НаименованиеЗадания, "синхронизация") > 0 Тогда
Возврат; // Выполняется фоновый обмен, выходим
КонецЕсли;
EndЕсли;
Вариант Б. Проверка специализированного пользователя
Если для выполнения процедур обмена (РИБ, интеграции) заведен отдельный служебный пользователь, мы можем проверять его имя в подписке. Это весьма надежный способ контроля сеанса:
ИмяТекущегоПользователя = ПользователиИнформационнойБазы.ТекущийПользователь().Имя;
Если ВРег(ИмяТекущегоПользователя) = "АДМИНИСТРАТОР_ОБМЕНА"
Или ВРег(ИмяТекущегоПользователя) = "СЛУЖЕБНЫЙ_РИБ" Тогда
Возврат;
КонецЕсли;
Вариант В. Использование параметров сеанса
Если обмен запускается через ваши собственные регламентные задания или обработки, идеальным решением будет создание специального параметра сеанса, например, ПараметрыСеанса.ВыполняетсяОбменДанными (тип Булево). Перед началом выгрузки или загрузки мы устанавливаем этот параметр в значение Истина, а по завершении возвращаем в Ложь. В подписке останется сделать простую проверку:
// В модуле подписки:
Если ПараметрыСеанса.ВыполняетсяОбменДанными Тогда
Возврат;
КонецЕсли;
Иногда подписка на событие используется для регистрации изменений объектов на узлах обмена. Если при загрузке данных сработает подписка и зарегистрирует этот же объект на отправку обратно, возникнет бесконечный цикл обмена («эхо-регистрация»). Для решения этой проблемы в БСП разработан стандартный механизм.
Чтобы отключить авторегистрацию при записи объекта во время загрузки, в БСП передают специальный параметр в дополнительные свойства объекта:
Источник.ОбменДанными.Загрузка = Истина;
Источник.ДополнительныеСвойства.Вставить("ОтключитьМеханизмРегистрацииОбъектов", Истина);
Этот флаг анализируется встроенными механизмами БСП, предотвращая повторную регистрацию изменений на других узлах и сохраняя целостность топологии РИБ.
Для того чтобы убедиться, что ваша подписка корректно отрабатывает и умеет «засыпать» во время обмена, рекомендуется написать небольшую тестовую обработку. С ее помощью мы сымитируем поведение платформы при загрузке данных.
Разберем по шагам, как написать такой тест:
&НаСервере
Процедура ПротестироватьЗаписьНабораНаСервере()
// Инициализируем набор записей тестируемого регистра
НаборЗаписей = РегистрыСведений.МойТестовыйРегистр.СоздатьНаборЗаписей();
// Устанавливаем любой корректный отбор (например, по измерению или регистратору)
НаборЗаписей.Отбор.Пользователь.Установить(ПараметрыСеанса.ТекущийПользователь);
// Имитируем поведение платформы при загрузке из XML
НаборЗаписей.ОбменДанными.Загрузка = Истина;
// Формируем тестовую строку данных
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Пользователь = ПараметрыСеанса.ТекущийПользователь;
НоваяЗапись.Значение Настройки = "ТестовоеЗначениеРИБ";
// Записываем набор. В этот момент должна сработать наша подписка
Попытка
НаборЗаписей.Записать();
Сообщить("Запись успешно произведена в режиме имитации РИБ!");
Исключение
Сообщить("Ошибка при записи: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
ПриЗаписи.Источник.ОбменДанными.Загрузка имеет значение Истина, а выполнение алгоритма успешно завершается на операторе Возврат.Давайте разберем основные грабли, на которые наступают даже опытные разработчики при интеграции кода в РИБ:
ОбменДанными.Загрузка ставится в самом обработчике подписки, но если внутри него вызывается общий модуль, который выполняет запись других связанных объектов, у этих «дочерних» объектов свойство Загрузка может быть равно Ложь (так как они записываются программно из кода, а не платформой из XML). Потребуется явно передавать этот флаг в подчиненные процедуры.ПриЗаписи вы решите дозаполнить реквизиты объекта и вызвать метод Записать(), платформа сбросит флаг Загрузка в Ложь, что приведет к зацикливанию или ошибкам прав доступа.Мы подробно проанализировали основные инструменты платформы 1С:Предприятие 8 и прикладных решений БСП для управления поведением кода в процессе РИБ-синхронизации. Использование свойства ОбменДанными.Загрузка решает подавляющее большинство задач, а для оставшихся сложных архитектурных сценариев отлично подходят параметры сеанса, анализ фоновых заданий и передача признаков через дополнительные свойства объектов.