Возьмём процесс, который встречается почти в любой небольшой компании. С сайта приходит заявка. Её нужно проверить: на месте ли имя, телефон или почта, не пустое ли сообщение. Затем приводим данные к единому виду, считаем приоритет и создаём карточку во внешней системе: в CRM, трекере или таблице. Три шага: вход, преобразование, внешнее действие.

Такой процесс одинаково аккуратно ложится и в граф n8n, и в короткий Python-модуль. В первый день разницы почти не видно: оба варианта работают, оба можно показать коллеге за пять минут. Поэтому сравнивать их по первому запуску бессмысленно. Интереснее другое: что произойдёт в течение следующего месяца, когда процесс начнёт меняться. Именно изменения, а не первоначальная сборка, определяют, сколько вы потом заплатите за выбор.

Один процесс в двух обличьях

В n8n это цепочка узлов. Первый узел принимает входящий запрос: Webhook запускает workflow по входящему событию, причём у него есть тестовый и рабочий адреса. Дальше идёт узел проверки условий, узел преобразования полей, узел обращения к внешней системе и, например, ветка отправки уведомления, если что-то пошло не так. Весь процесс виден на одном экране, и человек, ни разу не открывавший редактор кода, может проследить путь заявки пальцем.

В Python то же самое выглядит как небольшое веб-приложение или обработчик, за которым стоят несколько функций: одна проверяет вход, другая нормализует данные, третья считает приоритет, четвёртая вызывает внешний API. Порядок вызовов описан в одном месте, а правила живут в самих функциях. Увидеть процесс целиком можно, только прочитав код, зато правила записаны точно и без разночтений.

Оба варианта запускаются на одном VPS, и ресурсов им нужно немного: заявки приходят событиями, а не непрерывным потоком. Для старта обычно хватает младших конфигураций; в линейке MAATRIX это, например, Ba с 1 vCPU, 2 ГБ RAM и 40 ГБ NVMe или Uab с 2 vCPU и 4 ГБ. Это не обещание ёмкости, а ориентир для разговора: реальную нагрузку нужно мерить на своих данных. Важнее другое. Сервер с root-доступом позволяет держать на нём и n8n, и обычный сервис на Python рядом, не вводя новых зависимостей от чужих платформ. Это пригодится, когда дойдёт до гибрида.

Первый поворот: появилось обязательное поле

Представим, что через две недели менеджеры говорят: в заявке теперь обязателен ИНН компании. Без него карточку создавать нельзя, а клиенту нужно ответить, что поле не заполнено.

В n8n изменение выглядит так. Открываем workflow, добавляем условие в узел проверки, дописываем ветку для ответа об ошибке, при необходимости добавляем поле в узел создания карточки. Работа занимает минуты, и главное её достоинство в том, что вы видите, куда именно ложится новая проверка. Риск тоже понятен: правку делает тот, кто открыл редактор, и если у вас нет привычки хранить версии сценария отдельно, прошлое состояние придётся восстанавливать по памяти. Кроме того, изменение нужно не забыть перенести с тестового окружения на рабочее и убедиться, что опубликован именно нужный вариант: рабочий адрес вебхука, как сказано в документации узла, регистрируется при публикации.

В Python добавляем в функцию проверки несколько строк, пишем тест: заявка без ИНН отклоняется с понятной причиной, заявка с ИНН проходит. Правка попадает в систему контроля версий, у неё есть автор, дата и описание. Сверху ложится обычный порядок: изменить, прогнать тесты, выкатить. Это дольше на старте, но результат воспроизводим: коллега через год увидит, когда и почему поле стало обязательным.

Заметьте, что количество строк кода или узлов здесь вообще ничего не решает. Решает то, где живёт знание «ИНН обязателен». В графе оно распределено по условию и ветке на холсте. В коде оно собрано в одной функции и закреплено тестом. Если правил три, разницы почти нет. Если их тридцать и они зависят друг от друга, граф начинает разрастаться ветками, а функция остаётся функцией.

Второй поворот: поменялось правило расчёта

Теперь возьмём правило посложнее. Приоритет заявки раньше определялся просто: срочная или нет. Теперь он зависит от региона, от суммы, от того, обращался ли клиент раньше, и от времени суток, потому что ночные заявки уходят дежурному, а дневные в общую очередь. Условный пример: заявка из региона A на сумму выше порога от постоянного клиента получает высший приоритет, но если сумма не указана, считаем её по среднему значению для региона.

В n8n такое правило можно собрать из нескольких узлов условий и узла с небольшим фрагментом кода. Пока ветвлений мало, граф читается легко. Когда их становится много, холст превращается в схему метро, в которой пересадки приходится отслеживать глазами. Ещё хуже, что сравнить две версии правила по картинке трудно: чтобы понять, что изменилось, нужно сопоставить два холста узел за узлом.

В Python то же правило оформляется как чистая функция: на входе данные заявки и справочник регионов, на выходе приоритет. Её можно проверить десятками примеров без запуска всей системы: пограничные суммы, отсутствующее поле, непривычный регион, полночь. Каждый такой пример становится строкой в тесте, и при следующем изменении правила вы сразу узнаете, какие из прежних решений поменялись.

