Директивы компиляции и контекст в 1С 8.3: как код делится между клиентом и сервером

· 10 мин

Содержание

На клиенте в 1С 8.3 выполняется всё, что связано с пользователем: открытие форм, реакция на кнопки, диалоги, работа с файлами на его компьютере. На сервере — всё, что трогает базу: запросы, запись и проведение документов, блокировки, транзакции. Где именно выполнится процедура, решает директива компиляции над ней: &НаКлиенте, &НаСервере, &НаСервереБезКонтекста и другие.

Это правило одинаково для файловой и клиент-серверной базы, для тонкого клиента и веб-клиента. Меняется только цена ошибки: в файловой базе лишний вызов сервера почти незаметен, а на кластере с 50 пользователями и удалённым офисом он превращается в секунды ожидания на каждом нажатии.

Почему код 1С физически живёт в двух местах

В управляемом приложении клиент и сервер — это разные процессы, часто на разных компьютерах. Тонкий клиент (1cv8c.exe) или браузер с веб-клиентом показывают интерфейс. Рабочий процесс сервера (rphost) хранит сеанс, выполняет запросы и общается с СУБД.

Между ними — сеть. Каждый переход с клиента на сервер и обратно — это сетевой вызов: данные упаковываются, передаются, распаковываются.

Что из этого следует на практике:

  • 🖥️ на клиенте нет доступа к базе данных — ни запросов, ни чтения реквизитов по ссылке;
  • 🗄️ на сервере нет пользователя — нельзя открыть форму или задать вопрос;
  • 🔁 каждое обращение к серверу стоит времени, и его надо экономить;
  • 📦 между клиентом и сервером ходят только значения, которые платформа умеет передать.

В файловом варианте серверная часть работает внутри того же процесса клиента. Но логическое разделение остаётся тем же: код с директивой &НаКлиенте всё равно не сможет выполнить запрос. Поэтому правильно написанная форма одинаково работает и в файловой базе, и на кластере.

Толстый клиент в режиме обычного приложения устроен иначе: там многое выполняется локально. Всё, что ниже, относится к управляемому приложению — интерфейсу «Такси» и современным типовым конфигурациям.

Что доступно только на стороне клиента

Клиентский код отвечает за взаимодействие с человеком и его компьютером. Сюда относится:

  1. Обработчики событий формы и элементов — ПриОткрытии, ПередЗакрытием, ПриИзменении поля, нажатие кнопки команды.
  2. Открытие форм — ОткрытьФорму, ПолучитьФорму выполняются только на клиенте.
  3. Диалоги — ПоказатьПредупреждение, ПоказатьВопрос, ПоказатьВводЗначения. Вызывать их на сервере бессмысленно: там некому ответить.
  4. Файлы на компьютере пользователя — выбор файла, загрузка и выгрузка через НачатьПомещениеФайла, ПолучитьФайл и родственные методы.
  5. Оповещения и обработчики ожидания — Оповестить, ПодключитьОбработчикОжидания.
  6. Оборудование — сканеры штрихкодов, фискальные регистраторы, весы подключаются к клиентскому компьютеру.

Диалоги в 8.3 асинхронные. Старые модальные Вопрос() и Предупреждение() в конфигурациях с отключённым режимом модальности не работают. Вместо них — ПоказатьВопрос с ОписаниеОповещения или, начиная с платформы 8.3.18, асинхронные функции с Ждать.

&НаКлиенте
Асинх Процедура Очистить(Команда)
    Ответ = Ждать ВопросАсинх("Очистить табличную часть?", РежимДиалогаВопрос.ДаНет);
    Если Ответ = КодВозвратаДиалога.Да Тогда
        Объект.Товары.Очистить();
    КонецЕсли;
КонецПроцедуры

Табличную часть объекта здесь можно очистить прямо на клиенте: реквизит Объект — это данные формы, они уже лежат на клиенте. Обращаться к серверу не нужно.

Серверная логика: всё, что касается данных

На сервере выполняется работа с информационной базой:

  • 🔍 запросы (Новый Запрос) и выборки;
  • 💾 запись, проведение, пометка на удаление объектов;
  • 🔒 транзакции и управляемые блокировки (БлокировкаДанных);
  • 📄 формирование отчётов и печатных форм (табличный документ собирается на сервере, а показывается на клиенте);
  • 🧮 работа с прикладными типами: ТаблицаЗначений, ДеревоЗначений, СправочникОбъект, ДокументОбъект.

