Как мы строим интеграции: архитектура, стек, подход
Кейс

Как мы строим интеграции: архитектура, стек, подход

Когда бизнес просит «связать Ozon с МойСклад» — это звучит просто. На деле за каждой интеграцией стоит архитектура, которая определяет, будет ли система работать надёжно через год или сломается при первой нагрузке.

В этой статье разберём, как мы проектируем интеграции — от выбора стека до мониторинга в продакшене. Не абстрактные best practices, а конкретные решения, которые мы применяем в каждом проекте — и почему именно такие.

Почему «скрипт на cron» — это не интеграция

Самый частый запрос, с которого начинается разговор: «У нас есть скрипт, который раз в час забирает данные из API и кладёт в таблицу. Он иногда падает. Можете починить?»

Мы открываем код и видим одно и то же. Функция, которая делает fetch к API, парсит ответ, записывает в базу. Запускается по cron. Без обработки ошибок, без retry, без логов. Если API вернул 500 — скрипт упал, данные за этот час потеряны. Если вернул 429 (rate limit) — то же самое. Если ответил медленно и наступил таймаут — скрипт повис до следующего запуска, а следующий запуск стартовал параллельно и создал дубликаты.

Вот конкретные проблемы, которые мы встречаем в таких «интеграциях» раз за разом:

Потеря данных при сбоях. API на той стороне вернул ошибку — заказ, обновление цены, документ не обработался. Следующий запуск берёт только новые данные. Пропущенные — потеряны навсегда. Никто не узнает, пока клиент не позвонит и не спросит «где мой заказ?».

Дубликаты при повторах. Запрос ушёл, ответ не пришёл — таймаут. Заказ создался или нет? Скрипт не знает. При повторном запуске создаёт ещё раз. У клиента два одинаковых заказа в МойСклад.

Отсутствие retry. Большинство ошибок внешних API — временные. Сервер перегружен, сеть моргнула, rate limit. Через 10 секунд тот же запрос пройдёт. Но скрипт на cron не умеет ждать и повторять — он просто падает и ждёт следующего запуска через час.

Нет мониторинга. Скрипт упал в три часа ночи. Менеджер узнал утром, когда заказы перестали появляться в системе. Или не узнал вообще, потому что заказов в тот день было немного и расхождение никто не заметил.

Это не интеграция. Это автоматизация хорошей погоды: работает, пока всё идеально. А в работе с внешними API «идеально» не бывает никогда.

Наша архитектура: три слоя

Каждую интеграцию мы проектируем как конвейер из трёх слоёв. Источник, обработка, доставка. Между ними — очереди. Это не изобретение, это стандартная ETL-архитектура, адаптированная под реалии бизнес-интеграций: нестабильные API, rate limits, непредсказуемые объёмы данных.

1. Слой получения данных (Source)

Первый вопрос при проектировании: как забирать данные из источника?

API polling — самый частый вариант. Сервис каждые N секунд опрашивает API на предмет новых событий. Flowwow не отправляет вебхуки — мы polling’им их API каждые 30 секунд. Ozon Seller API тоже лучше работает в pull-модели: вебхуки ненадёжны, документация неполная. Polling предсказуем и не зависит от инфраструктуры на стороне API.

Webhooks — когда API умеет отправлять уведомления. МойСклад, Telegram Bot API, Stripe. Быстрее polling’а — данные приходят в момент события. Но требуют публичный endpoint, верификацию подписи, обработку дубликатов (webhook может прийти дважды). Мы используем вебхуки, когда API это поддерживает и документация внятная.

Файловый обмен — для систем, у которых нет API. Выгрузка в CSV, XML, Excel. Файл появляется на FTP или в Google Drive — сервис подхватывает, парсит, обрабатывает.

Для каждого источника мы решаем три вопроса:

  • Rate limits. Ozon API даёт 60 запросов в минуту. Google Sheets API — 300 в минуту на проект. Мы закладываем межзапросные паузы и никогда не работаем на пределе лимита. Запас — минимум 30%.
  • Пагинация. API отдаёт данные страницами: 100 товаров за запрос. Если товаров 4 000 — это 40 последовательных запросов. Скрипт обязан обрабатывать пагинацию корректно, включая случай, когда данные изменились между страницами.
  • Аутентификация. OAuth, API-ключи, токены с истекающим сроком. Refresh-логика автоматическая: если токен протух — обновляем и повторяем запрос, а не падаем с ошибкой 401.

2. Слой обработки (Processing)

Сердце интеграции. Здесь данные из формата источника превращаются в формат получателя, проходят валидацию и бизнес-логику.

Очередь задач — обязательна. Каждая единица работы (заказ, обновление цены, документ) попадает в очередь. Не обрабатывается сразу, а ставится в очередь и обрабатывается worker’ом. Это даёт три вещи: последовательность (не бывает race condition), персистентность (при перезапуске задачи не теряются), наблюдаемость (видно, сколько задач ждёт, сколько обработано, сколько упало).

На Node.js мы используем BullMQ + Redis. Типичная конфигурация:

