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