Привет, меня зовут Андрей, я руковожу командой, которая делает интеграции 1С с BPM-системами и внешними API. За последние пару лет мы связывали 1С с ELMA365 на нескольких проектах, и каждый раз проходили один и тот же путь: сначала регламентное задание, потом боль, потом вынос интеграции в отдельный сервис.
В этой статье я с кодом и цифрами из продакшена покажу, где у подхода «обработка по расписанию» архитектурный потолок, и как асинхронная шина на Symfony решает проблему надёжности обмена. Сразу оговорка: регламентное задание — нормальный инструмент для своей ниши, и я не собираюсь его хоронить. Речь о том, где эта ниша заканчивается.
С чего началось: инцидент на 300 процессов в день
Торговая компания, около 300 запусков BPMN-процессов в сутки. Интеграция была сделана «как у всех»: внешняя обработка, регламентное задание каждые 5 минут, прямые HTTP-вызовы к API ELMA365.
В один понедельник ELMA365 обновлялась и была недоступна 40 минут. Задание всё это время висело на таймаутах, потом начало разгребать накопившийся backlog: три часа оно молотило базу, держало блокировки и тормозило рабочие места менеджеров. Интеграция с внешней системой деградировала скорость основной учётной системы.
Вот этот момент — когда чужой даунтайм становится вашим даунтаймом — и есть граница применимости синхронной схемы.
Как обычно выглядит синхронная интеграция
Классика жанра у интеграторов:
Процедура ОбработкаЗадания() Экспорт
Выборка = ПолучитьЗаказыНаПередачу(); // ~200 документов за проход
Для Каждого Заказ Из Выборки Цикл
Запрос = Новый HTTPЗапрос("/api/v1/orders");
Запрос.Заголовки.Вставить("Authorization", "Bearer " + ПолучитьТокен());
Запрос.УстановитьТелоИзСтроки(СформироватьJSON(Заказ), "application/json");
Соединение = Новый HTTPСоединение(
"elma.company.ru", 443, , , ,
Новый ЗащищенноеСоединениеOpenSSL(), 30);
Попытка
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
ОтметитьПереданным(Заказ.Ссылка);
Исключение
ЗаписатьВЖурналРегистрации(ОписаниеОшибки());
// документ подхватится в следующий проход...
// если не сломается логика флагов
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Формально это «настоящая» интеграция по API. Но выполняется она внутри того же процесса, что и весь остальной учёт. Что это означает под нагрузкой:
- Блокирующее ожидание. Пока обработка ждёт ответ API (до 30 секунд на документ при деградации сети), задание заблокировано целиком. 200 документов × таймаут = часы простоя очереди.
- Retry и очереди пишутся руками. Встроенный язык не даёт из коробки exponential backoff, dead-letter очередей и rate limiter. Это либо самописный код, плохо покрытый тестами, либо его просто нет.
- Нагрузка на основную базу. Каждый HTTP-вызов выполняется в том же кластере, где работают пользователи. При росте объёма обмена тормозят интерфейсы и отчёты.
- Слабая наблюдаемость. Логи в журнале регистрации или текстовом файле. Без Prometheus/Grafana о проблеме вы узнаете по жалобе пользователя, а не по алерту.
- Риск при обновлениях. Обработка встроена в конфигурацию или расширение — обновление типовой 1С может конфликтовать с метаданными, и разбирать это придётся вручную.
По нашему опыту, практический потолок такой схемы — порядка 500–1000 транзакций в час для производственных и торговых предприятий с активными BPM-процессами. Цифра ориентировочная: у вас она может отличаться в зависимости от частоты запусков процессов и сложности контекстов. Но порядок именно такой.
Что мы сделали вместо этого: отдельный сервис на Symfony
Идея простая: интеграция — это не часть учёта, это отдельный сетевой сервис, который должен переживать таймауты, ретраи и очереди сообщений. Архитектура получилась такая:

