При автоматизации крупных торговых и производственных предприятий разработчики часто сталкиваются с задачей интеграции нескольких информационных баз 1С. Например, когда менеджеры работают в современной конфигурации 1С:Управление торговлей 11, а основной производственный и складской учет ведется в 1С:Управлении производственным предприятием 1.3 (УПП). Возникает соблазн сделать «красивое» и быстрое решение: при проведении заказа покупателя в УТ 11 мгновенно подключаться к УПП, создавать там аналогичный заказ, проверять остатки, резервировать товар и возвращать результат.
Однако реализация такой схемы «в лоб» через синхронный вызов (особенно через устаревшие COM-технологии) внутри транзакции проведения — это гарантированный способ парализовать работу всей системы. Давайте подробно разберем, почему синхронная интеграция в транзакции является критической архитектурной ошибкой, к каким последствиям она приводит на уровне СУБД, и рассмотрим надежные асинхронные альтернативы, которые успешно применяются в современной практике.
Давайте проанализируем, что происходит на уровне системы управления базами данных (MS SQL, PostgreSQL), когда пользователь нажимает кнопку «Провести» в документе ЗаказКлиента. Платформа 1С запускает неявную транзакцию СУБД. В этот момент накладываются монопольные и разделяемые блокировки на таблицы регистров, связанные с этим документом (например, остатки товаров, взаиморасчеты, денежные средства).
Если внутри процедуры ОбработкаПроведения мы инициируем внешнее подключение к другой базе (неважно, через V83.COMConnector, Web-сервис или HTTP-запрос), то транзакция в первой базе остается открытой всё то время, пока выполняется внешний вызов. Давайте посмотрим на цепочку задержек:
В лучшем случае этот процесс занимает 2–3 секунды. В худшем — при нестабильной сети или высокой загрузке серверов — процесс может затянуться на 30–60 секунд или завершиться по таймауту. Всё это время таблицы первой базы заблокированы. Другие пользователи, пытающиеся провести свои заказы или накладные, выстраиваются в очередь блокировок. Спустя короткое время они начинают массово получать критическую ошибку: «Превышено время ожидания установки блокировки». Работа компании останавливается.
Кроме того, если во второй базе произойдет сбой (например, не хватило остатка товара), первая база должна отменить свою транзакцию. Но если сетевой пакет с подтверждением отмены потеряется, мы получим рассинхронизацию данных: в УТ заказ не проведен, а в УПП документ успешно создался и зарезервировал остатки. Разгребать такие последствия программистам приходится вручную.
Исторически использование COM-объектов (V83.COMConnector) было самым простым способом связать две базы. Однако этот подход имеет критические недостатки:
Рассмотрим современные архитектурные паттерны, которые позволяют решить задачу оперативного взаимодействия баз надежно и быстро.
Для того чтобы уйти от синхронного ожидания проведения во второй базе, мы можем применить паттерн разделения процесса на шаги с использованием статусов. Давайте разберем этот алгоритм по шагам:
ЗаказКлиента. В этот момент документ не делает движений по ключевым складским регистрам, а записывается в промежуточном статусе «Ожидает подтверждения». Транзакция СУБД завершается мгновенно (за доли секунды).Давайте посмотрим, как может выглядеть реализация регистрации документа в очереди обмена при его записи. Разместим этот код в модуле объекта документа ЗаказКлиента:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Если документ проводится менеджером, временно устанавливаем статус ожидания
Если РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
ЭтотОбъект.СтатусЗаказа = Перечисления.СтатусыЗаказов.ОжидаетПодтверждения;
КонецЕсли;
КонецПроцедуры
Процедура ПриЗаписи(Отказ)
// Проверяем, нужно ли отправлять документ на подтверждение
Если ЭтотОбъект.СтатусЗаказа = Перечисления.СтатусыЗаказов.ОжидаетПодтверждения Тогда
// Записываем задачу в очередь обмена в этой же транзакции базы данных
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ОчередьИсходящихИнтеграции");
ЭлементБлокировки.УстановитьЗначение("Документ", ЭтотОбъект.Ссылка);
НачатьТранзакцию();
Попытка
Блокировка.Заблокировать();
МенеджерЗаписи = РегистрыСведений.ОчередьИсходящихИнтеграции.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Документ = ЭтотОбъект.Ссылка;
МенеджерЗаписи.ДатаРегистрации = ТекущаяДата();
МенеджерЗаписи.Состояние = Перечисления.СостоянияИнтеграции.Новое;
МенеджерЗаписи.Записать(Истина);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Отказ = Истина;
ВызватьИсключение "Не удалось зарегистрировать заказ в очереди интеграции: " + ОписаниеОшибки();
КонецПопытки;
КонецЕсли;
КонецПроцедуры
Многие разработчики ошибочно полагают, что в старых конфигурациях на обычных формах (таких как УПП 1.3) невозможно опубликовать HTTP-сервисы. Это не так. Возможность создания HTTP-сервисов — это свойство самой платформы 1С 8.3, и она никак не зависит от того, какой интерфейс используется в конфигурации (управляемый или обычный).
Давайте спроектируем HTTP-сервис на стороне УПП 1.3, который будет принимать заказы из УТ 11, пытаться их проводить и возвращать результат в формате JSON. Это работает значительно быстрее, чем тяжелые SOAP-веб-сервисы или COM-соединения.
Создадим в конфигурации УПП новый HTTP-сервис с именем OrderIntegration, шаблоном URL /orders и методом POST, который привязан к функции СоздатьИПровестиЗаказ. Рассмотрим пример реализации этой функции в модуле HTTP-сервиса:
Функция СоздатьИПровестиЗаказ(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
// Читаем JSON с данными заказа из тела HTTP-запроса
СтрокаДанных = Запрос.ПолучитьТелоКакСтроку("UTF-8");
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаДанных);
ДанныеЗаказа = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
// Пытаемся создать и провести документ в УПП
НачатьТранзакцию();
Попытка
НовыйЗаказ = Документы.ЗаказПокупателя.СоздатьДокумент();
НовыйЗаказ.Дата = ТекущаяДата();
НовыйЗаказ.Заполнить(Неопределено); // Заполнение базовых реквизитов по умолчанию
// Заполняем реквизиты данными из УТ
НовыйЗаказ.Контрагент = Справочники.Контрагенты.НайтиПоКоду(ДанныеЗаказа.КодКонтрагента);
НовыйЗаказ.ДоговорКонтрагента = НовыйЗаказ.Контрагент.ОсновнойДоговорКонтрагента;
Для Каждого СтрокаТовара Из ДанныеЗаказа.Товары Цикл
НоваяСтрока = НовыйЗаказ.Товары.Добавить();
НоваяСтрока.Номенклатура = Справочники.Номенклатура.НайтиПоКоду(СтрокаТовара.КодНоменклатуры);
НоваяСтрока.Количество = СтрокаТовара.Количество;
НоваяСтрока.Цена = СтрокаТовара.Цена;
НоваяСтрока.Сумма = СтрокаТовара.Количество * СтрокаТовара.Цена;
КонецЦикла;
// Записываем и проводим документ
НовыйЗаказ.Записать(РежимЗаписиДокумента.Проведение);
ЗафиксироватьТранзакцию();
// Возвращаем успешный ответ со ссылкой на созданный документ
Результат = Новый Структура;
Результат.Вставить("Успешно", Истина);
Результат.Вставить("НомерУПП", НовыйЗаказ.Номер);
Результат.Вставить("ОписаниеОшибки", "");
Исключение
ОтменитьТранзакцию();
// Возвращаем ошибку проведения
Результат = Новый Структура;
Результат.Вставить("Успешно", Ложь);
Результат.Вставить("НомерУПП", "");
Результат.Вставить("ОписаниеОшибки", КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
// Записываем ответ в JSON
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, Результат);
Ответ.УстановитьТелоИзСтроки(ЗаписьJSON.Закрыть(), "UTF-8");
Возврат Ответ;
КонецФункции
Теперь на стороне УТ 11 фоновое регламентное задание может легко отправлять данные по HTTP-протоколу, используя объект HTTPСоединение. Такой подход работает молниеносно, не создает лишней нагрузки и легко масштабируется на любое количество пользователей.
Если вам не хочется писать собственный HTTP-сервис для чтения или создания документов во второй базе, мы можем использовать стандартный программный интерфейс 1С — OData (автоматический REST-сервис платформы).
Для активации этого механизма достаточно запустить одну строчку кода в УПП, которая разрешит доступ к необходимым метаданным (например, к справочникам номенклатуры, контрагентов и документам заказов):
// Разрешаем удаленный доступ к документу ЗаказПокупателя по OData
МассивМетаданных = Новый Массив;
МассивМетаданных.Добавить(Метаданные.Документы.ЗаказПокупателя);
СистемаУправленияИнтерфейсомOData.УстановитьСоставИспользования(МассивМетаданных);
После этого внешняя база УТ сможет создавать документы в УПП простым HTTP-запросом к опубликованной базе по стандартному адресу вида: http://server/upp/odata/standard.odata/Document_ЗаказПокупателя. Платформа 1С самостоятельно преобразует входящие JSON-структуры в объекты базы данных УПП, выполнит все необходимые проверки и запишет документ.
Для крупных корпоративных систем оптимальным решением является интеграция через специализированных посредников — брокеров сообщений. Рассмотрим эту схему подробнее:
Для реализации такой схемы в 1С, начиная с версии платформы 8.3.14, встроены специальные «Сервисы интеграции», позволяющие работать с внешними брокерами сообщений напрямую на уровне платформы без использования сторонних внешних компонент.
Пытаться связать две базы 1С синхронно в момент проведения документов — это опасный путь, ведущий к нестабильности СУБД и постоянным блокировкам пользователей. Использование COM-соединений внутри транзакций является устаревшим и вредным подходом.
Для построения отказоустойчивой и быстрой архитектуры всегда используйте асинхронные методы интеграции. Реализуйте очереди задач через регистры сведений и регламентные задания, разделяйте процессы проведения документов с помощью статусов, используйте современные легковесные HTTP-сервисы и интерфейс OData. Это гарантирует бесперебойную работу ваших информационных систем при любых нагрузках.