Групповая модификация объектов в условиях инкапсуляции логики связей

20.03.26

Разработка - Математика и алгоритмы

Решение проблемы согласованной групповой модификации объектов в транзакции, в условиях инкапсуляции логики связей, с исключением взаимных блокировок. Паттерн "Единица работы" (пакет изменений).

Определение проблемы

Есть группа взаимосвязанных документов, содержащих зависимые реквизиты. При изменении одного документа необходимо отследить зависимости и обновить зависимые реквизиты в связанных документах.

Логика отслеживания зависимостей инкапсулирована в модуле каждого документа. Упрощенно, в модулях документов определена экспортная процедура:

Процедура ОбновитьЗависимыеРеквизиты(Знач Источник) Экспорт
	ОбновитьЗависимыеРеквизитыТекущегоДокумента(Источник);
	Записать(РежимЗаписиДокумента.Проведение);
	ЗависимыеДокументы = ПолучитьЗависимыеДокументы();
	Для каждого Ссылка Из ЗависимыеДокументы Цикл
		ДокОбъект = Ссылка.ПолучитьОбъект();
		ДокОбъект.ОбновитьЗависимыеРеквизиты(ЭтотОбъект);
	КонецЦикла;
КонецПроцедуры

Все документы группы должны быть изменены согласованно, поэтому инициирующая сторона должна запустить изменение в транзакции:

НачатьТранзакцию();
Попытка
	// ...здесь нужно заблокировать всю группу, но как?
	КорневойДокумент.ОбновитьЗависимыеРеквизиты(Источник);
	ЗафиксироватьТранзакцию();
Исключение
	ОтменитьТранзакцию();
	ВызватьИсключение;
КонецПопытки;

Возникающая при этом проблема озвучена в комментарии к вышеприведенному коду.

Если мы изменяем группу объектов в транзакции, то при параллельной работе возникает риск взаимной блокировки (deadlock). Чтобы его исключить, мы должны наложить управляемые блокировки на все изменяемые объекты до того, как начнем записывать первый из них. Но как инициирующая сторона сможет определить множество блокируемых документов, если логика определения этого множества инкапсулирована?

Решение "в лоб" может выглядеть следующим образом: в модуле объекта определить функцию, которая вернет массив ссылок на документы. Инициирующая сторона может использовать этот массив для блокировки всех документов перед началом их обновления.

НачатьТранзакцию();
Попытка
	ДокументыГруппы = КорневойДокумент.ВсеДокументыГруппы();
	Блокировка = Новый БлокировкаДанных;
	Для каждого Ссылка Из ДокументыГруппы Цикл
		//...добавить блокировку по ссылке
	КонецЦикла;
	Блокировка.Заблокировать();
	КорневойДокумент.ОбновитьЗависимыеРеквизиты(Источник);
	ЗафиксироватьТранзакцию();
Исключение
	ОтменитьТранзакцию();
	ВызватьИсключение;
КонецПопытки;

Очевидный недостаток этого решения - выполнение в два прохода. Первый раз придется пройтись по графу документов для определения множества блокируемых объектов, и второй раз - для обновления данных.

 

Паттерн "Единица работы"

В книге Мартина Фаулера "Шаблоны корпоративных приложений" описан паттерн "Единица работы" (Unit of Work).

Суть в следующем: производя серию изменений взаимосвязанных объектов, мы не записываем каждый объект в БД сразу после его изменения в памяти, а регистрируем операцию изменения в специальном журнале. В самом конце мы выполняем транзакционную запись всех измененных объектов в БД, предварительно накладывая все необходимые блокировки.

Порядок действий таков:

  1. Выполнить изменения объектов в памяти, при этом внося измененные объекты в журнал.
  2. После выполнения всех требуемых изменений - начать транзакцию.
  3. Заблокировать каждый объект журнала.
  4. Выполнить операцию с каждым объектом журнала (записать, удалить, пометить на удаление).
  5. Зафиксировать транзакцию.

