Как клиентский общий модуль обращается к серверу: флаг «Вызов сервера», параметры и исключения

· 10 мин

Содержание

Чтобы вызвать серверную функцию из клиентского общего модуля 1С, нужен отдельный серверный общий модуль с флагами «Сервер» и «Вызов сервера», а сама функция в нём должна быть объявлена с ключевым словом Экспорт. Тогда в клиентском коде достаточно написать ИмяМодуля.ИмяФункции(Параметры) — платформа сама передаст параметры на сервер, выполнит функцию и вернёт результат.

Частое заблуждение — пытаться поставить директиву &НаСервере прямо в клиентском общем модуле, как в модуле формы. В общих модулях директивы компиляции не работают: где выполняется код, решают флажки в свойствах модуля. Второе заблуждение — считать, что модуль с флагами «Клиент» и «Сервер» сразу даёт серверный вызов. Не даёт: такой модуль компилируется в обоих местах, и при вызове с клиента код выполнится на клиенте.

Где проходит граница между клиентом и сервером

В управляемом приложении 1С 8.3 код живёт в двух контекстах. Клиент — это тонкий клиент, веб-клиент или мобильный клиент на компьютере пользователя. Сервер — рабочий процесс кластера 1С (в файловом варианте серверный контекст работает внутри клиентского процесса, но правила те же).

На клиенте нет доступа к базе данных. Там недоступны Запрос, объекты справочников и документов, ТаблицаЗначений. Всё, что читает или пишет данные, выполняется на сервере.

Каждое обращение клиента к серверу — это отдельный серверный вызов:

  • 📦 параметры сериализуются и уходят на сервер;
  • ⚙️ сервер выполняет функцию в своём контексте;
  • 📨 результат (и изменённые параметры, если они переданы по ссылке) возвращается на клиент.

Важное следствие: серверный вызов не сохраняет состояние. Переменные серверного модуля между вызовами не живут, транзакция, начатая на сервере, должна закончиться в том же вызове.

В файловой базе вызов идёт без сети, поэтому «лишние» вызовы почти незаметны. Ошибки производительности всплывают только после перевода базы на клиент-сервер или публикации через веб-сервер.

Свойства общего модуля: какие флажки ставить

Откройте конфигуратор, в дереве Общие → Общие модули выделите модуль и откройте палитру свойств (Alt+Enter). Флажки определяют, где модуль компилируется и кто может к нему обращаться.

ФлажокЧто даётКогда ставить
Клиент (управляемое приложение)Модуль доступен в тонком, веб- и мобильном клиентеКлиентская логика: диалоги, оповещения, работа с формами
СерверМодуль компилируется на сервереРабота с данными
Вызов сервераЭкспортные методы серверного модуля видны с клиентаТолько для модуля-«шлюза» между клиентом и сервером
Внешнее соединениеМодуль доступен через COM-соединениеИнтеграции через внешнее соединение
ПривилегированныйКод выполняется без проверки правОсознанно, для узких задач
Повторное использование возвращаемых значенийКеширует результаты функцийРедко меняющиеся данные

Для нашей задачи нужна связка из двух модулей:

  1. Клиентский модуль, например РаботаСКонтрагентамиКлиент: флажок «Клиент (управляемое приложение)», остальные сняты.
  2. Серверный модуль РаботаСКонтрагентамиВызовСервера: флажки «Сервер» и «Вызов сервера», «Клиент» снят.

Суффиксы Клиент, ВызовСервера, КлиентСервер и ПовтИсп взяты из стандартов разработки 1С. Платформа их не требует, но по имени модуля сразу видно его контекст, и коллеги не будут гадать, почему метод недоступен.

Не ставьте «Вызов сервера» всем серверным модулям подряд. Каждый такой модуль открывает клиенту прямой доступ к своим экспортным функциям. Держите в нём только тонкие обёртки, а основную логику — в обычных серверных модулях без этого флажка.

Синтаксис вызова и директивы компиляции

В клиентском общем модуле серверная функция вызывается как метод другого модуля — через точку:

Результат = РаботаСКонтрагентамиВызовСервера.ДанныеКонтрагента(Контрагент);

Условия, без которых вызов не скомпилируется:

  • ✅ у серверного модуля стоит флажок «Вызов сервера»;
  • ✅ функция объявлена с Экспорт;
  • ✅ имя модуля и функции написано точно — проверка модуля (Ctrl+F7) покажет ошибку сразу.

Директивы &НаКлиенте, &НаСервере, &НаСервереБезКонтекста используются в модулях форм и команд. В общем модуле они не нужны: контекст задают свойства. Если модуль одновременно клиентский и серверный (КлиентСервер), для разделения кода внутри него применяют инструкции препроцессора:

#Если Сервер Тогда
	// этот фрагмент компилируется только на сервере
#КонецЕсли

Но и это не превращает клиентский вызов в серверный. Чтобы код реально ушёл на сервер, он должен лежать в модуле с флажком «Вызов сервера».

