Введение

История о том, как очередная «быстрая костыль-интеграция» на коленке неожиданно превратилась в почти полноценную Order Management System (OMS) с элементами event-driven архитектуры. Всё это — без предварительного проектирования и без единой строчки кода на Java/Scala/Python (хотя тут немного лукавства, так как пару скриптов на Groovy все-таки имеется), на чистом Apache NiFi и SQLite.

Девизом этого проекта мог бы стать слоган: «Мы не ищем лёгких путей, мы ищем работающие решения». Я инженер в одной ритейл компании, который любит решать задачи, и сегодня расскажу, как закрыл боль бизнеса малой кровью, используя не совсем типичный для веб-сервисов инструмент.

Пример работающего бота
Пример работающего бота

Предыстория: боль бизнеса длиною в несколько лет

Задача звучала на первый взгляд просто: предоставить клиентам возможность получать в качестве подарка услуги в обмен на бонусы программы лояльности. Но за этой простотой скрывались годы неудачных попыток. Решения получались либо дорогими, либо сложными и дорогими. Вечная история.

На очередном совещании, выслушав стенания коллег, я поймал себя на мысли: «А почему бы не попробовать?». У меня уже был положительный опыт быстрого прототипирования интеграций на Apache NiFi, и я решил рискнуть.

Идея-спринтер была проста: телеграм-бот, в который продавец вводит номер карты лояльности. Бот дергает API системы лояльности и списывает заранее прописанное в потоке количество бонусов. Всё. Быстро и сердито.

- Коллеги, а как вы отнесетесь к тому, чтобы продавцы просто вводили номер карты в телеграм-бот, а он сам всё списывал?» - выдал я

Воцарилась пауза. Первым нарушил молчание руководитель отдела продаж: - Телеграм-бот? Мы же не умеем с ботами работать, у нас даже чатов таких нет. -
Коллеги выглядели удивленными, ничего себе, какие от них скрывали возможности...

Я быстро переключил их с удивления на обсуждение конкретики:
- Коллеги, давайте технические детали оставим мне, а сейчас перейдем к описанию бизнес-процесса. Самое сложное здесь - даже не списание бонусов, а организация процесса записи на услугу. Как вы сейчас работаете с расписанием? -

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

- Вот видите! - обрадовался я. - Автоматизацию расписания мы точно оставим на потом. Сейчас говорим только о списании бонусов. Бот будет просто фиксировать факт оплаты.

Казалось, всех устроило такое упрощение. Но тут активизировался руководитель по CRM: - Минуточку. А как же антифрод? У нас же есть требование - подтверждение списания SMS-кодом. Мы не можем просто так позволить списывать бонусы без подтверждения от клиента.

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

Коллеги переглянулись. Руководитель продаж с одобрением. Руководитель CRM обрадовался: - Ну, если технически возможно... Давайте пробовать.

Так изначальная идея обрастала требованиями: теперь нужно было и телефон клиента по карте найти, и два метода API вызвать, а не один. Задача усложнилась, но все в разумных пределах.

День первый: вдохновение и рождение OMS

На следующий день я сел с целью набросать поток за 30 минут и… завис на полдня. Не из-за проблем, а из-за нахлынувшего вдохновения и идей.

Я осознал: если услугу покупают (пусть и за бонусы), значит, это товар. А если товар — почему бы не добавить его в заказ? А если заказ — почему бы не добавить в него статусы? Его можно оплатить, отменить, сделать возврат. Так родилась идея не просто «списывалки», а целой Order Management System.

При этом все эти мысли казались вполне выполнимыми. Технически это вылилось в:

Таблицы в SQLite

  • orders

  • payments

  • payment_confirmations

  • status_history.

Выбор SQLite был очевиден: для пилота с низкой нагрузкой — идеально, а при необходимости миграция на PostgreSQL или MySQL — дело техники.