В объектной технике исполнения журнал и все методы работы с ним реализуются в виде объекта, который можно легко передавать по иерархии вложенных вызовов. Таким образом сохраняется инкапсуляция внутренней логики изменяемых объектов: сторона, инициирующая изменение, не обязана ничего знать о природе взаимосвязей внутри группы. Изменяемые объекты сами регистрируют операции изменения в журнале, инициирующей стороне остается только зафиксировать изменения в самом конце.

 

Реализация

Термин "Единица работы" мне показался не очень внятным, поэтому я дал своему объекту более понятное имя "Пакет изменений". Он представляет из себя обработку, реализация которой приведена под спойлером.

 
 Обработка ПакетИзменений - Модуль объекта (меньше 200 строк)

 

Данная реализация поддерживает работу со всеми объектами ссылочного типа.

Основные методы:

  • Записать(Объект, Параметр1, Параметр2) - добавить операцию записи объекта. Два последних параметра должны использоваться только для документов.
  • УстановитьПометкуУдаления(Объект, Пометка, ВключаяПодчиненные) - добавить операцию установки пометки удаления. Последний параметр должен использоваться только для иерархических типов.
  • Удалить(Объект) - добавить операцию удаления объекта.
  • ВыполнитьВТранзакции() - применить все блокировки и выполнить все операции в транзакции.

По умолчанию ПакетИзменений использует режим автоматической блокировки объектов. Не следует путать с термином "автоматические блокировки" платформы 1С. В данном случае это его собственный режим, который означает, что для каждого добавляемого в журнал объекта будет также автоматически добавляться элемент в пакетную управляемую блокировку. Но само блокирование при этом не выполняется - оно будет выполнено только при вызове метода ВыполнитьВТранзакции.

Если требуется более тонкое управление блокировками, то для этого предусмотрена группа дополнительных методов:

  • АвтоматическаяБлокировка(Состояние) - Позволяет включить/выключить режим автоматической блокировки, а также получить его текущее состояние.
  • ДобавитьБлокировкуПоСсылке(Ссылка, Режим) - Добавляет элемент пакетной блокировки для блокирования объекта по ссылке.
  • ДобавитьБлокировку(ПространствоБлокировки) - Создает и возвращает новый элемент пакетной управляемой блокировки - для добавления произвольных блокировок.
  • ЗаблокироватьОбъекты() - Применяет пакетную блокировку со всеми ранее добавленными элементами блокировки.
  • ВыполнитьОперации() - Выполняет все операции с объектами, ранее добавленные в журнал.

При использовании последних двух методов вызывающая сторона должна сама позаботиться об открытии и фиксации транзакции.

 

Использование

С обработкой ПакетИзменений задача решается следующим образом.

Процедура обновления в модуле объекта теперь может выглядеть так:

Процедура ОбновитьЗависимыеРеквизиты(Знач Источник, Знач ПакетИзменений) Экспорт
	ОбновитьЗависимыеРеквизитыТекущегоДокумента(Источник);
	// вместо записи объекта добавляем операцию в журнал
	ПакетИзменений.Записать(ЭтотОбъект, РежимЗаписиДокумента.Проведение);
	ЗависимыеДокументы = ПолучитьЗависимыеДокументы();
	Для каждого Ссылка Из ЗависимыеДокументы Цикл
		ДокОбъект = Ссылка.ПолучитьОбъект();
		ДокОбъект.ОбновитьЗависимыеРеквизиты(ЭтотОбъект, ПакетИзменений); // передаем пакет дальше
	КонецЦикла;
КонецПроцедуры

Инициирующая сторона:

ПакетИзменений = Обработки.ПакетИзменений.Создать();
КорневойДокумент.ОбновитьЗависимыеРеквизиты(Источник, ПакетИзменений);
// здесь происходит реальная запись в БД
ПакетИзменений.ВыполнитьВТранзакции();

 