Отдельная группа — серверные события формы: ПриСозданииНаСервере, ПриЧтенииНаСервере, ПередЗаписьюНаСервере, ПослеЗаписиНаСервере. Они срабатывают в рамках того вызова, который платформа делает сама. Поэтому начальное заполнение формы правильно делать в ПриСозданииНаСервере, а не в ПриОткрытии с отдельным вызовом сервера.

Модули объектов, модули менеджеров и модули наборов записей целиком серверные. Ставить в них директивы не нужно, а писать в них интерактивные вещи нельзя.

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

Директивы компиляции и передача параметров

В модуле формы у каждой процедуры есть директива. Основных четыре:

ДирективаГде выполняетсяДоступ к данным формыДоступ к базе
&НаКлиентеклиентданет
&НаСервересерверда, форма передаётся на серверда
&НаСервереБезКонтекстасервернетда
&НаКлиентеНаСервереБезКонтекстатам, откуда вызвалинеттолько если вызвана на сервере

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

В общих модулях с флажками «Клиент» и «Сервер» одновременно, а также внутри серверных модулей встречаются инструкции препроцессора #Если Сервер Тогда … #КонецЕсли и #Если Клиент Тогда. Они отсекают куски кода, которые не должны компилироваться в одном из контекстов.

Что происходит при вызове &НаСервере

Клиентская процедура вызывает серверную. Платформа упаковывает данные формы — реквизиты, табличные части, состояние элементов — и отправляет их на сервер. Сервер выполняет код и возвращает изменённые данные. Форма с документом на 500 строк при каждом таком вызове гоняет по сети все 500 строк, даже если серверу нужна была одна дата.

Когда нужен &НаСервереБезКонтекста

Если серверу не нужны данные формы, используйте внеконтекстный вызов. Передаётся только то, что указано в параметрах:

&НаКлиенте
Процедура НоменклатураПриИзменении(Элемент)
    ТекСтрока = Элементы.Товары.ТекущиеДанные;
    ТекСтрока.Цена = ЦенаНоменклатуры(ТекСтрока.Номенклатура, Объект.Дата);
    ТекСтрока.Сумма = ТекСтрока.Цена * ТекСтрока.Количество;
КонецПроцедуры

&НаСервереБезКонтекста
Функция ЦенаНоменклатуры(Номенклатура, Дата)
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1
    |    Цены.Цена КАК Цена
    |ИЗ
    |    РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, Номенклатура = &Номенклатура) КАК Цены";
    Запрос.УстановитьПараметр("Дата", Дата);
    Запрос.УстановитьПараметр("Номенклатура", Номенклатура);
    Выборка = Запрос.Выполнить().Выбрать();
    Возврат ?(Выборка.Следующий(), Выборка.Цена, 0);
КонецФункции

Здесь по сети уходят две величины и возвращается одно число. Имя регистра в примере условное: в вашей конфигурации регистр цен может называться иначе и иметь другие измерения.

Какие значения можно передавать

Между клиентом и сервером передаются примитивы (Число, Строка, Дата, Булево), ссылки, Структура, Массив, Соответствие, ФиксированнаяСтруктура и другие сериализуемые типы. Внутри них — тоже только допустимые значения.

Нельзя передать на клиент ТаблицаЗначений, ДокументОбъект, РезультатЗапроса, Запрос. Для работы с ними на форме есть обёртки — реквизиты формы типа ДанныеФормыКоллекция и ДанныеФормыСтруктура. Перевод в прикладной объект и обратно — только на сервере, через РеквизитФормыВЗначение и ЗначениеВРеквизитФормы.

Большие объёмы — файлы, двоичные данные — передают через временное хранилище: клиент кладёт файл методом помещения файла, получает адрес и передаёт на сервер только строку адреса. Сервер забирает данные через ПолучитьИзВременногоХранилища.

Общие модули: флажки вместо директив