const queue = new Queue('orders', {
  connection: { host: 'localhost', port: 6379 },
  defaultJobOptions: {
    attempts: 5,
    backoff: { type: 'exponential', delay: 3000 },
    removeOnComplete: { age: 7 * 24 * 3600 },
    removeOnFail: false, // упавшие задачи остаются для разбора
  },
});

removeOnFail: false — принципиальный момент. Упавшие задачи не удаляются. Они остаются в dead letter queue, пока кто-то не разберётся, что произошло. Автоматизация, которая молча теряет данные, хуже ручной работы.

Retry с exponential backoff. Первая попытка — через 3 секунды. Вторая — через 9. Третья — через 27. Пятая — через 4 минуты. Если API перегружен, забрасывать его запросами каждые 3 секунды — значит усугублять проблему. Нарастающая пауза даёт сервису время восстановиться.

Бизнес-логика. Трансформация данных между форматами двух систем. Маппинг полей, конвертация валют, расчёт цен с учётом комиссий, валидация обязательных полей. Если данные невалидны — задача не повторяется (бесполезно), а сразу уходит в dead letter queue с описанием проблемы.

3. Слой доставки (Destination)

Запись обработанных данных в целевую систему. Звучит просто — но здесь скрывается самый коварный класс ошибок.

Идемпотентность — обязательна. Если retry автоматический, каждая операция записи обязана быть идемпотентной. Это значит: повторное выполнение с теми же данными даёт тот же результат без побочных эффектов. Пример: сервис создаёт заказ в МойСклад, запрос ушёл, ответ не пришёл — таймаут. Заказ создался или нет? Неизвестно. Retry создаёт заказ ещё раз. Без идемпотентности — у клиента два одинаковых заказа.

Решение: каждый объект создаётся с уникальным внешним ID — например, ID заказа из Flowwow. Перед записью сервис проверяет: существует ли объект с таким внешним ID? Если да — пропускаем или обновляем. Если нет — создаём. Повторный вызов безопасен.

Обработка конфликтов. Целевая система может отклонить запись: товар удалён, контрагент заблокирован, поле стало обязательным после обновления API. Каждый случай обрабатывается: retryable-ошибки повторяются, non-retryable — уходят в dead letter queue с понятным описанием для человека.

Стек

КомпонентТехнологияПочему
RuntimeNode.js + TypeScriptТипизация ловит ошибки до продакшена. Экосистема пакетов. Async/await идеален для I/O-bound интеграций
ОчередиBullMQ (Redis)Retry, приоритеты, rate limiting, dead letter queue из коробки. Redis — персистентность
ХранениеPostgreSQLНадёжность, JSONB для произвольных структур, транзакции
ДеплойDocker + VPS или Cloud RunГибкость: для малого бизнеса — VPS за 1 000 рублей, для enterprise — автоскейлинг в облаке
МониторингTelegram-алерты + структурированные логиКлиент узнаёт о проблеме за минуты, а не за дни

Почему TypeScript, а не Python, Go или Java? Для интеграций критичен I/O: запросы к API, ожидание ответов, параллельная обработка нескольких источников. Node.js с async/await делает это естественно. TypeScript добавляет типизацию — и когда API на той стороне меняет формат ответа, компилятор ловит проблему до деплоя, а не в три часа ночи в продакшене.

Почему не Kafka вместо BullMQ? Kafka — это про миллионы сообщений в секунду, кластеры брокеров, consumer groups. Наши интеграции обрабатывают десятки-сотни событий в минуту. BullMQ + Redis — ровно тот масштаб: надёжно, просто, без overhead’а на администрирование кластера. Если задаче нужна Kafka — значит, она переросла нашу специализацию, и мы честно об этом скажем.

Пример: FlowWow → МойСклад

Чтобы архитектура не была абстракцией — вот как она работает в конкретном проекте. Флористический магазин, заказы с Flowwow, складской учёт в МойСклад. Подробный разбор — в отдельном кейсе, здесь покажем поток данных через три слоя.

Source layer polling’ит Flowwow каждые 30 секунд. Получает список новых заказов. Каждый заказ кладёт в очередь BullMQ. Задача source — только получить данные и поставить в очередь. Никакой бизнес-логики.

Processing layer берёт заказ из очереди. Проверяет остатки в МойСклад по каждой позиции. Если всё в наличии — принимает заказ на Flowwow и создаёт заказ покупателя в МойСклад с привязкой по внешнему ID (идемпотентность). Если товара нет — мгновенно отклоняет, чтобы покупатель не ждал.

Destination layer записывает результат в МойСклад. Если при записи что-то пошло не так — задача возвращается в очередь с exponential backoff. Пять попыток. Если все пять провалились — dead letter queue и алерт в Telegram.

В пиковый день (8 марта) система обработала 330 заказов без единого ручного вмешательства.

Что происходит при сбое

Интеграция работает 24/7, а внешние API — нет. Вот реальные сценарии, которые случаются регулярно, и как система с ними справляется.

Сценарий 1: API недоступен. МойСклад API возвращает 503. Первая попытка — через 3 секунды. Вторая — через 9. К четвёртой попытке (через 81 секунду) API обычно уже вернулся. Заказы, поступившие во время сбоя, копятся в очереди — ни один не теряется. Когда API вернулся, очередь разгребается автоматически.