Набор HTTP-эндпоинтов, обрабатываемых NiFi:

  • POST /order/create – создание заказа.

  • POST /order/update – обновление (в основном статуса).

  • GET /order/search – поиск заказа.

  • POST /status/add – добавление записи в историю статусов.

Так выглядит набор методов OMS в "сыром" виде.
Так выглядит набор методов OMS в "сыром" виде.
Скрытый текст

Да, понимаю, что сейчас это выглядит как монстр, но нужно раскидать по ProcessingGroup соответствующие потоки, станет намного понятнее и управляемее.

День второй: углубление в кроличью нору — платежи и асинхронность

С оплатой я решил не упрощать. Вместо простого вызова списания родилась целая мини-платёжная система.

  • POST /payment/create – создание платежа, привязанного к заказу.

  • POST /payment/status/add – добавление статуса платежа («создан», «ожидает подтверждения», «подтверждён», «отменён» и т.д.).

  • POST /payment/async/confirm/add – асинхронное подтверждение. Этот эндпоинт не ждёт верификации кода, а лишь создаёт задачу на проверку.

  • GET /payment/async/confirm/search – проверка результата асинхронной задачи.

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

Архитектура «бегающих процессов» вместо событий

Так как у меня не было готового брокера сообщений вроде Kafka, или RabbitMQ, я реализовал логику через фоновые процессы в NiFi, которые периодически опрашивали базу данных.

  1. Процесс «Создание заказа»: ищет заказы со статусом new, переводит их в order_created и пишет запись в историю.

  2. Процесс «Создание платежа»: видит платёж со статусом payment_created и создаёт запись в payment_confirmations.

  3. Процесс «Отправка SMS»: для записей confirmation_created генерирует код, отправляет SMS, сохраняет код и ставит статус confirmation_pending.

  4. Процесс «Верификация кода»: проверяет, совпал ли введённый код с отправленным. Если да — меняет статус на payment_verified.

  5. Процесс «Подтверждение платежа»: для верифицированных платежей обновляет статусы заказа и платежа.

Пример процесса проверки введенного кода из SMS
Пример процесса проверки введенного кода из SMS

Именно здесь я осознал мощь NiFi для построения устойчивых ETL-цепочек. Каждый такой процесс — это независимый ProcessingGroup в NiFi, который можно легко вынести на отдельный сервер, превратив в микросервис. Решение уже сейчас было слабосвязанным и готовым к декомпозиции.

Меня искушала идея заменить это на чисто событийную архитектуру с одной таблицей-бородом событий (events), которую бы слушал один главный процесс-оркестратор. Но я вовремя остановился, решив сначала получить работающий прототип, а потом уже рефакторить. Нельзя погрязнуть в over-engineering на стадии MVP.

Неожиданный бонус: данные для аналитики и календаря

Самое интересное началось после того, как основные процессы были отлажены. Я вдруг осознал, что построил не просто инструмент для списания бонусов, а полноценную систему учёта заказов (OMS). А это значит, что все данные уже структурированы и готовы к аналитике.

Что это дало бизнесу сразу же:

  • Отчетность:

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

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

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

Всё это стало возможно благодаря всего одному дополнительному процессу в NiFi, который периодически агрегировал данные из таблиц заказов и платежей и складывал их в отдельные витрины данных или просто формировал готовые отчеты в виде CSV-файлов. NiFi здесь снова оказался идеальным инструментом.

  • Фундамент для будущего календаря:

    • Вся информация о забронированном времени уже хранится в таблице заказов. Для реализации функции показа свободных слотов достаточно просто сделать выборку по дате и статусу заказа (paid или confirmed).

    • Таким образом, в будущем интерфейсе календаря не будет никаких лишних интеграций — он будет просто обращаться к методу GET /api/order/busy-slots?date=YYYY-MM-DD, который легко реализовать на том же NiFi, чтобы вернуть список занятого времени.

Это был ключевой момент окупаемости решения. Мы получили не просто функционал «списать бонусы», а центр данных по этому процессу, который сразу же начал приносить бизнесу дополнительную пользу.