Общий модуль не разбивают директивами по процедурам. Его контекст задаётся флажками в свойствах модуля в конфигураторе:

  • Клиент (управляемое приложение) — модуль компилируется на клиенте;
  • Сервер — компилируется на сервере;
  • Внешнее соединение — доступен при подключении через COM;
  • Вызов сервера — серверные экспортные методы можно вызывать с клиента;
  • Привилегированный — код выполняется без проверки прав;
  • Глобальный — методы вызываются без имени модуля.

Самая частая путаница — между флажками «Сервер» и «Вызов сервера». Модуль только с флажком «Сервер» виден из серверного кода, но клиентский код его методы не увидит. Чтобы клиент мог вызвать функцию напрямую, нужен флажок «Вызов сервера».

Практичная схема, которой следуют типовые конфигурации на БСП:

  1. ИмяМодуля — серверный модуль с основной логикой.
  2. ИмяМодуляВызовСервера — тонкая прослойка с флажком «Вызов сервера», только экспортные функции для клиента.
  3. ИмяМодуляКлиент — клиентский модуль: формы, диалоги, оповещения.
  4. ИмяМодуляКлиентСервер — код, который нужен и там, и там: расчёты, проверки строк, работа со структурами.

Так серверная логика не выставляется наружу целиком, а каждый вызов сервера виден по имени модуля.

Повторное использование возвращаемых значений

У общего модуля без флажка «Глобальный» есть свойство «Повторное использование возвращаемых значений»: «Не использовать», «На время вызова» или «На время сеанса». Функция с одними и теми же параметрами тогда считается один раз, а дальше результат берётся из кэша.

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

Кэшировать на время сеанса можно только то, что действительно не меняется. Если закэшировать, например, курс валюты или остаток, пользователь будет видеть устаревшие значения до перезапуска.

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

Производительность формы определяют в первую очередь количество вызовов сервера и объём передаваемых данных, а не скорость кода.

Чтобы увидеть картину, запустите клиент с параметром командной строки /DisplayPerformance. Внизу окна появится счётчик: число вызовов сервера и переданный трафик. Нажмите кнопку на форме — и сразу станет видно, сколько обращений она делает.

Правила, которые дают основной выигрыш:

  1. Один вызов вместо нескольких. Если на клиенте нужно три значения, получите их одной серверной функцией, которая вернёт Структура.
  2. Внеконтекстный вызов вместо контекстного. &НаСервереБезКонтекста не гоняет форму по сети.
  3. Расчёты на клиенте. Сумма строки, НДС по ставке, проверка заполнения поля — всё это считается на клиенте без сервера. Общий код вынесите в модуль с флажками «Клиент» и «Сервер».
  4. Начальные данные — в ПриСозданииНаСервере. Всё, что форме понадобится, положите в реквизиты формы при её создании.
  5. Никаких вызовов в цикле. Цикл по 200 строкам с серверной функцией внутри — это 200 сетевых вызовов. Соберите массив, передайте его одним вызовом и получите Соответствие в ответ.
  6. Кэш в клиентских модулях — через повторное использование значений.

Для разбора медленных мест в конфигураторе есть замер производительности в отладчике: он показывает время по строкам кода и вызовы сервера.

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

Частые ошибки разделения контекстов

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

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

Директива &НаСервере у процедуры, которая ничего не берёт из формы, — повод для рефакторинга. Её почти всегда можно сделать внеконтекстной.

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

Код в файловой базе тоже делится на клиент и сервер?
Да. Серверная часть работает внутри процесса клиента, но директивы действуют так же. Код, написанный для файловой базы, без изменений работает на кластере.
Можно ли на клиенте выполнить запрос к базе?
Нет. Запросы, чтение реквизитов по ссылке и запись объектов доступны только на сервере. С клиента нужно вызвать серверную функцию.
Чем `&НаСервере` отличается от `&НаСервереБезКонтекста`?
Первая директива передаёт на сервер данные формы и даёт к ним доступ. Вторая передаёт только параметры процедуры, поэтому работает быстрее, но не видит реквизиты формы.
Как проверить, сколько вызовов сервера делает форма?
Запустите клиент с параметром /DisplayPerformance. В нижней части окна появится число вызовов сервера и объём переданных данных.
Почему нельзя вернуть таблицу значений на клиент?
ТаблицаЗначений существует только на сервере. Для формы используйте реквизит формы табличного типа и ЗначениеВРеквизитФормы либо верните массив структур.