В процессе разработки на платформе 1С часто возникает задача передать некоторые данные с клиента на сервер. Стандартный способ — добавить новый параметр в вызываемую серверную процедуру. Но что делать, если эта процедура находится очень глубоко в цепочке вызовов, и изменение ее сигнатуры повлечет за собой рефакторинг множества других процедур и функций? Это трудоемко и рискованно. Рассмотрим ситуацию, когда нужно в одном клиентском модуле сохранить значение в некий буфер, а затем прочитать его в другом серверном модуле в рамках текущего сеанса. При этом изменять параметры процедур, создавать новые константы или параметры сеанса — нежелательно.
Проанализируем несколько рабочих и надежных способов организации такого "скрытого" канала передачи данных.
Это механизм платформы, предназначенный для хранения пользовательских настроек между сеансами. Однако его можно успешно применять и для временной передачи данных внутри одного сеанса. Основная особенность, которую нужно учесть: объект ХранилищеОбщихНастроек недоступен напрямую в коде тонкого клиента.
Решение — инкапсулировать работу с хранилищем в процедуры общего серверного модуля. Мы создаем две простые экспортные процедуры: одну для записи значения, другую для чтения.
Посмотрим на пример. Создадим общий серверный модуль, например, УправлениеБуферомОбмена.
// Общий серверный модуль "УправлениеБуферомОбмена"
Процедура СохранитьЗначениеВБуфер(Ключ, Значение) Экспорт
ХранилищеОбщихНастроек.Сохранить("МойБуферДанных", Ключ, Значение);
КонецПроцедуры
Функция ЗагрузитьЗначениеИзБуфера(Ключ) Экспорт
Возврат ХранилищеОбщихНастроек.Загрузить("МойБуферДанных", Ключ);
КонецФункции
Процедура ОчиститьЗначениеВБуфере(Ключ) Экспорт
ХранилищеОбщихНастроек.Удалить("МойБуферДанных", Ключ);
КонецПроцедуры
Теперь использовать этот механизм очень просто. Сначала в клиентской процедуре вызываем серверную обертку для сохранения данных:
&НаКлиенте
Процедура ПередВызовомСервернойЛогики()
// Сохраняем некое важное значение перед вызовом сложной логики
ДанныеДляПередачи = "Это важная строка, которую нужно прочитать на сервере";
УправлениеБуферомОбмена.СохранитьЗначениеВБуфер("КлючМоихДанных", ДанныеДляПередачи);
// Теперь вызываем серверную процедуру, в которую не хотим добавлять параметр
ВыполнитьСложнуюСервернуюЛогику();
КонецПроцедуры
А внутри глубоко вложенной серверной процедуры мы можем прочитать это значение:
&НаСервере
Процедура КакаяТоВложеннаяПроцедура()
// Здесь нам понадобились данные, сохраненные на клиенте
ПолученныеДанные = УправлениеБуферомОбмена.ЗагрузитьЗначениеИзБуфера("КлючМоихДанных");
Если ПолученныеДанные <> Неопределено Тогда
// Используем полученные данные
Сообщить("На сервере успешно получено значение: " + ПолученныеДанные);
КонецЕсли;
// Важно! Не забываем очищать буфер после использования, чтобы не оставлять "мусор"
УправлениеБуферомОбмена.ОчиститьЗначениеВБуфере("КлючМоихДанных");
КонецПроцедуры
Преимущества: Простота реализации, не требует изменения структуры метаданных.
Недостатки: Небольшие накладные расходы на сериализацию/десериализацию. Необходимо тщательно следить за уникальностью ключей и очищать данные после использования.
Это, пожалуй, самый распространенный и надежный "промышленный" способ для решения подобных задач. Создается специальный технический регистр сведений, который выступает в роли буфера.
Разберем по шагам:
ВременныеДанныеСеанса.Пользователь (тип СправочникСсылка.Пользователи) — для привязки данных к конкретному пользователю.КлючДанных (тип Строка) — уникальный идентификатор наших данных, аналог ключа из предыдущего примера. Можно использовать УникальныйИдентификатор, если требуется гарантированная уникальность.ХранилищеЗначения. Назовем ресурс Значение.Теперь работа с этим регистром выглядит так. Сначала пишем данные:
&НаСервере
Процедура ЗаписатьДанныеВРегистрНаСервере(Ключ, Данные)
// Получаем текущего пользователя
ТекущийПользователь = Пользователи.ТекущийПользователь();
// Создаем менеджер записи
МенеджерЗаписи = РегистрыСведений.ВременныеДанныеСеанса.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Пользователь = ТекущийПользователь;
МенеджерЗаписи.КлючДанных = Ключ;
МенеджерЗаписи.Значение = Новый ХранилищеЗначения(Данные);
// Записываем данные в базу
МенеджерЗаписи.Записать();
КонецПроцедуры
А затем читаем в другом месте:
&НаСервере
Функция ПрочитатьДанныеИзРегистраНаСервере(Ключ)
ТекущийПользователь = Пользователи.ТекущийПользователь();
// Читаем данные из регистра
СтруктураОтбора = Новый Структура("Пользователь, КлючДанных", ТекущийПользователь, Ключ);
Запись = РегистрыСведений.ВременныеДанныеСеанса.Получить(СтруктураОтбора);
Если Запись <> Неопределено И Запись.Свойство("Значение") Тогда
// Очищаем запись, так как она больше не нужна
НаборЗаписей = РегистрыСведений.ВременныеДанныеСеанса.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Пользователь.Установить(ТекущийПользователь);
НаборЗаписей.Отбор.КлючДанных.Установить(Ключ);
НаборЗаписей.Записать(); // Записываем пустой набор для удаления
Возврат Запись.Значение.Получить();
Иначе
Возврат Неопределено;
КонецЕсли;
КонецФункции
Важный момент: этот регистр будет постоянно расти. Необходимо предусмотреть механизм очистки. Это можно делать сразу после прочтения (как в примере выше) или регламентным заданием, которое периодически удаляет все записи старше, например, одного дня.
Преимущества: Надежность, прозрачность (данные видны в базе), возможность хранить любые типы данных.
Недостатки: Требует изменения конфигурации, создает дополнительную нагрузку на базу данных, требует механизма очистки.
Этот механизм специально предназначен для передачи данных, особенно больших, между клиентом и сервером или между серверными вызовами.
Проанализируем схему работы:
ПоместитьВоВременноеХранилище()."e1cib/tempstorage/...".ПолучитьИзВременногоХранилища() извлекает по нему исходные данные.Посмотрим на пример, где мы сначала сохраняем данные, а потом в другом вызове их читаем.
&НаСервере
Функция СохранитьДанныеИПолучитьАдрес()
СтруктураДанных = Новый Структура;
СтруктураДанных.Вставить("Параметр1", "Значение1");
СтруктураДанных.Вставить("Параметр2", 123);
Адрес = ПоместитьВоВременноеХранилище(СтруктураДанных, УникальныйИдентификатор);
Возврат Адрес;
КонецФункции
&НаСервере
Процедура ИспользоватьДанныеПоАдресу(Адрес)
Данные = ПолучитьИзВременногоХранилища(Адрес);
Если Данные <> Неопределено Тогда
// Данные - это наша исходная структура
Сообщить(Данные.Параметр1); // "Значение1"
КонецЕсли;
// Хранилище очищается платформой автоматически после завершения запроса,
// в котором был последний раз получен объект из хранилища.
// Но можно и удалить принудительно:
УдалитьИзВременногоХранилища(Адрес);
КонецПроцедуры
Преимущества: Идеально подходит для больших объемов данных (файлы, таблицы), управляется платформой, не требует изменения метаданных.
Недостатки: Адрес хранилища имеет ограниченное время жизни, что делает его непригодным для долгосрочного хранения данных между сеансами.
Это очень быстрый и элегантный способ для обмена данными в пределах одного сеанса. Он не затрагивает базу данных вообще.
Как это работает:
СеансовыйКэш.Перем.Благодаря установленному флагу, состояние этой переменной будет сохраняться между разными серверными вызовами в рамках одного сеанса. Это, по сути, создает "глобальную" переменную на время жизни сеанса на сервере.
Пример реализации модуля СеансовыйКэш:
// Общий серверный модуль "СеансовыйКэш"
// В свойствах модуля ОБЯЗАТЕЛЬНО установить флаг
// "Повторное использование возвращаемых значений"
Перем КонтейнерЗначений Экспорт; // Наша "глобальная" переменная
Процедура ИнициализироватьКэш() Экспорт
Если КонтейнерЗначений = Неопределено Тогда
КонтейнерЗначений = Новый Соответствие;
КонецЕсли;
КонецПроцедуры
Процедура УстановитьЗначение(Ключ, Значение) Экспорт
ИнициализироватьКэш();
КонтейнерЗначений.Вставить(Ключ, Значение);
КонецПроцедуры
Функция ПолучитьЗначение(Ключ) Экспорт
ИнициализироватьКэш();
Возврат КонтейнерЗначений[Ключ];
КонецФункции
Использование ничем не отличается от других методов: в одном серверном вызове пишем, в другом — читаем.
Преимущества: Максимальная производительность, так как все операции происходят в оперативной памяти сервера без обращений к СУБД. Не требует изменения метаданных.
Недостатки: Данные живут только в пределах текущего сеанса и будут потеряны при его завершении. Требует внимательности в многопользовательской среде, так как кэш уникален для каждого сеанса.
Вывод: выбор конкретного способа зависит от задачи. Для быстрой передачи простых данных внутри сеанса идеально подойдет кэширующий модуль. Для передачи больших объемов данных — временное хранилище. Если нужна повышенная надежность или данные должны "пережить" короткие разрывы связи, лучше использовать регистр сведений. А ХранилищеОбщихНастроек — это хороший компромисс между простотой и функциональностью.