Ограничения

Ну как же без ложки дёгтя.

Данный метод не годится, если логика обновления требует выполнения согласованного чтения всех объектов группы. В этом случае необходимо выполнить блокирование всех объектов группы еще до начала чтения их из БД. Однако, с учетом того, что для определения связей все равно приходится сначала прочитать документы из БД, требование согласованного чтения делает задачу очень нетривиальной. Вплоть до того, что хранение графа связей придется выносить в отдельный регистр.

 

Текущая реализация не работает с наборами записей регистров сведений. Также не поддерживаются операции БизнесПроцессОбъект.Старт() и ЗадачаОбъект.ВыполнитьЗадачу(). При необходимости, это все нетрудно добавить.

Также нет никакой оптимизации в отношении возможного повторного добавления объектов в журнал - дважды добавленный объект будет действительно записан дважды.

Вступайте в нашу телеграмм-группу Инфостарт

паттерны единица работы unit of work пакет изменений инкапсуляция транзакция

См. также

Математика и алгоритмы Программист 1С 8.3 Абонемент ($m)

Данная внешняя обработка для платформы 1С:Предприятие реализует усовершенствованный алгоритм Левенштейна для вычисления схожести строк с учетом различных лингвистических особенностей русского языка. В отличие от классической реализации, этот алгоритм учитывает фонетические, визуальные и контекстные особенности набора текста.

1 стартмани

07.11.2025    5239    14    InFlach    17    

27

Математика и алгоритмы Запросы Программист 1С:Предприятие 8 Бесплатно (free)

Рассмотрим быстрый алгоритм поиска дублей с использованием hash функции по набору полей шапки и табличных частей.

08.07.2024    5412    ivanov660    9    

24

Математика и алгоритмы Программист 1С:Предприятие 8 1C:Бухгалтерия Россия Абонемент ($m)

На написание данной работы меня вдохновила работа @glassman «Переход на ClickHouse для анализа метрик». Автор анализирует большой объем данных, много миллионов строк, и убедительно доказывает, что ClickHouse справляется лучше PostgreSQL. Я же покажу как можно сократить объем данных в 49.9 раз при этом: 1. Сохранить значения локальных экстремумов 2. Отклонения от реальных значений имеют наперед заданную допустимую погрешность.

1 стартмани

30.01.2024    13450    stopa85    12    

43

Математика и алгоритмы Бесплатно (free)

Разработка алгоритма, построенного на модели симплекс-метода, для нахождения оптимального раскроя.

19.10.2023    21292    user1959478    57    

40

Математика и алгоритмы Разное 1С:Предприятие 8 1C:Бухгалтерия Россия Абонемент ($m)

Расширение (+ обработка) представляют собою математический тренажер. Ваш ребенок сможет проверить свои знание на математические вычисление до 100.

2 стартмани

29.09.2023    12724    maksa2005    8    

27

Математика и алгоритмы Инструментарий разработчика Программист 1С:Предприятие 8 Россия Абонемент ($m)

Что ж... лучше поздно, чем никогда. Подсистема 1С для работы с регулярными выражениями: разбор выражения, проверка на соответствие шаблону, поиск вхождений в тексте.

1 стартмани

09.06.2023    22008    11    SpaceOfMyHead    20    

65

Математика и алгоритмы Программист 1С:Предприятие 8 1C:Бухгалтерия Бесплатно (free)

Три задачи - три идеи - три решения. Мало кода, много смысла. Мини-статья.

03.04.2023    14397    RustIG    9    

30

Механизмы платформы 1С Математика и алгоритмы Программист 1С:Предприятие 8 Россия Бесплатно (free)

В статье анализируются средства платформы для решения системы линейных уравнений в 1С. Приводятся доводы в пользу некорректной работы встроенных алгоритмов, а значит потенциально некорректного расчета себестоимости в типовых конфигурациях.

23.11.2022    13444    gzharkoj    15    