- 1С публикует события (новый заказ, смена статуса) — быстрым POST на эндпоинт коннектора или через таблицу регистрации изменений с курсором;
- коннектор (Symfony) мгновенно кладёт событие в очередь (Messenger) и возвращает 202 — 1С не ждёт, пока ELMA365 соизволит ответить;
- consumer-воркеры разбирают очередь и общаются с API ELMA365 со всеми ретраями;
- webhook'и от ELMA365 (статусы согласований) принимаются тем же коннектором и так же уходят в очередь, а затем пишутся обратно в 1С через HTTP-сервис.
Ключевой момент: 1С кладёт событие в очередь за миллисекунды. Недоступность ELMA365 больше не блокирует ничего внутри 1С — сообщения просто ждут своей очереди.
Конфигурация Messenger: retry и dead-letter из коробки
# config/packages/messenger.yaml
framework:
messenger:
failure_transport: failed
transports:
elma_sync:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%' # doctrine://default?queue_name=elma_sync
retry_strategy:
max_retries: 5
delay: 1000 # 1 сек
multiplier: 2 # 1 -> 2 -> 4 -> 8 -> 16 сек
max_delay: 60000
failed:
dsn: 'doctrine://default?queue_name=failed'
routing:
App\Message\SyncOrderToElmaMessage: elma_sync
Это готовые паттерны фреймворка, а не самописный код внутри обработки: они предсказуемо работают и покрыты тестами Symfony.
Сообщение и обработчик с идемпотентностью
// src/Message/SyncOrderToElmaMessage.php
final readonly class SyncOrderToElmaMessage
{
public function __construct(
public string $orderRef, // GUID ссылки 1С
public int $revision, // версия данных для идемпотентности
) {}
}
// src/MessageHandler/SyncOrderToElmaHandler.php
#[AsMessageHandler]
final readonly class SyncOrderToElmaHandler
{
public function __construct(
private OneCOrderReader $oneC,
private Elma365Client $elma,
) {}
public function __invoke(SyncOrderToElmaMessage $message): void
{
$order = $this->oneC->readOrder($message->orderRef);
if ($order === null) {
return; // документ удалён в 1С — просто забываем
}
if ($order->revision <= $this->elma->lastSyncedRevision($message->orderRef)) {
return; // идемпотентность: эта версия уже отправлена
}
$this->elma->upsertOrder($order);
}
}
Идемпотентность через revision — обязательна. При ретраях и дублях событий (а они будут) вы не должны дважды завести один и тот же заказ в BPM.
Webhook от ELMA365: принимаем за миллисекунды
// src/Controller/ElmaWebhookController.php
#[Route('/webhook/elma365/process-status', methods: ['POST'])]
public function processStatus(Request $request, MessageBusInterface $bus): Response
{
// Синхронно только проверяем подпись — обработку в фон
$payload = $this->signatureVerifier->verify($request);
$bus->dispatch(new ApplyProcessStatusMessage(
processId: $payload['processId'],
status: $payload['status'],
));
return new Response('', Response::HTTP_ACCEPTED); // 202 за миллисекунды
}
Webhook принимается мгновенно и обрабатывается в фоне — без риска таймаута на стороне ELMA365 и без потери статусов при перезапусках.
Масштабирование и наблюдаемость
# docker-compose.yml
services:
worker:
build: .
command: php bin/console messenger:consume elma_sync --time-limit=3600
restart: always
depends_on: [ db ]
db:
image: postgres:16
docker compose up -d --scale worker=3
Рост транзакций до десятков тысяч в сутки — это увеличение числа воркеров одной командой, а не переписывание логики. Плюс отдельный сервис тривиально подключается к Prometheus/Grafana: метрики очереди, алерты при росте dead-letter — то, что внутри 1С организуется с большим трудом.
Реальные цифры из продакшена
Для иллюстрации — метрики одного из наших клиентов (производство металлоконструкций, ~180 сотрудников, 1С:ERP 2.5 + ELMA365). Интеграцию мигрировали с регламентного задания на Symfony-сервис без изменения бизнес-логики:
| Показатель | Было (рег. задание 1С) | Стало (Symfony) |
|---|---|---|
| Транзакций в сутки | ~4 000 | ~4 000 |
| Средняя задержка 1С→ELMA | 7 минут | 3 секунды |
| Потерянных сообщений за месяц | ~40 | 0 |
| «Зависших» процессов | 12 в месяц | 0 |
| Жалоб пользователей на «тормоза» | ~15 в месяц | 0 |
| Восстановление после падения API ELMA | Ручной перезапуск | Автоматически, за 2–4 минуты |
Самая дорогая строка здесь — последняя. При регламентном задании каждый даунтайм ELMA365 превращался в ручную работу: кто-то должен заметить сбой, перезапустить задание, вручную проверить, что дошло. Теперь очередь просто переживает даунтайм и разбирает backlog за минуты без участия человека.
Прямое сравнение
| Критерий | Регламентное задание 1С | Symfony-коннектор |
|---|---|---|
| Задержка передачи данных | Минуты (интервал задания) | Секунды (событийная модель) |
| Retry при сбое API | Только если написано вручную | Из коробки (Messenger) |
| Dead-letter очередь | Нет (нужно писать регистр) | Встроено |
| Нужна отдельная инфраструктура | Нет | Да (сервис + очередь) |
| Нагрузка на базу 1С | Есть, растёт с объёмом | Нет (вынесена наружу) |
| Кто дорабатывает | 1С-программист | PHP/Symfony-разработчик |
| Практический потолок по транзакциям | До ~500–1000/час | Практически не ограничен |
Честные контраргументы
«Retry можно написать и в 1С». Можно. Но тогда вы пишете свой Messenger на встроенном языке: без нормальных тестов, без dead-letter, без метрик. Это изобретение велосипеда с квадратными колёсами, который ещё и поддерживать вашему же коллеге.
«Почему Symfony, а не .NET или Node?». Не принципиально. Суть — паттерны: очередь, воркеры, идемпотентность, раздельное деплой. Symfony — наш стек, но те же принципы перекладываются на любой язык.
«Это лишняя инфраструктура». Да, и это честная цена гибкости: нужен сервер или контейнер под сервис и очередь. Минимально — 2 vCPU / 4 GB под сервис и столько же под брокер; на практике для нагрузки до 10 000 транзакций в сутки хватает одной VM 4 vCPU / 8 GB с Docker Compose. Для небольшого офиса с полусотней процессов в день это избыточно. Для растущего предприятия — дешевле, чем простой согласований.
«А как же обновления 1С?». В этом и плюс: коннектор живёт вне конфигурации, общается с 1С через OData или HTTP-сервисы, и обновление типовой конфигурации его не затрагивает.
Чек-лист: когда регламентного задания достаточно
Задайте себе три вопроса:
Сколько у вас транзакций в час и насколько критична задержка? До пары сотен операций и допустимая задержка в 5–10 минут — регламентного задания достаточно. Тысячи операций с требованием near real-time — нужна событийная модель.
Сколько стоит вам один «зависший» бизнес-процесс? Если остановки согласований или потери контекста уже влияют на операционную деятельность — это сигнал, что архитектурный потолок текущего решения достигнут.
Кто будет поддерживать интеграцию дальше? Если в штате есть только 1С-разработчик — миграция на отдельный сервис означает либо обучение, либо привлечение подрядчика на сопровождение.
И отдельно — сценарии, где городить отдельный сервис действительно нет смысла:
- Фоновая синхронизация справочников раз в час;
- Ночная выгрузка отчётности;
- До пары сотен операций в час с допустимой задержкой в 5–10 минут.
Вопросы к сообществу
- Как вы реализуете retry в своих регламентных заданиях? Есть ли у вас самописные механизмы dead-letter, или ограничиваетесь «Попытка...Исключение»?
- Используете ли вы SQL-реплику 1С для интеграций? Какие подводные камни встречали с прямым чтением таблиц AccumRg* и InfoRg*?
- Какой брокер сообщений предпочитаете в 1С-интеграциях — RabbitMQ, Kafka или что-то другое? Почему?
- Сталкивались ли с ситуацией, когда регламентное задание «падало» из-за таймаута внешнего API и блокировало пользовательские сеансы? Как решали?
Итог
Регламентное задание прекрасно знает, как устроен ваш учёт. Но оно никогда не проектировалось как сетевой сервис, который должен переживать таймауты API, ретраи и очереди сообщений, — а именно это нужно для стабильной работы BPM-процессов при высокой нагрузке.
Если счёт идёт на тысячи транзакций в час, а остановки согласований уже стоят денег — разница в архитектуре перестаёт быть теоретической. Поэтому интеграции 1С ↔ ELMA365 мы теперь делаем не как обработку внутри 1С, а как отдельный сервис — с расчётом, что он будет расти вместе с бизнесом, а не упрётся в потолок через год.
Об авторе: Андрей Болонин, ModernERP. Проектируем интеграционные шины 1С ↔ BPM (ELMA365) и внешние API для среднего бизнеса. Буду рад ответить в комментариях на вопросы по архитектуре под ваши нагрузки — на Infostart комментарии часто полезнее самой статьи.
Вступайте в нашу телеграмм-группу Инфостарт