Зарисовки на тему оформления кода HTTP-сервиса 4. Контроллер
В ленте типовых HTTP-сервисов паттерн «голый роутер» давно перестал быть моветоном. Но контроллер — это не просто вызов функции, а ответственность за жизненный цикл запроса. Опытные разработчики часто путают «тонкий» контроллер с «пустым», а потом удивляются, почему при масштабировании сервис становится нагромождением дублирующейся валидации и бесконечных попыток/исключений. Разбираем, где проходит грань между архитектурной опрятностью и избыточностью, и почему в 1С контроллер — это не класс, а соглашение.
Контекст: зачем контроллер в HTTP-сервисе 1С?
Стандартный шаблон модуля HTTP-сервиса — пара десятков строк с вызовом одной экспортной процедуры. Когда таких роутов становится больше десяти, каждый обрастает копипастой: разбор заголовков, валидация JSON, обработка ошибок, логирование. Проблема — отсутствие единой точки входа, где можно перехватить исключение, нормализовать ответ и зафиксировать метрику.
Контроллер — это не объект метаданных, а концептуальная прослойка между роутером (который уже есть в конфигурации) и бизнес-логикой. Он принимает на себя:
- извлечение и предобработку входных данных (тело, параметры пути, заголовки);
- сквозную валидацию (формат JSON, обязательные поля, типы);
- маршрутизацию внутри одного роута (GET/POST/разные действия);
- единый формат ответа (код состояния, структура ошибки).
Почему «голый роутер» — это плохо?
// Типичный код новичка — всё в одной процедуре-обработчике
Функция ОбработкаЗапроса(Запрос, Ответ, ДополнительныеПараметры) Экспорт
ТелоСтрока = Запрос.ПолучитьТелоКакСтроку();
СтруктураВхода = ПрочитатьЗначениеJSON(ТелоСтрока);
Если СтруктураВхода = Неопределено Тогда
Ответ.УстановитьТелоИзСтроки("""Ошибка""");
Ответ.КодСостояния = 400;
Возврат;
КонецЕсли;
Попытка
Данные = Обработать(СтруктураВхода);
Исключение
ЗаписьЖурналаРегистрации(...);
Ответ.УстановитьТелоИзСтроки("""Внутренняя ошибка""");
Ответ.КодСостояния = 500;
Возврат;
КонецПопытки;
Ответ.УстановитьТелоИзСтроки(ЗаписатьЗначениеJSON(Данные));
Ответ.КодСостояния = 200;
КонецФункции
Проблема этого подхода — смешение ответственности. Обработка ошибок, парсинг, бизнес-логика — всё в одной функции. При копировании в соседний роут забывают поменять код ошибки или не обрабатывают уникальный случай, и сервис начинает «молча глотать» некорректные запросы.
Ключевой тезис: Контроллер должен быть настолько тонким, чтобы его можно было прочитать за один экран монитора, и настолько толстым, чтобы он не допускал повторения одной и той же валидации в каждом роуте. Баланс достигается не количеством кода, а структурой вызовов.
Архитектурные сценарии: «тонкий» vs «жирный» контроллер
В экосистеме 1С сложились два подхода к реализации контроллера. Ни один не является серебряной пулей — выбор зависит от сложности сервиса, количества методов и требований к тестируемости.
Сценарий 1: Контроллер как фасад
Подходит для простых CRUD-сервисов (до 20 методов). Контроллер — одна общая функция-диспетчер, которая получает имя действия из URL или заголовка и вызывает конкретный обработчик. Валидация и формат ответа вынесены в общие процедуры этого же модуля.
// Пример диспетчера в модуле HTTP-сервиса
Функция ВыполнитьДействие(ИмяДействия, Запрос, Ответ)
// Общая валидация тела запроса
ТелоСтрока = Запрос.ПолучитьТелоКакСтроку();
СтруктураJSON = ПрочитатьЗначениеJSON(ТелоСтрока);
Если ТипЗнч(СтруктураJSON) <> Тип("Структура") Тогда
ВернутьОшибку(Ответ, 400, "Неверный формат JSON");
Возврат;
КонецЕсли;
// Вызов конкретного обработчика
Результат = Обработчики[ИмяДействия](СтруктураJSON);
// Единый формат ответа
Ответ.УстановитьТелоИзСтроки(ЗаписатьЗначениеJSON(Результат));
Ответ.КодСостояния = 200;
КонецФункции
Плюсы: единая точка входа, легко подключать логирование и метрики. Минусы: модуль быстро разрастается, если в каждом обработчике своя особая логика валидации и кодов состояния.
Сценарий 2: Контроллер-команда (по действию)
Для сложных сервисов с нетривиальными бизнес-правилами (например, проверка взаимосвязей, каскадные операции). Каждый роут получает свой объект-контроллер (или набор функций) в отдельном модуле-обработчике, а общий «степлер» собирает ответ с передачей контекста ошибки.
// Модуль контроллера для конкретного метода (например, "создатьПользователя")
Функция СоздатьПользователя(Запрос, ДопПараметры) Экспорт
// Валидация на уровне контроллера
ТелоСтрока = Запрос.ПолучитьТелоКакСтроку();
Данные = ПрочитатьЗначениеJSON(ТелоСтрока);
// Проверка обязательных полей
Если Не Данные.Свойство("Имя") Или Не Данные.Свойство("Email") Тогда
Возврат Новый HTTPОтвет(400, "Требуются поля Имя и Email");
КонецЕсли;
// Вызов бизнес-логики
Попытка
Результат = ОбработкаПользователей.Добавить(Данные);
Исключение
Возврат Новый HTTPОтвет(500, КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
Возврат Новый HTTPОтвет(201, ЗаписатьЗначениеJSON(Результат));
КонецФункции
Плюсы: изоляция, легко тестировать каждый контроллер отдельно. Минусы: дублирование парсинга и кода ответа — если не завести общий базовый модуль.
Ловушка: Часто разработчики в погоне за «чистотой» выносят в контроллер даже логирование и отправку уведомлений. В результате контроллер снова становится толстым, а бизнес-логика — размазанной. Правило: контроллер отвечает только за HTTP-контракт (вход/выход), всё остальное в бизнес-слое.
Практическое руководство: как построить контроллер без боли
Ниже — чек-лист, который мы выработали на нескольких промышленных интеграциях. Он не претендует на истину в последней инстанции, но проверен в бою.
Чек-лист проектирования контроллера
- Определите единый формат ответа. Создайте функцию
СоздатьУспешныйОтвет(Код, Данные)иСоздатьОшибку(Код, Текст). Возвращайте их из всех обработчиков. - Разделите чтение и валидацию. Выносите парсинг JSON в отдельную функцию с обработчиком
Попытка. ВозвращайтеСтруктура(при успехе) илиНеопределено. - Используйте промежуточный слой контекста. Создайте структуру с полями
Запрос,Ответ,ПараметрыПути,Пользователь(если авторизация). Передавайте её в каждый контроллер. - Не генерируйте исключения в контроллере для бизнес-логики. Исключение — только для нештатных ситуаций (нет соединения с БД, ошибка платформы). Ожидаемые ошибки (невалидные данные, запись недоступна) обрабатывайте через коды состояния.
- Тестируйте изоляцию контроллера. Напишите модульные тесты, вызывая экспортные функции напрямую с искусственным контекстом. Это обнаружит «утечку» логики в контроллер.
Типичные ошибки
- Контроллер пишет в базу. Запись, изменение данных — прерогатива бизнес-слоя. Контроллер только вызывает метод и возвращает ответ.
- Все ответы — 200 ОК с кодом ошибки внутри JSON.
Расширение «НОПик» для 1С — встраиваемый коннектор к внешнему AI с интеллектуальным поиском по базе. Задавайте вопросы обычными словами - AI сам найдёт нужное. 45 дней бесплатно.
Попробовать НОПик →