27
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. ksnik 669 20.03.26 10:24 Сейчас в теме
Можно применить в
https://infostart.ru/1c/articles/2643659/
ЧАНК 16: Пакетная обработка взаимосвязанных объектов (Unit of Work) или расширить ЧАНК 3 и ЧАНК 13.

Ниже представлен полный текст нового чанка, который нужно добавить в справочник:

text
[ЧАНК 16: Пакетная обработка взаимосвязанных объектов (Unit of Work)]

## Описание проблемы
При изменении группы взаимосвязанных документов (например, родительский документ и его подчинённые) возникает риск взаимных блокировок (deadlock) при параллельной работе. Логика определения зависимостей инкапсулирована в модулях объектов, поэтому инициирующая сторона не может заранее определить множество объектов для блокировки.

## Паттерн "Единица работы" (Unit of Work)

### Основная идея
Изменения выполняются в памяти с регистрацией всех модифицируемых объектов в специальном журнале (пакете изменений). Фактическая запись в БД выполняется в конце транзакции после наложения управляемых блокировок на все зарегистрированные объекты.

### Порядок действий
1. Создать объект "ПакетИзменений"
2. Выполнить изменения объектов в памяти, передавая пакет по иерархии вызовов
3. Каждый изменяемый объект регистрирует операцию в пакете (вместо немедленной записи)
4. После всех изменений — вызвать метод ВыполнитьВТранзакции() пакета
5. Пакет автоматически: открывает транзакцию → блокирует все зарегистрированные объекты → выполняет операции → фиксирует транзакцию

### Реализация (обработка "ПакетИзменений")

