При разработке механизмов интеграции часто возникает ситуация, когда стандартный обмен данными (через XML-файлы определенного формата) не подходит, но механизм регистрации изменений в Планах обмена крайне удобен. Мы можем использовать план обмена только как «очередь» изменившихся объектов, которую нужно выгрузить в произвольном формате (например, через JSON или HTTP-запрос) и затем очистить.
Основная сложность заключается в том, чтобы корректно присвоить номер сообщения зарегистрированным записям. Это необходимо для обеспечения целостности данных: если во время процесса выгрузки объект будет снова изменен другим пользователем, мы не должны удалять его регистрацию. В этой статье мы подробно разберем, как заставить платформу 1С пронумеровать изменения без создания реальных файлов на диске.
Рассмотрим ситуацию: мы решили просто выбрать все изменения запросом и после выгрузки очистить их методом ПланыОбмена.УдалитьРегистрациюИзменений(). Если база многопользовательская, велик риск «потерять» данные. Пока наша обработка формировала пакет данных для отправки, пользователь мог изменить и записать один из объектов, который уже попал в наш пакет. Если мы просто удалим всю регистрацию по узлу, то изменение, внесенное пользователем после начала нашей выгрузки, будет стерто, и в следующую выгрузку объект не попадет.
Использование номера сообщения решает эту проблему:
УдалитьРегистрациюИзменений(Узел, Номер) будут удалены только те записи, чей номер совпадает с нашим. Новые изменения останутся в базе и попадут в следующий сеанс обмена.Чтобы избежать обращения к файловой системе, мы можем использовать метод УстановитьСтроку() объекта ЗаписьXML. Рассмотрим этот алгоритм по шагам.
Проанализируем программный код, который позволяет «обмануть» систему и пронумеровать записи в памяти:
// Подготовим необходимые объекты
УзелОбмена = ПланыОбмена.МойПланОбмена.НайтиПоКоду("SiteExchange");
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку(); // Всё будет происходить в оперативной памяти
ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
// 1. Начинаем запись сообщения.
// В этот момент платформа инкрементирует номер отправленного сообщения на узле.
ЗаписьСообщения.НачатьЗапись(ЗаписьXML, УзелОбмена);
НомерСформированногоСообщения = ЗаписьСообщения.НомерСообщения;
// 2. Выбираем изменения.
// Именно этот вызов проставляет полученный НомерСообщения в таблицу регистрации изменений.
ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(УзелОбмена, НомерСформированногоСообщения);
Пока ВыборкаИзменений.Следующий() Цикл
// Здесь мы можем выполнять свою произвольную логику:
// Формировать JSON, отправлять данные по HTTP или записывать в свою таблицу.
ОбъектДанных = ВыборкаИзменений.Данные;
// ... логика обработки ...
КонецЦикла;
// 3. Завершаем запись сообщения.
ЗаписьСообщения.ЗавершитьЗапись();
СформированнаяПустаяСтрокаXML = ЗаписьXML.Закрыть();
// 4. Удаляем только те изменения, которые получили наш номер.
ПланыОбмена.УдалитьРегистрациюИзменений(УзелОбмена, НомерСформированногоСообщения);
В данном примере мы используем ЗаписьXML.УстановитьСтроку(), что полностью исключает создание временных файлов на жестком диске сервера. Это работает быстро и эффективно.
Некоторые разработчики предпочитают не полагаться на стандартный механизм нумерации, если логика обмена очень сложная. Рассмотрим альтернативный подход, который обсуждался участниками сообщества. Суть его заключается в ручном контроле состава выгруженных данных внутри транзакции.
Разберем логику этого метода:
Посмотрим, как это может выглядеть в коде:
// Предварительный сбор данных
СписокВыгруженныхОбъектов = Новый Массив;
Выборка = ПланыОбмена.ВыбратьИзменения(УзелОбмена, 0); // Берем всё без присвоения номера
Пока Выборка.Следующий() Цикл
// Выгружаем...
СписокВыгруженныхОбъектов.Добавить(Выборка.Данные);
КонецЦикла;
// ... блок кода отправки данных (может быть длительным) ...
// Фиксация результата
НачатьТранзакцию();
Попытка
Для Каждого СсылкаИзСписка Из СписокВыгруженныхОбъектов Цикл
// Блокируем запись регистрации для исключения коллизий
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("ПланОбмена.МойПланОбмена.Изменения");
ЭлементБлокировки.УстановитьЗначение("Узел", УзелОбмена);
// Здесь можно добавить условие по ссылке, если конфигурация позволяет
Блокировка.Заблокировать();
// Удаляем регистрацию конкретного объекта
ПланыОбмена.УдалитьРегистрациюИзменений(УзелОбмена, СсылкаИзСписка);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
// Логика обработки ошибки
КонецПопытки;
Важное примечание: Этот метод гораздо более трудозатратен и опасен с точки зрения производительности (блокировки). Использование стандартного механизма с НомерСообщения, как в первом решении, является рекомендуемым подходом фирмы «1С».
Проанализируем, почему метод ВыбратьИзменения(Узел, Номер) так важен. Многие начинающие программисты полагают, что этот метод просто фильтрует записи. На самом деле, когда вы передаете в него второй параметр (номер), платформа выполняет Update в таблице регистрации изменений в базе данных (SQL). Она прописывает этот номер всем записям, у которых номер на данный момент равен 0.
Это происходит атомарно. Именно поэтому, даже если вы не записываете данные в XML-файл, вызов этого метода обязателен для «маркировки» пакета. Если вы вызовете ВыбратьИзменения без второго параметра, номер присвоен не будет, и метод УдалитьРегистрациюИзменений(Узел, Номер) ничего не удалит.
Иногда процесс выгрузки может завершиться аварийно (например, выключился сервер или произошла ошибка в коде формирования данных). В этом случае в таблице регистрации останутся записи, которым уже присвоен номер сообщения, но они так и не были удалены.
Рассмотрим, как с этим бороться. При следующем запуске обмена рекомендуется анализировать наличие таких записей. Стандартный метод ВыбратьИзменения без указания номера выберет все записи (и с номерами, и без). Однако, если вы хотите перепослать именно «зависший» пакет, вы можете вызвать ВыбратьИзменения(Узел, НомерЗависшегоПакета).
Практический совет: Чтобы узнать номер следующего сообщения, которое ожидает платформа, можно обратиться к свойству узла УзелОбмена.НомерОтправленногоСообщения. Номер сообщения, который будет присвоен при следующем вызове НачатьЗапись, обычно равен НомерОтправленногоСообщения + 1.
Мы выяснили, что для реализации качественного обмена без использования файлов достаточно использовать стандартные объекты ЗаписьСообщенияОбмена и ЗаписьXML, работающую со строкой. Это позволяет:
Рассмотренные подходы позволяют гибко настраивать интеграции любой сложности, не ограничиваясь рамками стандартного формата обмена данными.