Клиент или сервер: что где выполняется
Один из первых вопросов на собеседовании: покажите код с директивами компиляции и спросите, где что выполнится.
Клиент-серверная архитектура 1С — модель, в которой интерфейс (формы, кнопки, поля) живёт на клиенте, а работа с базой данных идёт только на сервере; между ними код «переезжает» по директивам компиляции.
Что вообще спрашивают
Работа пользователя в 1С устроена так: тонкий клиент (или веб-клиент) показывает формы на компьютере сотрудника, а кластер серверов держит соединение с СУБД и выполняет тяжёлую логику. Клиент и сервер — это два разных процесса, часто на разных машинах. Поэтому один и тот же модуль формы физически компилируется в двух местах, и разработчик директивами указывает, где именно должна оказаться каждая процедура.
На собеседовании обычно дают маленький фрагмент и спрашивают: «Где выполнится эта строка? Почему тут ошибка компиляции?». Проверяют не зубрёжку, а понимаете ли вы, что клиент и сервер разделены не условно, а по-настоящему.
Ключевые директивы компиляции в модуле управляемой формы:
&НаКлиенте— процедура выполняется на компьютере пользователя. Здесь доступны элементы формы, диалоги,ОткрытьФорму, но нет прямого доступа к базе.&НаСервере— выполняется на сервере, есть доступ к базе и к данным формы (реквизитам). На сервер целиком передаётся контекст формы.&НаСервереБезКонтекста— тоже сервер, но контекст формы не передаётся: быстрее и дешевле, но внутри не видно реквизитов формы — только переданные параметры.
Живой пример
Пользователь нажал кнопку. Обработчик всегда клиентский — интерфейс живёт на клиенте. Но чтобы прочитать данные из базы, нужно «сходить» на сервер:
&НаКлиенте
Процедура ЗаполнитьЦенуПоТовару(Команда)
// Мы на клиенте: базы тут нет, поэтому вызываем серверный метод
Если НЕ ЗначениеЗаполнено(Объект.Товар) Тогда
Сообщить("Сначала выберите товар");
Возврат;
КонецЕсли;
Объект.Цена = ПолучитьЦенуСервер(Объект.Товар);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьЦенуСервер(СсылкаНаТовар)
// Мы на сервере: доступ к базе есть
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Цена ИЗ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, Номенклатура = &Товар)";
Запрос.УстановитьПараметр("Дата", ТекущаяДата());
Запрос.УстановитьПараметр("Товар", СсылкаНаТовар);
Выборка = Запрос.Выполнить().Выбрать();
Возврат ?(Выборка.Следующий(), Выборка.Цена, 0);
КонецФункции
Обратите внимание: параметр в серверную функцию мы передали явно (СсылкаНаТовар), потому что выбрали &НаСервереБезКонтекста — внутри неё реквизит Объект.Товар недоступен.
Почему нельзя обращаться к базе с клиента
Это любимый добивающий вопрос. Причин несколько, и хорошо назвать хотя бы две:
- Безопасность. Если бы клиент напрямую видел СУБД, любой пользователь мог бы обойти права и бизнес-логику. Сервер — единственный «привратник» к данным.
- Архитектура. Клиент вообще не держит соединение с базой данных — его держит рабочий процесс кластера. Строка запроса на клиенте просто не скомпилируется.
- Производительность и масштаб. Тяжёлые выборки должны идти рядом с базой, а не гонять терабайты по сети на слабый ноутбук кассира.
&НаКлиенте
Процедура НеправильноЧитаемБазу()
// ОШИБКА КОМПИЛЯЦИИ: объект "Запрос" недоступен на клиенте
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Ссылка ИЗ Справочник.Контрагенты";
Результат = Запрос.Выполнить();
КонецПроцедуры
Результат:
Ошибка компиляции модуля: обращение к процедуре (функции) как к переменной (Запрос)
— тип "Запрос" недоступен в контексте "НаКлиенте".
Как это работает: цена вызова сервера
Каждый переход с клиента на сервер — это серверный вызов: сериализация данных, передача по сети, разбор на другой стороне. Один вызов — это доли секунды, но если вызвать сервер в цикле по строкам таблицы, набегают секунды и минуты «тормозов».
Отсюда правило: минимизируйте число серверных вызовов. Плохо — дёргать сервер на каждую строку; хорошо — собрать данные и сходить один раз. Здесь же всплывает разница контекстов: вызов &НаСервере тащит на сервер и обратно весь контекст формы (все реквизиты, таблицы) — это дороже, чем &НаСервереБезКонтекста, куда едут только нужные параметры.
// ПЛОХО: сервер дёргается на каждую строку — N вызовов
&НаКлиенте
Процедура ЗаполнитьЦеныПлохо()
Для Каждого Строка Из Объект.Товары Цикл
Строка.Цена = ПолучитьЦенуСервер(Строка.Номенклатура); // вызов сервера в цикле!
КонецЦикла;
КонецПроцедуры
// ХОРОШО: один серверный вызов на всю таблицу
&НаКлиенте
Процедура ЗаполнитьЦеныХорошо()
ЗаполнитьВсеЦеныНаСервере(); // внутри — один проход по таблице формы
КонецПроцедуры
Как отвечать
Скелет сильного ответа: «Клиент отвечает за интерфейс, сервер — за данные. Директивой я задаю, где компилируется процедура. С клиента к базе обратиться нельзя: там просто нет объектов вроде Запрос, и это правильно с точки зрения безопасности. Каждый переход на сервер — дорогой серверный вызов, поэтому я стараюсь ходить на сервер редко и, где можно, использую &НаСервереБезКонтекста, чтобы не гонять контекст формы».
Если попросят пример из практики — расскажите про заполнение табличной части: наивный код вызывает сервер в цикле и тормозит, а правильный делает один вызов и обрабатывает все строки на сервере.
Частые ошибки на собеседовании
- Говорить «код выполняется там, где написан». Нет — выполняется там, где указывает директива, а не где физически лежит строка в модуле.
- Путать
&НаСервереи&НаСервереБезКонтекстаили считать, что разницы нет. Разница именно в передаче контекста формы, и она влияет на производительность. - Забывать про цену серверного вызова и спокойно ставить обращение к серверу внутрь цикла.
- Утверждать, что «с клиента к базе можно, просто медленно». Нельзя вообще — не скомпилируется.
Итоги
- Клиент — интерфейс, сервер — данные; директива компиляции решает, где выполнится процедура.
- С клиента нет доступа к базе (нет объекта
Запроси т.п.) — по соображениям безопасности и архитектуры. - Серверный вызов — дорогая операция; вызовы в цикле — классический источник тормозов.
&НаСервереБезКонтекстадешевле&НаСервере, потому что не передаёт контекст формы.