Почему нельзя вызвать серверный метод формы

Из клиентского общего модуля можно обратиться к экспортной клиентской процедуре формы через объект формы: Форма.ОбновитьОтображение(). А вот методы формы с &НаСервере из общего модуля недоступны. Если логика нужна и форме, и общему модулю, вынесите её в модуль ВызовСервера.

Пример: клиентский модуль получает данные контрагента с сервера

Задача: по кнопке проверить, заполнен ли ИНН у выбранного контрагента, и предупредить пользователя. Чтение данных — на сервере, сообщение — на клиенте.

Серверный модуль РаботаСКонтрагентамиВызовСервера (флажки «Сервер» и «Вызов сервера»):

Функция ДанныеКонтрагента(Знач Контрагент) Экспорт

	Результат = Новый Структура("Наименование, ИНН, КПП", "", "", "");

	Если Не ЗначениеЗаполнено(Контрагент) Тогда
		ВызватьИсключение НСтр("ru = 'Не указан контрагент'");
	КонецЕсли;

	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	Контрагенты.Наименование КАК Наименование,
	|	Контрагенты.ИНН КАК ИНН,
	|	Контрагенты.КПП КАК КПП
	|ИЗ
	|	Справочник.Контрагенты КАК Контрагенты
	|ГДЕ
	|	Контрагенты.Ссылка = &Ссылка";
	Запрос.УстановитьПараметр("Ссылка", Контрагент);

	Выборка = Запрос.Выполнить().Выбрать();
	Если Выборка.Следующий() Тогда
		ЗаполнитьЗначенияСвойств(Результат, Выборка);
	КонецЕсли;

	Возврат Результат;

КонецФункции

Клиентский модуль РаботаСКонтрагентамиКлиент (флажок «Клиент (управляемое приложение)»):

