При переходе или адаптации конфигураций, использующих Библиотеку Стандартных Подсистем (БСП) или собственные серверные модули, разработчики часто сталкиваются с неочевидным поведением платформы 1С:Предприятие. Казалось бы, клиентское приложение запущено в режиме Толстого клиента, но код ведет себя по-разному в зависимости от того, работает ли база в режиме Обычного приложения или Управляемого приложения.
В этой статье мы подробно разберем ситуацию, когда попытка вернуть с сервера значение типа ИнформацияОбОшибке (или другие мутабельные типы, например, СписокЗначений) приводит к падению в обычном приложении, но прекрасно работает в управляемом.
Рассмотрим сценарий, описанный в обсуждении. У нас есть конфигурация, работающая в клиент-серверном варианте. Мы выполняем вызов серверной процедуры из модуля толстого клиента.
Пример кода, вызывающего ошибку в обычном приложении:
// Код на клиенте
Результат = СерверныйМодуль.ВыполнитьДействиеНаСервере();
// Код в общем модуле (Сервер)
Функция ВыполнитьДействиеНаСервере() Экспорт
Попытка
// Какое-то действие
ВызватьИсключение "Тестовая ошибка";
Исключение
// Возвращаем объект ИнформацияОбОшибке
Возврат ИнформацияОбОшибке();
КонецПопытки;
КонецФункции
Если запустить этот код в Толстом клиенте (Управляемое приложение), он отработает корректно. Если же запустить тот же самый код в Толстом клиенте (Обычное приложение), мы получим ошибку: "Попытка передачи мутабельного значения".
Давайте разберемся, почему так происходит, ведь в обоих случаях используется "Толстый клиент".
Хотя тип клиента называется одинаково ("Толстый"), среда выполнения (контекст), в которой работает код, кардинально отличается.
В этом "шлюзе" правила валидации данных работают строже. Платформа в обычном приложении не может гарантировать целостность мутабельных (изменяемых) объектов при их передаче через границу клиент-сервер.
Мутабельные значения — это объекты, которые могут изменять свое состояние после создания. К ним относятся:
СписокЗначенийТаблицаЗначенийИнформацияОбОшибкеВ строгой клиент-серверной архитектуре передача таких объектов по ссылке невозможна (так как память разная), а передача по значению (копирование) может привести к логическим ошибкам: если сервер изменит переданный список, клиент об этом не узнает автоматически.
Тип ИнформацияОбОшибке особенно сложен: он может содержать внутри себя контекст возникновения ошибки, ссылки на модули и формы. В режиме Обычного приложения этот объект может захватить ссылки на контекст старого интерфейса (Windows-окна), которые физически невозможно сериализовать для передачи между процессами.
Если проблема возникает при использовании стандартных библиотек (например, БСП) или общих модулей, которые не обязательно должны выполняться на сервере при работе в толстом клиенте, мы можем изменить место исполнения кода.
В обсуждении было найдено эффективное решение для интеграции БСП в старую базу:
Установите флажок "Клиент (обычное приложение)" или "Толстый клиент" в свойствах общего модуля.
Если модуль скомпилирован и на клиенте, и на сервере, то при вызове метода из толстого клиента платформа предпочтет выполнить код локально (без вызова сервера), если это возможно. Это полностью исключает необходимость сериализации данных через сеть/процессы.
Если серверный вызов необходим (например, для доступа к данным, недоступным на клиенте), то единственным архитектурно верным способом передачи мутабельных данных является использование Временного хранилища.
Давайте перепишем наш пример правильно, чтобы он работал в любом режиме запуска:
// Код на клиенте
АдресВХранилище = СерверныйМодуль.ВыполнитьДействиеНаСервере();
Если ЗначениеЗаполнено(АдресВХранилище) Тогда
// Получаем данные уже на клиенте
ДанныеОшибки = ПолучитьИзВременногоХранилища(АдресВХранилище);
// Обрабатываем ошибку...
КонецЕсли;
// Код в общем модуле (Сервер)
Функция ВыполнитьДействиеНаСервере() Экспорт
Попытка
// Какое-то действие
Возврат ""; // Все хорошо
Исключение
Информация = ИнформацияОбОшибке();
// Помещаем мутабельный объект во временное хранилище
Возврат ПоместитьВоВременноеХранилище(Информация, Новый УникальныйИдентификатор);
КонецПопытки;
КонецФункции
Метод ПоместитьВоВременноеХранилище специально разработан для сохранения любых сериализуемых данных на сервере и возврата клиенту строкового адреса (UUID), который является простым типом (строка) и легко передается.
Если вам не нужен весь сложный объект ИнформацияОбОшибке, а достаточно только текста, не возвращайте сам объект. Верните структуру или строку.
Правильный подход с сериализацией в простые типы:
// На сервере
Исключение
Инфо = ИнформацияОбОшибке();
// Создаем Структуру (она является коллекцией, но корректно сериализуется,
// если внутри простые типы)
Результат = Новый Структура;
Результат.Вставить("ЕстьОшибка", Истина);
Результат.Вставить("Описание", ПодробноеПредставлениеОшибки(Инфо));
Возврат Результат;
КонецПопытки;
Аналогично следует поступать со СпискомЗначений. В Синтаксис-помощнике для СписокЗначений есть примечание о доступности и сериализации, но для обычного приложения надежнее преобразовать список в Массив структур перед возвратом на клиент.
ИнформацияОбОшибке, СписокЗначений с сервера на клиент в обычном приложении не поддерживается и вызывает ошибку мутабельности.