При интеграции различных информационных баз 1С разработчики часто используют технологию COM-соединения. Это простой и удобный способ обращаться к данным внешней базы напрямую, используя привычный объектный синтаксис 1С. Однако в управляемом приложении (начиная с платформы 8.2 и особенно в 8.3) возникает серьезная архитектурная сложность.
Типичный сценарий: нам нужно подключиться к внешней базе, быстро получить список значений (например, номенклатуру или контрагентов), вывести его пользователю на форму для выбора (клиентская часть), а после выбора продолжить обработку во внешней базе с использованием того же соединения (серверная часть). Инициализация нового COM-соединения при каждом серверном вызове — крайне ресурсоемкая операция, которая может занимать от нескольких секунд до полутора минут. В этой статье мы подробно разберем, почему «теряется» переменная соединения, как обойти это ограничение платформы и какие альтернативные архитектурные решения существуют.
Объект типа COMОбъект (в частности, V83.COMConnector) представляет собой указатель на неуправляемый системный ресурс операционной системы Windows (библиотеку или внешний процесс). При работе в управляемом приложении 1С разделяет выполнение кода на клиентский (в браузере или тонком клиенте) и серверный (в рабочем процессе rhost на сервере приложений).
Физически клиентский компьютер пользователя и сервер приложений 1С могут находиться на расстоянии сотен километров друг от друга. Попытка напрямую передать переменную, содержащую COMОбъект, с сервера на клиент или сохранить ее в реквизите формы приводит к ошибке сериализации. Платформа 1С:Предприятие не умеет преобразовывать указатели операционной системы в поток данных для отправки по сети. Соответственно, при завершении серверного вызова и возврате управления на клиент все несериализуемые локальные серверные переменные уничтожаются сборщиком мусора.
Рассмотрим четыре основных практических способа решения этой проблемы, их преимущества, недостатки и особенности реализации.
Самый простой способ сохранить соединение активным в течение всего сеанса работы с формой — выполнять подключение и всю сопутствующую работу на стороне клиента. Этот вариант работает только в том случае, если пользователи запускают систему под толстым клиентом.
Для реализации этого метода мы объявляем переменную в самом верху модуля формы с директивой &НаКлиенте. Эта переменная будет жить до тех пор, пока открыта форма, и мы сможем обращаться к ней при любых интерактивных действиях пользователя.
Разберем пример реализации в коде формы:
&НаКлиенте
Перем мСоединение; // Переменная модуля формы, доступная во всех клиентских процедурах
&НаКлиенте
Процедура ПодключитьсяКБазе(Команда)
// Инициализируем соединение прямо на клиенте
COMКоннектор = Новый COMОбъект("V83.COMConnector");
СтрокаПодключения = "Srvr=""ServerName"";Ref=""BaseName"";Usr=""Администратор"";Pwd=""Пароль"";";
Попытка
мСоединение = COMКоннектор.Connect(СтрокаПодключения);
Сообщить("Успешно подключено к внешней базе!");
Исключение
Сообщить("Ошибка подключения: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
// Получаем первичные данные для выбора пользователя
ПолучитьДанныеДляВыбора();
КонецПроцедуры
&НаКлиенте
Процедура ПолучитьДанныеДляВыбора()
// Используем соединение напрямую на клиенте
СписокВыбора = мСоединение.Справочники.Номенклатура.Выбрать();
Пока СписокВыбора.Следующий() Цикл
// Заполняем элементы формы для интерактивного выбора
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура ПослеВыбораПользователя(ВыбранныйЭлемент)
// Соединение не потерялось, используем его повторно
Если мСоединение <> Неопределено Тогда
ВнешнийОбъект = мСоединение.Справочники.Номенклатура.НайтиПоНаименованию(ВыбранныйЭлемент);
// Выполняем дальнейшие действия
КонецЕсли;
КонецПроцедуры
Плюсы метода: простота реализации, отсутствие необходимости передавать данные между сервером и клиентом, мгновенный отклик при повторных обращениях.
Минусы метода: решение не будет работать в тонком клиенте (если на локальном компьютере пользователя не зарегистрированы библиотеки COM 1С) и принципиально несовместимо с веб-клиентом (например, при работе через браузер или на мобильных устройствах).
Если нам обязательно нужно использовать тонкий клиент и выполнять обращения к COM-объекту на стороне сервера, мы можем попытаться поместить соединение во ВременноеХранилище.
Напрямую платформа 1С:Предприятие 8.3 запрещает помещать несериализуемые объекты во временное хранилище и выдает ошибку. Однако существует обходной путь: если мы упакуем COM-соединение внутрь стандартной коллекции (например, в Структуру или Соответствие), платформа проигнорирует ограничение и позволит сохранить структуру. Само соединение при этом останется физически лежать в оперативной памяти текущего рабочего процесса сервера 1С.
Рассмотрим, как это реализовать на практике:
&НаСервере
Процедура ПодключитьсяИСохранитьНаСервере()
COMКоннектор = Новый COMОбъект("V83.COMConnector");
СтрокаПодключения = "Srvr=""ServerName"";Ref=""BaseName"";Usr=""Администратор"";";
Попытка
Соединение = COMКоннектор.Connect(СтрокаПодключения);
Исключение
ВызватьИсключение "Ошибка COM: " + ОписаниеОшибки();
КонецПопытки;
// Упаковываем соединение в структуру-контейнер
Контейнер = Новый Структура;
Контейнер.Вставить("АктивноеСоединение", Соединение);
// Помещаем во временное хранилище, привязывая к уникальному идентификатору формы
АдресХранилища = ПоместитьВоВременноеХранилище(Контейнер, УникальныйИдентификатор);
КонецПроцедуры
&НаСервере
Процедура ВыполнитьДействиеССоединением()
// Извлекаем контейнер из временного хранилища
Контейнер = ПолучитьИзВременногоХранилища(АдресХранилища);
Если ТипЗнч(Контейнер) = Тип("Структура") И Контейнер.Свойство("АктивноеСоединение") Тогда
Соединение = Контейнер.АктивноеСоединение;
// Работаем с соединением без повторной авторизации
ВнешнийСправочник = Соединение.Справочники.Контрагенты;
Иначе
// Если соединение пропало из кэша, инициализируем его заново
ПодключитьсяИСохранитьНаСервере();
КонецЕсли;
КонецПроцедуры
Важное предупреждение: данный метод является недокументированным поведением платформы. Его стабильная работа не гарантируется производителем. В крупных клиент-серверных базах с многопроцессорным кластером этот метод будет регулярно давать сбои. Если ваш следующий серверный вызов будет направлен балансировщиком нагрузки на другой рабочий процесс сервера (rhost), то этот процесс физически не найдет в своей оперативной памяти указатель на COM-объект, созданный в рамках другого процесса, и вы получите ошибку.
Для создания надежной архитектуры в клиент-серверной модели можно применить профессиональный паттерн проектирования с использованием фонового задания. Суть метода заключается в том, что COM-соединение открывается и удерживается внутри выделенного фонового процесса, который выступает брокером сообщений.
Проанализируем алгоритм этого паттерна:
Этот метод полностью решает проблему масштабируемости, не зависит от разрядности сервера 1С и исключает ошибки, связанные с блокировками памяти рабочих процессов.
Использование COM-технологий в 2020-х годах считается устаревшим подходом (legacy). COM-соединения сильно привязаны к операционной системе Windows, требуют сложной настройки прав доступа (DCOM, COM+), расходуют значительное количество оперативной памяти сервера и создают уязвимости в безопасности.
Если перед вами стоит задача регулярного или даже разового обмена данными между базами, лучшим решением будет использование встроенных механизмов 1С для работы по протоколу HTTP.
Разберем, как быстро настроить интеграцию без изменения конфигурации подключаемой базы:
Пример отправки HTTP-запроса из базы-источника:
&НаСервере
Функция ПолучитьДанныеЧерезHTTP(ПараметрыЗапроса)
Соединение = Новый HTTPСоединение("192.168.1.100", 80, "ПользовательAPI", "ПарольAPI");
Запрос = Новый HTTPЗапрос("/BaseName/hs/ExchangeService/v1/GetItems");
// Передаем параметры в формате JSON
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ПараметрыЗапроса);
СтрокаJSON = ЗаписьJSON.Закрыть();
Запрос.УстановитьТелоИзСтроки(СтрокаJSON, КодировкаТекста.UTF8);
Попытка
Ответ = Соединение.ОтправитьДляАнализа(Запрос);
Если Ответ.КодСостояния = 200 Тогда
Возврат Ответ.ПолучитьТелоКакСтроку();
КонецЕсли;
Исключение
Сообщить("Ошибка связи с сервером: " + ОписаниеОшибки());
КонецПопытки;
Возврат Неопределено;
КонецФункции
Преимущества перехода на HTTP-сервисы:
Выбор конкретного метода зависит от архитектуры вашей системы и регулярности выполнения задачи:
ВременноеХранилище (Решение 2), однако вы должны заложить в код обязательную процедуру переподключения в случае, если сессия на сервере сбросится.