При проектировании обмена данными между интернет-магазином и учетной системой 1С разработчики часто сталкиваются с принципиальным архитектурным спором: кто должен выступать инициатором соединения? С одной стороны, веб-разработчики предпочитают отдавать данные по запросу (модель Pull), чтобы не нагружать сайт лишней логикой очередей и повторных отправк. С другой стороны, разработчики 1С настаивают на событийной модели (модель Push), когда сайт мгновенно передает новые заказы на HTTP-сервис 1С, аргументируя это требованием «минимальной задержки» и снижением бесполезной нагрузки на веб-сервер. В этой статье мы подробно проанализируем обе модели, разберем их сильные и слабые стороны, а также рассмотрим компромиссные и наиболее надежные варианты реализации обмена.
В этой схеме веб-сайт выступает в роли пассивного сервера, предоставляющего GET-эндпоинт (API) для получения списка новых заказов. 1С выступает клиентом: она с определенной периодичностью (например, каждые 20 секунд или раз в минуту) отправляет HTTP-запросы к сайту, забирает накопившиеся заказы и помечает их как полученные.
Преимущества Pull-модели:
Недостатки Pull-модели:
В этой схеме роли меняются: сайт при создании или изменении заказа на стороне клиента мгновенно инициирует POST-запрос на опубликованный HTTP-сервис 1С. База 1С выступает в роли веб-сервера, принимающего входящие пакеты данных.
Преимущества Push-модели:
Недостатки Push-модели:
Если внедрение полноценной Push-модели пугает веб-разработчика из-за необходимости писать отказоустойчивую очередь тяжелых транзакций, а Pull-модель не устраивает 1С-ника из-за частого «холостого» опроса, мы рекомендуем использовать гибридный паттерн «Уведомление и забор» (Ping-and-Pull).
Проанализируем шаги этого процесса:
{"event": "new_order", "id": "12345"}.Такой подход избавляет сайт от необходимости хранить сложную очередь неотправленных тяжелых пакетов (если 1С недоступна, повторно отправить короткий пинг намного проще), исключает холостые запросы и обеспечивает скорость обмена, близкую к реальному времени.
При реализации Push-модели крайне важно, чтобы входящие HTTP-запросы обрабатывались системой 1С максимально быстро. Если при получении JSON-пакета 1С начнет синхронно создавать контрагента, искать номенклатуру, рассчитывать скидки и проводить документ ЗаказКлиента, веб-сервер быстро исчерпает свободные потоки, и сайт начнет получать ошибки 504 Gateway Timeout.
Рассмотрим правильную архитектуру обработки на стороне 1С: входящий запрос должен лишь записывать сырые данные JSON в буферный регистр сведений, после чего сразу возвращать сайту ответ 202 Accepted. Разбор буфера и создание документов будут происходить асинхронно в фоновом режиме.
Ниже представлен пример кода HTTP-сервиса 1С для приема заказов с проверкой на дубликаты (обеспечение свойства идемпотентности API):
// Обработчик метода POST HTTP-сервиса "ПринятьЗаказ"
Функция ДобавитьЗаказ(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
// Получаем тело запроса в виде строки JSON
ТелоСтрокой = Запрос.ПолучитьТелоКакСтроку("UTF-8");
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(ТелоСтрокой);
ДанныеЗаказа = ПрочитатьJSON(Чтение);
Чтение.Закрыть();
// Извлекаем уникальный идентификатор заказа на сайте
IDЗаказаНаСайте = ДанныеЗаказа.id;
Если ПустаяСтрока(IDЗаказаНаСайте) Тогда
Ответ.КодСостояния = 400; // Bad Request
Ответ.УстановитьТелоИзСтроки("{""error"":""Не указан ID заказа""}", "UTF-8");
Возврат Ответ;
КонецЕсли;
// Проверяем, не получали ли мы этот заказ ранее (идемпотентность)
ЗапросДублей = Новый Запрос;
ЗапросДублей.Текст =
"ВЫБРАТЬ Первые 1
| Очередь.ИдентификаторЗаказа КАК ИдентификаторЗаказа
|ИЗ
| РегистрСведений.ОчередьЗаказовСайта КАК Очередь
|ГДЕ
| Очередь.ИдентификаторЗаказа = &IDЗаказа";
ЗапросДублей.УстановитьПараметр("IDЗаказа", IDЗаказаНаСайте);
Если Не ЗапросДублей.Выполнить().Пустой() Тогда
// Заказ уже был успешно принят ранее, возвращаем статус 208 Already Reported
Ответ.КодСостояния = 208;
Ответ.УстановитьТелоИзСтроки("{""status"":""Заказ уже находится в обработке""}", "UTF-8");
Возврат Ответ;
КонецЕсли;
// Записываем JSON в буферную таблицу (Регистр сведений) для последующей обработки
МенеджерЗаписи = РегистрыСведений.ОчередьЗаказовСайта.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ИдентификаторЗаказа = IDЗаказаНаСайте;
МенеджерЗаписи.ДатаПолучения = ТекущаяДатаСеанса();
МенеджерЗаписи.ТелоJSON = ТелоСтрокой;
МенеджерЗаписи.Обработан = Ложь;
Попытка
МенеджерЗаписи.Записать(Истина);
Ответ.КодСостояния = 202; // Accepted (Принято к обработке)
Ответ.УстановитьТелоИзСтроки("{""status"":""Заказ успешно помещен в очередь""}", "UTF-8");
Исключение
Ответ.КодСостояния = 500; // Internal Server Error
Ответ.УстановитьТелоИзСтроки("{""error"":""Ошибка записи в базу данных 1С""}", "UTF-8");
КонецПопытки;
Возврат Ответ;
КонецФункции
Для разбора накопленной очереди заказов настроим регламентное задание 1С, которое будет запускать процедуру фоновой обработки с периодичностью, например, раз в 30 секунд. Проанализируем пример кода этой процедуры:
// Процедура выполняется в фоновом задании по расписанию
Процедура ОбработатьОчередьЗаказов() Экспорт
// Выбираем необработанные заказы из буфера
ЗапросОчереди = Новый Запрос;
ЗапросОчереди.Текст =
"ВЫБРАТЬ
| Очередь.ИдентификаторЗаказа КАК IDЗаказа,
| Очередь.ТелоJSON КАК ТелоJSON
|ИЗ
| РегистрСведений.ОчередьЗаказовСайта КАК Очередь
|ГДЕ
| Очередь.Обработан = ЛОЖЬ";
Результат = ЗапросОчереди.Выполнить();
Если Результат.Пустой() Тогда
Возврат;
КонецЕсли;
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
НачатьТранзакцию();
Попытка
// Читаем JSON данные конкретного заказа
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(Выборка.ТелоJSON);
ДанныеЗаказа = ПрочитатьJSON(Чтение);
Чтение.Закрыть();
// Создаем новый документ "Заказ клиента"
НовыйЗаказ = Документы.ЗаказКлиента.СоздатьДокумент();
НовыйЗаказ.Дата = ТекущаяДата();
НовыйЗаказ.ИдентификаторСайта = Выборка.IDЗаказа;
// Здесь выполняется бизнес-логика: поиск контрагента, заполнение ТЧ "Товары"
// ЗаполнитьРеквизитыЗаказа(НовыйЗаказ, ДанныеЗаказа);
НовыйЗаказ.Записать(РежимЗаписиДокумента.Проведение);
// Помечаем запись в регистре очереди как успешно обработанную
МенеджерЗаписи = РегистрыСведений.ОчередьЗаказовСайта.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ИдентификаторЗаказа = Выборка.IDЗаказа;
МенеджерЗаписи.Прочитать();
Если МенеджерЗаписи.Выбран() Тогда
МенеджерЗаписи.Обработан = Истина;
МенеджерЗаписи.ДатаОбработки = ТекущаяДатаСеанса();
МенеджерЗаписи.Записать(Истина);
КонецЕсли;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
// Логируем ошибку для последующего разбора администратором
ЗаписатьЗаписьВЖурналРегистрации("ОбменССайтом.ОшибкаОбработкиЗаказа",
УровеньЖурналаРегистрации.Ошибка,
,
,
"Ошибка при обработке заказа " + Выборка.IDЗаказа + ": " + ОписаниеОшибки());
// Сдвигаем дату или увеличиваем счетчик ошибок записи, чтобы временно пропустить проблемный заказ
ПропуститьПроблемныйЗаказ(Выборка.IDЗаказа, ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
КонецПроцедуры
В споре о том, кто должен выступать инициатором обмена, нет однозначно неправой стороны — всё зависит от требований к скорости, безопасности и доступного бюджета разработки.
Если во главу угла ставится максимальная скорость получения заказов без создания паразитной нагрузки на серверы — 1С-программист прав: необходимо настраивать событийный Push-обмен. Но для его безопасной и надежной работы веб-разработчику потребуется реализовать буферную очередь отправки на сайте, а 1С-специалисту — опубликовать отказоустойчивый асинхронный HTTP-сервис с гарантированной защитой от дублирования транзакций.