Вывод здесь не в том, что код лучше. Граф удобен, когда последовательность шагов важнее содержания шагов. Код удобен, когда содержание решения сложнее порядка действий. Приоритет с шестью условиями — это содержание, а не порядок.

Ночная ошибка: что вы увидите утром

Допустим, внешняя система в три часа ночи вернула ошибку, и несколько заявок не превратились в карточки. Что вы найдёте утром?

В n8n у каждого выполнения есть история: какой узел отработал, какие данные в него вошли, где процесс остановился. Это сильная сторона: человек без опыта разработки открывает неудачное выполнение и видит, что упало именно обращение к карточке, а входные данные были корректны. Для оповещений предусмотрен отдельный механизм: error workflow назначается в настройках и начинается с Error Trigger, а один такой сценарий можно использовать для нескольких рабочих процессов. Это значит, что уведомление дежурному в мессенджер вы настраиваете один раз, а не копируете в каждый сценарий.

В Python всё, что вы увидите утром, зависит от того, что вы заранее решили записывать. Если в журнал попадают идентификатор заявки, шаг и причина отказа, поиск займёт те же минуты. Если журнал состоит из одной строки «ошибка», придётся воспроизводить сбой. Код ничего не делает за вас бесплатно: ни истории, ни оповещений, ни повторной отправки. Зато и ограничений у вас нет: вы сами решаете, что считать ошибкой, какие поля скрыть от журнала, в какую систему мониторинга отправить событие.

Есть ещё одна тонкость, одинаковая для обоих вариантов, и про неё часто забывают: повтор. Если внешняя система ответила ошибкой, это не значит, что карточка не создана: запрос мог дойти, а ответ потеряться. Повторив обращение вслепую, вы получите дубль. Ни граф, ни код сами по себе от этого не защищают. Нужно решать, как вы узнаёте, что заявка уже обработана: по идентификатору заявки, по поиску существующей карточки или по признаку, который поддерживает внешняя система. Новый идентификатор попытки сам по себе безопасного повтора не даёт: он должен привязываться к самой заявке, а не к очередной попытке. Эту проверку в n8n придётся нарисовать отдельным узлом, в Python написать отдельной функцией, и в обоих случаях её полезно проверить заранее.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.

АРЕНДОВАТЬ СЕРВЕР

Тесты: кто проверяет, что вы ничего не сломали

Здесь различия самые заметные. Python даёт стандартные средства автоматических проверок: unittest поддерживает автоматизацию тестов, подготовку окружения, наборы проверок и отчёт о результате. Это значит, что любую правку можно прогнать через набор примеров до того, как она попадёт на сервер. Тест на отсутствие ИНН, тест на ночную заявку, тест на пустое сообщение: всё это короткие функции, и через полгода они будут описывать правила лучше любой документации.

Схематично тест для приоритета выглядит так:

import unittest
from rules import priority

class PriorityTests(unittest.TestCase):
    def test_known_client_large_sum(self):
        lead = {"region": "A", "sum": 500000, "repeat": True}
        self.assertEqual(priority(lead), "high")

    def test_missing_sum_uses_region_average(self):
        lead = {"region": "A", "repeat": False}
        self.assertEqual(priority(lead), "normal")

if __name__ == "__main__":
    unittest.main()

Это условный пример: названия функции и порогов выдуманы, но идея понятна. Правило проверяется без сети, без CRM и без запуска сервера.

В n8n роль тестов играет другое: история выполнений и ручной запуск на образцах данных. Вы отправляете на тестовый адрес вебхука пример заявки, смотрите, как она проходит по узлам, и убеждаетесь, что результат ожидаемый. Это быстро и наглядно, но у подхода есть граница: проверка делается глазами и повторяется только пока вы о ней помните. Набор из двадцати граничных случаев вам придётся либо хранить отдельно, например в виде файла с примерами, либо перепроверять вручную после каждой правки. История выполнений отвечает на вопрос «что произошло», а тест отвечает на вопрос «что должно происходить». Это разные задачи, и одно другое не заменяет.

Поэтому честная формулировка такая: для сценария с несколькими простыми шагами ручной проверки по образцам достаточно. Для правила с большим числом исходов нужен набор повторяемых примеров, и проще всего его получить в коде.

Передача работы коллеге

Третья сцена: вы уходите в отпуск, и процесс нужно сопровождать другому человеку.

Если этот человек далёк от программирования, граф n8n — понятный язык: он видит узлы, читает названия, открывает историю. Задача «заявки перестали приходить» разбирается по цепочке: пришёл ли вебхук, прошла ли проверка, создалась ли карточка. Но у графа есть слабое место: смысл узлов в голове автора. Узел с названием «Код 2» и пятнадцатью строками внутри быстро станет загадкой. Помогают человеческие названия узлов и короткие заметки к развилкам.

Если коллега программист, ему проще прочитать модуль, запустить тесты и посмотреть историю изменений. Но тот же модуль без документации и тестов будет для него таким же чёрным ящиком, как граф для нетехнического человека. Ясность дают не инструмент, а дисциплина: названия, описание правил, примеры.

