При разработке на управляемых формах в системе «1С:Предприятие 8» программисты часто сталкиваются с директивами компиляции, разделяющими код на клиентский и серверный. Особый интерес и множество споров вызывает поведение толстого клиента в клиент-серверном режиме работы базы данных. Многих удивляет тот факт, что в толстом клиенте под директивой &НаКлиенте система позволяет без ошибок создать объект Запрос, выполнить его и получить данные напрямую из базы данных.
В этой статье мы подробно разберем, как устроен этот механизм изнутри, разрешим популярные заблуждения о «выполнении запросов прямо на компьютере пользователя» и выясним, почему такое поведение недопустимо в тонком клиенте, а также к каким проблемам производительности оно приводит.
Среди разработчиков 1С (особенно тех, кто начинал с обычных форм) распространено заблуждение, что в толстом клиенте (как и в обычном приложении) все вычисления и запросы «выполняются на клиенте». На самом деле, при работе в клиент-серверном варианте платформа всегда использует классическую трехзвенную архитектуру:
1cv8.exe (толстый клиент) или 1cv8c.exe (тонкий клиент), запущенный на рабочем месте пользователя.rphost.exe.Даже если мы пишем код внутри процедуры с директивой &НаКлиенте в толстом клиенте, прямого физического доступа к СУБД у клиентского компьютера нет. У толстого клиента отсутствуют встроенные драйверы СУБД для прямого подключения к таблицам базы данных (если только разработчик вручную не инициализирует внешнее COM-соединение типа ADODB.Connection, что является совершенно другой технологией).
Все запросы к базе данных в обязательном порядке проходят через сервер приложений 1С. Когда в толстом клиенте на стороне клиента вызывается метод Запрос.Выполнить(), платформа осуществляет так называемый неявный вызов сервера (implicit server call).
Проанализируем детально по шагам, что происходит под капотом платформы, когда мы выполняем следующий код под директивой &НаКлиенте в толстом клиенте:
&НаКлиенте
Процедура ВыполнитьЗапросНаКлиенте(Команда)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
КонецПроцедуры
При вызове этого кода на клиентской машине происходят следующие скрытые процессы:
rphost.exe).РезультатЗапроса, полностью упаковывает всю выборку в сетевой пакет и отправляет обратно на клиентский компьютер.1cv8.exe принимает пакет, воссоздает в оперативной памяти объект РезультатЗапроса, после чего управление возвращается в код разработчика, и начинается обход выборки.Таким образом, физически запрос к базе данных всегда выполняет СУБД по команде сервера 1С. Если открыть утилиту мониторинга СУБД (например, SQL Server Management Studio или консоль pgAdmin) и посмотреть активные транзакции, то в качестве источника запроса мы всегда увидим сетевое имя и IP-адрес сервера приложений 1С (где крутится rphost.exe), а не имя компьютера конечного пользователя.
Попытка выполнить аналогичный код в тонком клиенте (1cv8c.exe) приведет к ошибке компиляции: тип Запрос на клиенте будет попросту недоступен. Разработчики платформы пошли на это ограничение намеренно по двум ключевым причинам:
Облегчение дистрибутива и потребления памяти. Тонкий клиент проектировался как легковесное приложение, способное быстро запускаться, работать на мобильных устройствах, слабых офисных ПК или даже внутри веб-браузера. Чтобы сделать его «тонким», из его исполняемого кода были исключены многие тяжеловесные библиотеки платформы: трансляторы запросов, объектные модели метаданных, менеджеры тяжелых типов данных (таких как СправочникОбъект или ДокументОбъект). Толстый клиент (1cv8.exe), напротив, содержит абсолютно все библиотеки платформы в своем локальном исполняемом коде, поэтому технически он «умеет» работать с объектом Запрос на своей стороне.
Архитектурный контроль. Позволив разработчикам использовать запросы на клиенте в толстом клиенте, фирма «1С» столкнулась с тем, что программисты начали массово писать неоптимальный код, перегружающий сеть. Полный запрет использования запросов и объектной техники на клиенте в тонком клиенте — это жесткий архитектурный барьер, заставляющий разработчиков явно разделять логику приложения на клиентскую (интерфейсную) и серверную (бизнес-логику и работу с БД).
Хотя толстый клиент управляемого приложения технически позволяет писать запросы под директивой &НаКлиенте, делать это в реальных конфигурациях категорически запрещено стандартами разработки «1С». Рассмотрим основные проблемы, к которым ведет такая практика:
&НаСервере), мы можем сразу обработать полученные данные на месте: свернуть таблицу значений, отфильтровать ее, рассчитать итоги и передать на клиент только несколько итоговых строк или чисел. Если же запрос выполняется под директивой &НаКлиенте, сервер 1С вынужден сериализовать и отправить по сети абсолютно всю выборку данных. При объеме выборки в десятки тысяч строк сетевой канал между сервером и клиентом будет мгновенно забит, а клиентское приложение «зависнет» на время десериализации гигантского пакета.Давайте проанализируем, как правильно оптимизировать код и перенести работу с базой данных на серверную сторону. Рассмотрим правильный шаблон реализации:
&НаКлиенте
Процедура ВыполнитьЗапросПравильно(Команда)
// Вызываем серверный метод для получения подготовленных данных
ТаблицаРезультата = ПолучитьДанныеНоменклатурыНаСервере();
// Обрабатываем легкий результат на клиенте (например, выводим пользователю)
Для Каждого СтрокаРезультата Из ТаблицаРезультата Цикл
Сообщить(СтрокаРезультата.Наименование);
КонецЦикла;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьДанныеНоменклатурыНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 100
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
// Выгружаем результат в массив структур для минимизации трафика передачи
РезультатТаблица = Запрос.Выполнить().Выгрузить();
МассивРезультатов = Новый Массив;
Для Каждого Строка Из РезультатТаблица Цикл
СтруктураСтроки = Новый Структура("Ссылка, Наименование", Строка.Ссылка, Строка.Наименование);
МассивРезультатов.Добавить(СтруктураСтроки);
КонецЦикла;
Возврат МассивРезультатов;
КонецФункции
Обратите внимание на использование директивы &НаСервереБезКонтекста. Этот контекст является наиболее легковесным, так как при вызове такой функции с клиента на сервер не передаются данные самой формы (ее реквизиты), что экономит сетевой трафик и ускоряет работу системы.
Внутри серверной функции мы выполнили запрос к базе данных, выгрузили его в таблицу, переложили только необходимые поля в массив простых структур и вернули этот массив на клиент. В результате по сети ушел минимальный объем данных, а сама база данных отработала максимально эффективно в родном для нее серверном окружении.