Процедура ПроверитьИНН(Контрагент) Экспорт

	Попытка
		Данные = РаботаСКонтрагентамиВызовСервера.ДанныеКонтрагента(Контрагент);
	Исключение
		ПоказатьПредупреждение(, КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
		Возврат;
	КонецПопытки;

	Если ПустаяСтрока(Данные.ИНН) Тогда
		ПоказатьПредупреждение(, СтрШаблон(
			НСтр("ru = 'У контрагента «%1» не заполнен ИНН'"), Данные.Наименование));
	КонецЕсли;

КонецПроцедуры

Из формы остаётся вызвать РаботаСКонтрагентамиКлиент.ПроверитьИНН(Объект.Контрагент) в клиентском обработчике команды.

Что здесь сделано правильно:

  • 🔹 один серверный вызов возвращает сразу три поля — не по вызову на каждое;
  • 🔹 на клиент уходит Структура с примитивами, а не объект справочника;
  • 🔹 параметр объявлен как Знач — ссылка не гоняется обратно на клиент;
  • 🔹 ошибка сервера перехвачена на клиенте и показана пользователю понятным текстом.

Передача параметров и возврат значений через границу

Между клиентом и сервером проходят только значения, которые платформа умеет сериализовать и которые существуют на клиенте.

Можно передаватьНельзя передавать
Строка, Число, Дата, Булево, НеопределеноСправочникОбъект, ДокументОбъект
Ссылки: СправочникСсылка, ДокументСсылка, перечисленияТаблицаЗначений, ДеревоЗначений
Структура, Массив, Соответствие и их фиксированные вариантыЗапрос, РезультатЗапроса, Выборка
УникальныйИдентификатор, ДвоичныеДанные, ХранилищеЗначенияОбъекты чтения и записи XML, JSON, файлов на сервере

Правило касается и вложенных значений. Если в Структуру положить ТаблицуЗначений, вызов упадёт так же, как при передаче таблицы напрямую.

Как обойти ограничение:

  1. Таблицу переведите в Массив структур на сервере и верните массив.
  2. Большой объём данных положите во временное хранилище (ПоместитьВоВременноеХранилище) и передайте на клиент только адрес.
  3. Для формы используйте реквизиты формы: ДанныеФормыКоллекция передаётся в серверные методы формы штатно.

Знач и параметры по ссылке

Параметры без Знач передаются по ссылке. При серверном вызове это значит: после выполнения функции платформа отправит значение параметра обратно на клиент, даже если его никто не менял. Для крупной структуры это лишний трафик на каждом вызове.

Правило простое: если функция не должна менять входной параметр, объявляйте его как Знач. Результат возвращайте через Возврат, а не через изменение параметров.

Исключения на сервере и как их ловить на клиенте

Исключение, возникшее в серверной функции, не теряется. Оно прерывает серверный вызов и поднимается на клиенте в точке вызова. Поэтому Попытка ... Исключение вокруг вызова в клиентском модуле работает так же, как вокруг обычного кода.

Что получить в блоке Исключение:

  • 🧾 ИнформацияОбОшибке() — объект с описанием, модулем и строкой ошибки;
  • 💬 КраткоеПредставлениеОшибки(...) — текст для пользователя;
  • 🛠 ПодробноеПредставлениеОшибки(...) — текст со стеком для журнала и отладки.

Порядок действий, который работает в реальных конфигурациях:

  1. На сервере проверяйте входные данные и вызывайте ВызватьИсключение с понятным текстом.
  2. Подробности ошибки пишите на сервере в журнал регистрации через ЗаписьЖурналаРегистрации — там они полезнее, чем на экране пользователя.
  3. На клиенте показывайте краткое представление и не продолжайте работу с пустым результатом.

Транзакцию, начатую на сервере через НачатьТранзакцию(), завершайте в том же вызове: ЗафиксироватьТранзакцию() или ОтменитьТранзакцию() в блоке Исключение. Не оставляйте открытую транзакцию в надежде закончить её следующим вызовом с клиента — так не получится, а данные останутся заблокированными до конца вызова.

Не глушите исключение пустым блоком Исключение КонецПопытки. Пользователь не увидит ошибку, а код ниже начнёт работать с Неопределено и упадёт уже в непонятном месте.

Как сократить число серверных вызовов

Скорость клиентского кода чаще всего упирается не в сам запрос, а в количество обращений к серверу. В клиент-серверной базе через интернет каждое лишнее обращение добавляет сетевую задержку.

Приёмы, которые дают эффект сразу:

  • 🚫 Не вызывайте сервер в цикле. Вместо 50 вызовов по одной строке передайте массив из 50 ссылок одним вызовом и получите Соответствие в ответ.
  • 📦 Собирайте данные пакетом. Одна функция возвращает Структуру со всем, что нужно форме, а не пять функций по одному полю.
  • 🗂 Кешируйте постоянные данные. Для настроек и справочной информации, которая меняется редко, создайте модуль ВызовСервера с флажком «Повторное использование возвращаемых значений» = «На время сеанса». Повторный вызов с теми же параметрами вернёт результат из кеша без обращения к серверу.
  • 🧹 Возвращайте минимум. Ссылка и три строки весят меньше, чем структура со всеми реквизитами объекта.
  • ⏳ Долгие операции — в фоновое задание. Если расчёт идёт минуты, клиент будет ждать всё это время: серверный вызов синхронный. Запустите фоновое задание и проверяйте его состояние по обработчику ожидания.

Кешируемый модуль возвращает одно и то же значение всем вызывающим. Отдавайте из него фиксированные коллекции (ФиксированнаяСтруктура, ФиксированныйМассив), чтобы случайное изменение результата в одном месте не испортило кеш для остальных.

Когда к серверу обращаться не нужно

Часть задач решается без серверного вызова вовсе:

  • 🧮 чистые вычисления — проверка контрольной суммы ИНН, форматирование суммы, разбор строки — кладите в модуль КлиентСервер, он выполнится на месте;
  • 📋 данные, которые уже есть в реквизитах формы, читайте прямо на клиенте, а не перезапрашивайте из базы;
  • 🔁 значения, нужные на каждом открытии формы, получайте одним вызовом при создании формы на сервере и храните в реквизите.

Как посчитать вызовы

В конфигураторе включите Сервис → Параметры → Запуск 1С:Предприятия → Дополнительно → Отображать показатели производительности и запустите отладку. В клиенте появится панель с количеством серверных вызовов и объёмом переданных данных. Нажмите кнопку на форме и посмотрите, сколько вызовов прибавилось. Если больше одного-двух на простое действие — ищите вызов в цикле.

Для детального разбора подойдёт замер производительности в отладчике: он покажет, какие строки кода сколько выполнялись.

Типовые ошибки при обращении к серверу из клиентского модуля

Диагностика: выберите симптом

Частые вопросы

Можно ли поставить флажки «Клиент» и «Вызов сервера» одному модулю?
Нет, «Вызов сервера» имеет смысл только для серверного модуля. Клиентская и серверная части должны быть разными модулями.
Работает ли вызов сервера из глобального общего модуля?
Да. Глобальный клиентский модуль вызывает функции модуля ВызовСервера так же, как обычный клиентский. Отличие только в том, что его собственные методы доступны без имени модуля.
Можно ли вызвать сервер асинхронно, не блокируя клиент?
Сам серверный вызов синхронный. Для долгих расчётов запускайте фоновое задание на сервере и опрашивайте его состояние обработчиком ожидания на клиенте.
Нужен ли флажок «Вызов сервера» для вызова из модуля формы?
Если форма вызывает общий модуль из метода &НаСервере, флажок не нужен: это уже серверный контекст. Флажок нужен, только когда к общему модулю обращаются из клиентского кода.
Сбрасывается ли кеш повторного использования после изменения данных?
Не сразу. Кеш живёт в пределах срока, выбранного в свойстве модуля, поэтому кешировать стоит только то, что почти не меняется. Принудительно сбросить его можно методом ОбновитьПовторноИспользуемыеЗначения().