```bsl
// Основные методы обработки

// Добавить операцию записи объекта
Процедура Записать(Объект, РежимЗаписи = Неопределено, РежимПроведения = Неопределено) Экспорт

// Добавить операцию установки пометки удаления
Процедура УстановитьПометкуУдаления(Объект, Пометка, ВключаяПодчиненные = Ложь) Экспорт

// Добавить операцию удаления объекта
Процедура Удалить(Объект) Экспорт

// Выполнить все операции в транзакции с автоматической блокировкой
Процедура ВыполнитьВТранзакции() Экспорт

// Управление режимом автоматической блокировки
Функция АвтоматическаяБлокировка(Состояние = Неопределено) Экспорт

// Добавление произвольных блокировок
Процедура ДобавитьБлокировкуПоСсылке(Ссылка, Режим = РежимБлокировкиДанных.Исключительный) Экспорт
Функция ДобавитьБлокировку(ПространствоБлокировки) Экспорт
Использование в модуле объекта
До применения паттерна (проблемный код):

bsl
Процедура ОбновитьЗависимыеРеквизиты(Знач Источник) Экспорт
ОбновитьЗависимыеРеквизитыТекущегоДокумента(Источник);
Записать(РежимЗаписиДокумента.Проведение); // немедленная запись!
ЗависимыеДокументы = ПолучитьЗависимыеДокументы();
Для каждого Ссылка Из ЗависимыеДокументы Цикл
ДокОбъект = Ссылка.ПолучитьОбъект();
ДокОбъект.ОбновитьЗависимыеРеквизиты(ЭтотОбъект);
КонецЦикла;
КонецПроцедуры
После применения паттерна (корректно):

bsl
Процедура ОбновитьЗависимыеРеквизиты(Знач Источник, Знач ПакетИзменений) Экспорт
ОбновитьЗависимыеРеквизитыТекущегоДокумента(Источник);
// Регистрация операции вместо немедленной записи
ПакетИзменений.Записать(ЭтотОбъект, РежимЗаписиДокумента.Проведение);

ЗависимыеДокументы = ПолучитьЗависимыеДокументы();
Для каждого Ссылка Из ЗависимыеДокументы Цикл
ДокОбъект = Ссылка.ПолучитьОбъект();
// Передаём пакет дальше по иерархии
ДокОбъект.ОбновитьЗависимыеРеквизиты(ЭтотОбъект, ПакетИзменений);
КонецЦикла;
КонецПроцедуры
Использование в инициирующей стороне
bsl
Процедура ОбработкаИзмененияДокументов(КорневойДокумент, Источник)
ПакетИзменений = Обработки.ПакетИзменений.Создать();

// Все изменения выполняются в памяти, без записи в БД
КорневойДокумент.ОбновитьЗависимыеРеквизиты(Источник, ПакетИзменений);

// Единая транзакция с блокировками и записью
ПакетИзменений.ВыполнитьВТранзакции();
КонецПроцедуры
Преимущества паттерна
Устранение взаимных блокировок — все объекты блокируются до начала записи

Сохранение инкапсуляции — логика связей остаётся внутри объектов

Единая точка транзакции — снижает риск частичных изменений

Один проход по графу — не требуется предварительного обхода для сбора ссылок

Прозрачность для вызывающего кода — достаточно передать пакет по иерархии

Ограничения
Не подходит для сценариев с согласованным чтением — если логика требует чтения всех объектов группы до начала изменений, необходимо предварительное блокирование

Требует модификации существующих методов — добавление параметра "ПакетИзменений" в экспортные процедуры

Не поддерживает наборы записей регистров сведений (требует доработки)

Отсутствует дедупликация объектов — объект, добавленный дважды, будет записан дважды (требует оптимизации)

Связь с другими чанками
Используется совместно с [ЧАНК 3: Обработка ошибок и транзакций] для корректной работы транзакций

Используется совместно с [ЧАНК 13: Обработка исключительных ситуаций] для обработки ошибок при блокировках

Требует [ЧАНК 1: Безопасный код] для проверки параметров и существования ссылок

text

## Обновлённая матрица "Ситуация → Чанки"

В таблицу соответствия необходимо добавить новую строку:

| Ситуация | Какие чанки добавить в промпт | Почему |
|----------|-------------------------------|--------|
| **Пакетное изменение группы взаимосвязанных документов** | ЧАНК 16 (Unit of Work) + ЧАНК 3 (Транзакции) + ЧАНК 13 (Исключения) | Предотвращение deadlock'ов, согласованное изменение группы объектов |

## Обновлённая итоговая таблица

| Категория | Конкретная задача | Какие чанки добавить в промпт |
|-----------|-------------------|-------------------------------|
| **Транзакции** | Групповое изменение взаимосвязанных документов | `[ЧАНК 16: Пакетная обработка][ЧАНК 3: Обработка ошибок и транзакций][ЧАНК 13: Исключительные ситуации]` |

---

**Вывод:** Паттерн "Единица работы" логично дополняет существующую систему чанков, решая важную проблему согласованного изменения групп объектов. Он должен быть добавлен как **ЧАНК 16** с соответствующими обновлениями справочных таблиц.
2. korvintorson 86 20.03.26 13:04 Сейчас в теме
(1)
Требует модификации существующих методов — добавление параметра "ПакетИзменений" в экспортные процедуры

Мне самому не очень нравится вариант с передачей пакета в параметрах процедур. Можно подумать в сторону использования глобального пакета изменений в контексте текущего серверного вызова. Но должен быть четкий регламент использования, такой же, как с транзакциями.
Что-то вроде такого:
НачатьПакетИзменений(); // инициализирует глобальный пакет
Попытка
    КорневойДокумент.ОбновитьЗависимыеРеквизиты(Источник);
    ЗафиксироватьПакетИзменений(); // выполняет ВыполнитьВТранзакции() и уничтожает глобальный пакет
Исключение
    ОтменитьПакетИзменений(); // уничтожает глобальный пакет без фиксации
    ВызватьИсключение;
КонецПопытки;

А процедура обновления будет делать так:
ГлобальныйПакетИзменений().Записать(ЭтотОбъект);
Для отправки сообщения требуется регистрация/авторизация