Сценарий 2: Rate limit. Ozon API вернул 429 — превысили лимит запросов. Система не паникует: задача возвращается в очередь с увеличенной задержкой. BullMQ поддерживает rate limiting из коробки — можно задать «не более 50 запросов в минуту», и библиотека сама выдерживает паузы.

Сценарий 3: Таймаут без ответа. Запрос на создание заказа в МойСклад ушёл, ответ не пришёл за 30 секунд. Создался заказ или нет — неизвестно. Система ставит задачу на retry. При повторной попытке проверяет: есть ли заказ с таким внешним ID? Если есть — задача считается выполненной. Если нет — создаёт заново. Идемпотентность исключает дубликаты.

Сценарий 4: Непредвиденная ошибка. API изменил формат ответа, TypeScript-десериализация сломалась. Задача падает, все пять retry проваливаются с одной и той же ошибкой. Задача уходит в dead letter queue. Telegram-алерт приходит разработчику с полным контекстом: какой заказ, какой endpoint, какая ошибка, сколько попыток было. Разработчик обновляет маппинг, перезапускает задачу из DLQ — заказ обрабатывается.

Главное: ни один сценарий не приводит к потере данных. Задержка — да. Потеря — нет.

Мониторинг: не dashboard, а Telegram

Когда мы только начинали, пробовали стандартный путь: Grafana, дашборды, метрики. Красиво. И абсолютно бесполезно для малого и среднего бизнеса.

Почему: дашборд работает, когда кто-то на него смотрит. В компании с 10-50 сотрудниками нет дежурного инженера, который сидит перед монитором с графиками. Менеджер, который отвечает за интеграцию, сидит в Telegram — переписывается с поставщиками, с клиентами, с командой. Если уведомление приходит туда же — он увидит его через минуту. Если на дашборд — через день. Или никогда.

Наш мониторинг устроен так:

Telegram-алерты при сбоях. Задача попала в dead letter queue, circuit breaker разомкнулся, сервис перезапустился — алерт приходит в Telegram-чат. Формат: что случилось, когда, с какими данными, сколько попыток было. Не «ошибка в системе», а конкретика, по которой можно действовать.

Ежедневная сводка. Раз в день — отчёт: сколько задач обработано, сколько ошибок, среднее время обработки. Позволяет поймать деградацию до того, как она стала проблемой. Если вчера обрабатывали 200 заказов за 2 секунды, а сегодня те же 200 за 15 секунд — что-то изменилось, нужно разобраться.

Watchdog. Мониторинг сам может сломаться. Отдельный процесс проверяет, что основная система жива. Если основной сервис не отчитался за 15 минут — watchdog шлёт алерт. Это не перестраховка — мы убедились в необходимости watchdog’а на практике, когда GAS-триггер молча не срабатывал два дня подряд.

Для enterprise-клиентов добавляем Grafana и structured-логи в ELK. Но для 90% наших проектов Telegram-бот + watchdog покрывает все потребности мониторинга — и клиент уже там.

Итоги

Четыре принципа, которые мы закладываем в каждую интеграцию:

  1. Очередь задач — обязательна. Без очереди нет retry, нет персистентности, нет наблюдаемости. Даже если «тут всего десять заказов в день» — очередь нужна. Потому что рано или поздно API упадёт, и без очереди эти десять заказов потеряются.

  2. Retry — это система, а не try/catch с повтором. Exponential backoff, circuit breaker, dead letter queue, разделение retryable и non-retryable ошибок. Каждая ошибка обрабатывается по-разному, каждая — видима.

  3. Идемпотентность — не опция, а требование. Если операция может быть повторена автоматически, она обязана быть безопасной при повторении. Внешний ID, проверка перед записью, upsert вместо insert.

  4. Мониторинг, который реально работает. Telegram-алерты доходят до человека за минуту. Дашборд, на который никто не смотрит, — бесполезен. Watchdog следит за самим мониторингом.

Эти принципы не зависят от стека. Они одинаково работают в BullMQ на Node.js и в самописной очереди на Google Apps Script. Инструменты разные — архитектурные решения одинаковые.

Читайте также


Если вы выбираете подрядчика для интеграции и хотите понять, как именно будет устроена система — запишитесь на диагностику. Разберём вашу задачу, покажем архитектуру на примере и оценим сроки — до начала работ.

С вами была команда GoogleSheets.ru, мы строим интеграции, которые не теряют данные.

Не хотите разбираться сами?

Если читали статью и поняли, что руками уже не справляетесь — напишите. Оценим задачу бесплатно и предложим решение.

КМ
Константин
Менеджер проектов · ответит в течение часа

Как автоматизация окупается → Чеклист: 7 процессов для автоматизации →

140+ реализованных проектов
Google Products Expert в команде

Хотите такой же результат?

Расскажите о задаче — предложим решение и покажем релевантные кейсы.

Написать в Telegram

Подписывайтесь — делимся скриптами, кейсами и лайфхаками