В среде разработчиков 1С вопрос о допустимости вызова метода Записать(РежимЗаписиДокумента.Проведение) для одного объекта внутри обработчика ОбработкаПроведения другого объекта является одним из самых обсуждаемых. С одной стороны, платформа технически позволяет это сделать, с другой — официальные стандарты разработки фирмы «1С» категорически запрещают подобные манипуляции. Проанализируем ситуацию, разберем технические риски и выясним, почему «так делать нельзя», но иногда очень хочется.
Рассмотрим пример кода, который вызвал жаркую дискуссию:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
СформироватьДвиженияДокумента();
Движения.Записать();
Если ЗначениеЗаполнено(РКОВозвратВознаграждения) и НЕ РКОВозвратВознаграждения.Проведен Тогда
РКООбъект = РКОВозвратВознаграждения.ПолучитьОбъект();
Попытка
РКООбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
Отказ = Истина;
КонецПопытки;
КонецЕсли;
КонецПроцедуры
На первый взгляд, логика прозрачна: если у нас есть связанный документ (РКО), который еще не проведен, мы пытаемся провести его «на лету» в транзакции текущего документа. Если возникает ошибка, мы устанавливаем Отказ = Истина, чтобы откатить проведение основного документа. Однако в этой конструкции заложено сразу несколько «мин замедленного действия».
Вспомним фундаментальные требования к транзакционным системам — ACID (Atomicity, Consistency, Isolation, Durability). Проведение документа в 1С — это всегда одна атомарная транзакция. Когда мы внутри этой транзакции инициируем проведение другого документа, мы создаем вложенную логику, которая значительно усложняет работу СУБД.
Разберем, как это влияет на систему:
Попытка...Исключение или неправильно обработает результат, система может оказаться в противоречивом состоянии.Использование блока Попытка...Исключение внутри ОбработкаПроведения для вызова записи другого объекта — это архитектурная ошибка. Выясним причину: если при записи РКООбъект произойдет ошибка на уровне СУБД (например, конфликт блокировок или нарушение уникальности индекса), транзакция всей сессии помечается платформой как «испорченная» (doomed).
Даже если вы перехватили ошибку в блоке Исключение и установили Отказ = Истина, транзакция уже не может быть зафиксирована. Более того, любая последующая попытка обращения к базе данных в рамках этой же транзакции вызовет системную ошибку: «В данной транзакции уже происходили ошибки!». Таким образом, Попытка не спасает от аварийного завершения процесса, а лишь маскирует первоначальную причину сбоя.
Посмотрим на ситуацию со стороны СУБД. Представим двух пользователей:
Возникает классический тупик: первый ждет освобождения ресурсов второго, второй — первого. Платформа 1С очень чувствительна к порядку захвата ресурсов. Вложенное проведение документов — это кратчайший путь к деградации производительности и постоянным «вылетам» пользователей по таймауту блокировок.
Если бизнес-логика требует, чтобы проведение одного документа влияло на другой, следует использовать более безопасные механизмы. Рассмотрим их подробнее.
Если задача заключается в соблюдении хронологического порядка и актуальности остатков, лучше использовать штатный механизм Последовательности. Документы регистрируются в последовательности, а их перепроведение выполняется специальной обработкой или фоновым заданием в правильном порядке, каждый в своей отдельной транзакции.
Это наиболее современный и рекомендуемый подход. Вместо того чтобы проводить зависимый документ здесь и сейчас, мы фиксируем необходимость этого действия. Разберем по шагам:
ОбработкаПроведения основного документа мы записываем задачу в специальный регистр сведений (очередь задач).ПослеЗаписи или через регламентное задание) запускается фоновое задание.Иногда логику выносят в Подписки на события (событие ОбработкаПроведения). Это позволяет соблюсти принцип слабой связанности (лоу-каплинг), о котором упоминали коллеги в обсуждении. Однако стоит помнить: подписка выполняется в той же транзакции, что и сам документ. Если вы в подписке на Документ А вызываете проведение Документа Б, вы получаете все те же риски блокировок, что и при прямом вызове в модуле объекта.
Вместо того чтобы заставлять Документ А проводить Документ Б, можно сделать так, чтобы оба документа делали движения по одному и тому же регистру. Таким образом, состояние системы будет определяться совокупностью движений, а не «проведенностью» какого-то конкретного связанного объекта.
Подводя итог, проанализируем аргумент: «Если это работает и дает правильный результат клиенту — значит, так можно». В краткосрочной перспективе на базе с одним пользователем это может работать. Но как только нагрузка вырастет, такой код станет причиной невоспроизводимых ошибок, порчи данных и недовольства пользователей из-за постоянных блокировок.
Основные рекомендации:
Записать(РежимЗаписиДокумента.Проведение) внутри другого документа.Попытка...Исключение внутри транзакции проведения, если вы не планируете немедленно прервать транзакцию.Соблюдение этих правил обеспечит стабильную работу системы и избавит вас от необходимости «разгребать» последствия заблокированных таблиц в разгар рабочего дня.