Вопрос передачи работы по сути сводится к вопросу: кто будет менять процесс чаще всего. Если это операционный специалист, который не пишет код, граф даёт ему самостоятельность. Если это разработчик, он будет чувствовать себя спокойнее в репозитории.

Где заканчивается оркестрация и начинается предметная логика

Полезно разделить два вида работы, которые смешиваются в любом процессе.

  • Оркестрация: принять событие, вызвать сервис, дождаться ответа, отправить уведомление, записать результат, обработать ошибку. Это работа по склеиванию систем.
  • Предметная логика: как считать приоритет, какие поля обязательны, как нормализовать телефон, когда заявка считается дублем. Это правила бизнеса, которые меняются вместе с бизнесом.

Оркестрация естественно ложится на граф. Там много интеграций, повторяющихся действий и мест, где важно видеть последовательность и историю выполнений. Писать собственный код для вызова популярной системы и пересылки результата обычно означает заново делать то, что уже готово в виде узла.

Предметная логика естественно ложится на код. Она требует точных условий, повторяемых проверок и понятных различий между версиями. Когда правило начинает занимать полэкрана узлов, это признак того, что оно просится в функцию.

Граница, разумеется, не абсолютная. Небольшое правило в одно условие нет смысла выносить в отдельный модуль только из принципа. Но полезный вопрос на каждом шаге один и тот же: этот шаг про то, куда и когда передать данные, или про то, что именно с ними решить?

Гибрид с ясной границей

Самый практичный вариант для нашего процесса получается, если не выбирать одно из двух. Граф в n8n принимает заявку, вызывает небольшой Python-сервис для проверки и расчёта, получает ответ и дальше занимается тем, что умеет лучше всего: создаёт карточку, шлёт уведомление, обрабатывает сбой.

Граница проходит по понятной линии: Python-сервис получает заявку и возвращает решение, например «принять с приоритетом high» или «отклонить: нет ИНН». Он не знает, в какую CRM пойдёт карточка и кто получит уведомление. Граф, в свою очередь, не знает, как считается приоритет, он только реагирует на ответ. Благодаря этому каждая часть меняется независимо: правило расчёта правится в коде с тестами, а замена CRM или добавление второго канала уведомлений делается на холсте.

Чтобы граница не размылась, полезны несколько договорённостей:

  1. Формат обмена фиксируется явно: какие поля идут в сервис и какие возвращаются. Любое изменение формата описывается и проверяется тестом.
  2. Сервис не обращается к внешним системам сам. Как только правило начинает ходить в CRM, граница теряется, а ошибки перестают быть понятными.
  3. Ответ сервиса содержит причину решения, а не только вердикт. Тогда в истории выполнений n8n видно, почему заявка отклонена, и не нужно лезть в журнал сервиса.
  4. Для сбоев сервиса в графе предусмотрена ветка: если расчёт недоступен, заявка не теряется, а уходит в очередь ручной обработки, и об этом сообщает общий сценарий ошибок.

Всё это живёт на одном VPS: n8n и маленький сервис общаются по локальному адресу, наружу смотрит только вебхук. Для такого соседства важен root-доступ: вы сами решаете, как запускать сервис, где хранить его журналы и как обновлять. В каталоге MAATRIX есть n8n с установкой, но точное состояние наличия и операционной системы лучше посмотреть в форме заказа. А тем, кто хочет глубже разобраться с самим n8n на сервере и его применением в сценариях с ИИ, подойдёт отдельный материал: ИИ-автоматизации в n8n на VPS.

Как принять решение для своего процесса

Вместо подсчёта узлов и строк задайте себе несколько вопросов о будущих изменениях.

  • Кто будет менять правила: человек, который открывает редактор, или разработчик с репозиторием?
  • Сколько у правила исходов и насколько они зависят друг от друга? Три независимых условия — это граф. Таблица исключений — это код.
  • Нужно ли вам доказывать, что после правки старые случаи работают как раньше? Если да, без набора повторяемых проверок будет тяжело.
  • Много ли в процессе готовых интеграций, которые не хочется писать самому?
  • Насколько дорого стоит дубль или потерянная заявка и как вы это поймёте утром?

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

Выберите одну изменчивую часть

Вместо того чтобы решать, что выбрать для всей автоматизации, найдите в своём процессе одну часть, которая меняется чаще всего. Для нашей заявки это правило приоритета: его будут крутить каждый квартал, вслед за бизнесом. Именно его разумно вынести в код с тестами, и тогда любое изменение превращается в понятный и проверяемый шаг. Остальное, то есть приём, уведомления, карточку и разбор сбоев, пусть держит граф, потому что там важны наглядность и история выполнений.

А если изменчивой окажется интеграция, например вы часто переезжаете с одной CRM на другую, то место для неё на холсте. Правило определяется не вкусом, а тем, что именно вы будете править в следующем месяце. Найдите эту часть, поселите её там, где её удобнее менять и проверять, и остальное решится само.