При разработке механизмов интеграции 1С с внешними системами (например, базами данных MS SQL) программисты часто сталкиваются с дилеммой: в какой момент передавать данные во внешнюю базу? Если сделать это слишком рано (в событии ПередЗаписью), транзакция в 1С может откатиться из-за ошибки в других модулях, а во внешней базе изменения уже будут зафиксированы. Если делать это в самом конце события ПриЗаписи, всё равно остается риск, что сработает подписка на событие, расположенная ниже в дереве метаданных, которая установит флаг Отказ = Истина.
В этой статье мы подробно разберем, как работает механизм транзакций в 1С, почему нельзя полагаться на стандартные обработчики для синхронизации "в реальном времени" и какие архитектурные паттерны гарантируют целостность данных между 1С и внешними системами.
Для начала проанализируем последовательность событий при записи объекта. Когда мы вызываем метод Записать(), платформа открывает транзакцию. Внутри этой транзакции последовательно выполняются:
ПередЗаписью (в модуле объекта).ПередЗаписью.ПриЗаписи (в модуле объекта).ПриЗаписи.Важно понимать: если на любом из этих этапов (даже в самой последней подписке) возникнет ошибка или будет установлен Отказ = Истина, вся транзакция откатится. Это означает, что любые изменения, внесенные в базу 1С в рамках этой транзакции, будут аннулированы. Однако, если вы внутри этой транзакции отправили запрос к стороннему API или выполнили хранимую процедуру в MS SQL, это действие не откатится автоматически вместе с транзакцией 1С.
Рассмотрим наиболее надежный и профессиональный подход. Вместо того чтобы пытаться "поймать" момент успешного завершения записи в коде модуля, мы будем использовать свойство транзакционности 1С в свою пользу. Рассмотрим пошагово, как это работает через ПланыОбмена.
План обмена позволяет регистрировать изменения объектов. Регистрация изменений происходит в той же транзакции, что и запись объекта. Если транзакция 1С откатывается, запись в плане обмена тоже исчезает. Если транзакция завершается успешно, запись остается.
Алгоритм действий следующий:
ПланОбмена (назовем его, например, СинхронизацияСВнешнейБазой).ПриЗаписи выполняем регистрацию изменений:
ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, Ссылка);
Далее создаем регламентное задание, которое будет работать вне основной транзакции записи. Оно будет анализировать таблицу изменений плана обмена, считывать данные объектов и передавать их в MS SQL. Поскольку регламентное задание видит только зафиксированные (Commit) данные, вы гарантированно не отправите во внешнюю базу данные, запись которых в 1С сорвалась.
Если планы обмена кажутся избыточными, проанализируем вариант с использованием вспомогательного РегистраСведений. Этот метод также основан на транзакционной целостности.
Рассмотрим пример кода в обработчике ПриЗаписи:
Процедура ПриЗаписи(Отказ)
Если Отказ Тогда
Возврат;
КонецЕсли;
// Вместо прямой отправки в SQL, пишем в свою "очередь"
МенеджерЗаписи = РегистрыСведений.ОчередьСинхронизации.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Объект = Ссылка;
МенеджерЗаписи.ДатаСобытия = ТекущаяДата();
МенеджерЗаписи.Записать();
КонецПроцедуры
Выясним причину надежности этого метода: если после вашего кода кто-то другой (например, в подписке) установит Отказ = Истина, запись в регистре ОчередьСинхронизации также будет отменена платформой. Внешний обработчик (регламентное задание) просто не увидит этой записи и не начнет интеграцию.
Если запись инициируется пользователем вручную из формы, мы можем использовать событие ПослеЗаписи модуля формы. Разберем нюанс: это событие вызывается платформой только после успешного завершения транзакции записи.
Посмотрим на пример реализации в форме:
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
// Этот код выполнится только если объект реально записан в БД
Сообщить("Объект успешно зафиксирован в базе данных.");
ПередатьДанныеВВнешнююСистему(Объект.Ссылка);
КонецПроцедуры
Важное ограничение: этот метод не сработает, если объект записывается программно (например, через Объект.Записать() в фоновом задании или обработке), так как события формы в этом случае не инициализируются.
В ситуациях, когда необходимо выполнить логику сразу после программной записи, проанализируем использование конструкции Попытка...Исключение. Рассмотрим пример кода, который можно разместить в обработке:
ОбъектСправочника = СсылкаНаЭлемент.ПолучитьОбъект();
ОбъектСправочника.Наименование = "Новое наименование";
Попытка
ОбъектСправочника.Записать();
// Если мы дошли до сюда, транзакция успешно зафиксирована (для простых случаев)
ИнтеграцияСSQL.ОтправитьДанные(ОбъектСправочника.Ссылка);
Исключение
// Сюда попадем, если был Отказ = Истина или системная ошибка
ЗаписьЖурналаРегистрации("Ошибка записи", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
КонецПопытки
Однако стоит помнить, что если Записать() вызывается внутри другой, более широкой транзакции (например, при проведении документа), то успешное выполнение ОбъектСправочника.Записать() еще не означает, что данные окончательно попадут в базу — внешняя транзакция всё еще может откатиться позже.
Рассмотрим продвинутый способ проверки факта изменения объекта через анализ свойства ВерсияДанных. Платформа 1С меняет версию данных (строковое представление или метку времени) при каждой успешной записи в базу.
Разберем алгоритм проверки:
ВерсияДанных из базы.Записать().Ссылка.ВерсияДанных.
ВерсияДо = Ссылка.ВерсияДанных;
Попытка
Объект.Записать();
// Для надежности перечитываем версию из БД
НоваяВерсия = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Объект.Ссылка, "ВерсияДанных");
Если НоваяВерсия <> ВерсияДо Тогда
// Успех
КонецЕсли;
Исключение
// Отказ
КонецПопытки
Проанализировав все варианты, можно сделать следующие выводы:
ПриЗаписи) — это антипаттерн. Она замедляет работу системы, создает блокировки и не гарантирует целостность при откате транзакции.ПланыОбмена или Регистры-очереди — самый надежный способ. Мы фиксируем намерение обмена в транзакции 1С, а саму передачу данных делаем отдельным процессом.ДополнительныеСвойства объекта для передачи флагов в подписки и проверяйте результат выполнения метода Записать() в блоке Попытка.Выбор конкретного метода зависит от требований к скорости синхронизации и критичности данных, но в 90% случаев интеграции с внешними SQL-базами использование планов обмена является "золотым стандартом" разработки на платформе 1С.