День третий: телеграм-бот и необходимость API-шлюза

Для фронтенда я использовал телеграм-бота. У меня уже был наработанный опыт создания телеграм-ботов в NiFi, поэтому я быстро интегрировал его со своим loyalty.orders.api.

Но тут ждал сюрприз. Мои «сырые» API-методы требовали JSON с более чем 15 полями: номер карты, телефон, адрес магазина и т.д. Просить продавца вводить это всё в бота — утопия.

Так стихийно родился API-гейтвей/оркестратор. Я создал прокси-методы:

  • POST /proxy/order/create – принимает всего три параметра: код продавца, карту клиента и дату. Всю остальную информацию (магазин, телефоны) он подтягивает сам на основе кода продавца.

  • POST /proxy/payment/create – принимает только номер заказа.

  • POST /proxy/payment/confirm – принимает номер заказа и SMS-код.

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

Пример маршрутизации в ProxyAPI
Пример маршрутизации в ProxyAPI

Для MVP я реализовал 4 команды:

  • /new НОМЕР_КАРТЫ ДАТА ВРЕМЯ – создание заказа.

  • /pay НОМЕР_ЗАКАЗА – создание платежа.

  • /payconfirm НОМЕР_ЗАКАЗА КОД – подтверждение платежа.

  • /ordersearch НОМЕР_ЗАКАЗА – просмотр заказа.

Так можно ли делать веб-сервисы на NiFi?

Короткий ответ: да, но осторожно.

NiFi — не нативный инструмент для создания веб-сервисов. У него нет встроенной экосистемы фреймворков, как у Spring или Express.js. Однако его мощь заключается в другом:

  1. Устойчивость «из коробки»: NiFi гарантирует доставку данных, имеет механизмы повторных попыток и прекрасно работает с очередями.

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

  3. Модульность через Processing Groups: каждый процесс можно изолировать, протестировать и потом легко перенести в отдельный микросервис.

  4. Скриптование: процессоры вроде ExecuteScript (Groovy, Jython, JavaScript) позволяют быстро реализовать сложную логику там, где стандартных процессоров не хватает.

Это решение идеально подходит для:

  • Быстрого прототипирования и MVP.

  • Пилотирования гипотез без привлечения больших команд разработки.

  • Внутренних автоматизаций и интеграций, где важна надёжность, а не низкая latency.

Для высоконагруженных API, конечно, лучше подойдут классические фреймворки. Но для многих задач NiFi оказывается более чем достаточным и невероятно эффективным по времени внедрения.

Планы на будущее

Система работает, но это только начало. В планах:

  • Инлайн-кнопки в боте («Оплатить», «Отменить», «Вернуть») для улучшения UX.

  • Диалоговый сценарий создания заказа вместо ввода одной командой.

  • Интеграция с календарём для отображения только свободных слотов (благо, все данные для этого уже есть!).

  • Рефакторинг в сторону событийности. Здесь встаёт вопрос: остаться на SQL-таблицах или поднять Kafka? Первое - проще, второе - правильнее для масштабирования.

  • Сбор фидбека от первых пользователей и итеративное улучшение.

Выводы

Этот проект — отличный пример того, как практика рождает архитектуру. Мы не проводили предварительный анализ и не рисовали диаграммы UML. Архитектура выкристаллизовывалась сама в процессе кодинга, когда приходило понимание слабых мест и точек роста.

Главные инсайты:

  1. NiFi — мощный инструмент для прототипов и не только. На нём можно не только сделать MVP для проверки гипотезы, но и получить вполне себе готовое к внедрению решение благодаря встроенной отказоустойчивости.

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

  3. Слабосвязность и модульность заложены в NiFi изначально. Благодаря Processing Groups ваш прототип может плавно эволюционировать в микросервисную архитектуру.

И самое главное — задача бизнеса, которая годами не решалась, была закрыта за несколько дней малой кровью. А это лучшая награда для инженера.

А вы пробовали использовать NiFi в качестве бэкенда?

Комментарии (0)