Рассмотрим распространенную задачу разработки в 1С: требуется отправлять email-уведомления определенным пользователям при создании нового документа, а также при изменении статуса этого документа (например, на статус "Закрыта"). При этом необходимо гарантированно фиксировать факт отправки, чтобы исключить повторную рассылку одних и тех же писем при перепроведении.
Многие начинающие разработчики пытаются решить эту задачу "в лоб": отправляют письмо прямо в модуле объекта и там же устанавливают булево значение (галочку) УведомлениеОтправлено = Истина. Проанализируем, почему этот подход приводит к ошибкам, потери данных и зависаниям базы, а также подробно разберем правильное архитектурное решение с использованием регистров сведений.
Часто программисты пишут код изменения реквизитов в процедуре ПриЗаписи() модуля документа. В отладчике мы видим, что значение реквизиту присваивается, но при повторном открытии документа галочка снова сброшена в Ложь. Выясним причину такого поведения.
Запись документа в 1С происходит строго внутри транзакции базы данных. События распределяются следующим образом:
INSERT или UPDATE к таблицам СУБД.Если мы изменим реквизит ЭтотОбъект.Уведомление = Истина внутри ПриЗаписи(), мы поменяем его только в оперативной памяти сервера для данного экземпляра объекта. В саму базу данных это изменение уже не попадет, так как этап формирования и отправки данных в СУБД уже пройден. Использовать метод Записать() внутри процедуры ПриЗаписи() категорически запрещено — это приведет к зацикливанию или грубым нарушениям логики транзакции.
Вторая и куда более критичная ошибка — это попытка вызывать процедуры отправки электронной почты непосредственно в обработчиках записи документа (как в ПередЗаписью(), так и в ПриЗаписи()).
Посмотрим на проблемы, к которым это гарантированно приведет:
ПередЗаписью() еще может не быть номера. Если мы сформируем текст письма на этом этапе, в уведомлении будет пустое поле номера документа.Отказ = Истина. Транзакция откатится, документа в базе не будет, но письмо уже уйдет получателю.ПриЗаписи() выполняется внутри открытой транзакции СУБД, удерживая эксклюзивные блокировки на таблицы. Если почтовый SMTP-сервер ответит с задержкой в 10-15 секунд (или вовсе зависнет), запись документа "повиснет" на это время. Все остальные пользователи, пытающиеся работать с этими таблицами, будут заблокированы, что может парализовать работу целого отдела.Для того чтобы понять, что статус поменялся именно сейчас на "Закрыта" (и нужно инициировать отправку), нам не нужны дополнительные галочки в самом документе. В 1С применяется механизм сравнения старых данных (из базы) и новых (введенных на форме пользователем).
Для этого в обработчике ПередЗаписью() или ПриЗаписи() мы обращаемся к свойству Ссылка. Свойство Ссылка хранит данные, которые находятся в базе до фиксации текущих изменений, а свойства самого объекта хранят новые данные.
Посмотрим на пример кода:
Процедура ПриЗаписи(Отказ)
// Проверяем, что это новый документ
Если ЭтоНовый() Тогда
// Фиксируем необходимость отправить письмо о создании
ЗафиксироватьЗадачуНаОтправку(ЭтотОбъект.Ссылка, "Создание");
КонецЕсли;
// Проверяем изменение статуса
Если Ссылка.Статус <> Статус И Статус = Перечисления.СтатусыЗаявок.Закрыта Тогда
// Фиксируем необходимость отправить письмо о закрытии
ЗафиксироватьЗадачуНаОтправку(ЭтотОбъект.Ссылка, "Закрытие");
КонецЕсли;
КонецПроцедуры
Архитектурно верное решение задачи, принятое в качестве стандарта разработки 1С, — это паттерн "Очередь сообщений". Мы должны вынести долгий процесс (отправку email) за пределы транзакции записи документа.
Разберем по шагам, как это реализовать:
Шаг 1. Создание регистра сведений.
Создаем независимый непериодический Регистр Сведений, например, ОчередьОтправкиПисем.
Добавляем измерения: Документ (тип ДокументСсылка) и ВидСобытия (Строка или Перечисление: "Создание", "Закрытие").
Добавляем ресурсы: Отправлено (Булево), КоличествоПопыток (Число), ТекстОшибки (Строка).
Шаг 2. Легкая запись намерений.
При записи документа (как показано в примере кода выше), мы не отправляем письма. Мы лишь делаем быструю программную запись в наш регистр сведений, что для такого-то документа произошло такое-то событие. Эта операция занимает доли секунды, не тормозит работу пользователя и безопасно фиксируется в той же транзакции.
Шаг 3. Регламентное задание для рассылки.
Создаем Общий модуль (с галочкой "Сервер") и Регламентное задание, которое запускается по расписанию (например, каждые 2-3 минуты).
Внутри процедуры регламентного задания мы:
Отправлено = Ложь и КоличествоПопыток < 3.Отправлено = Истина (или вообще удаляем запись из регистра).Если мы ведем разработку в современных типовых конфигурациях 1С (УТ, ERP, БП, КА и др.), нам даже не придется писать регистр сведений с нуля. В них встроена Библиотека стандартных подсистем (БСП).
В БСП уже есть готовая подсистема работы с почтовыми сообщениями и своя встроенная очередь. Нам достаточно при записи документа программно подготовить параметры письма и передать их в типовой экспортный метод общего модуля рассылки. Дальше типовые фоновые задания 1С сами отправят письмо, зафиксируют статус отправки и, при необходимости, прикрепят его к карточке контрагента в истории взаимодействий.
Применяя подход с асинхронной отправкой через регистры сведений и регламентные задания, мы обеспечиваем высокую стабильность системы, отказоустойчивость при сетевых сбоях и комфортную скорость работы пользователей в базе 1С.