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