Перейти к основному контенту

Компонентная разработка vs «Durex-код»

📘 Памятка инженера Metabot

1️⃣ В чём разница подходов

Скриптовый подход Компонентный подход
Код вставляется прямо в сценарий Сценарий использует готовый компонент
Telegram-логика торчит в webhook-скрипте Telegram спрятан внутри плагина
Сценарист не может этим пользоваться Сценарист работает декларативно
Нет чёткого контракта Есть входы, выходы, параметры
Разовое решение Переиспользуемый модуль
Зависит от конкретного проекта Доставляется в любой проект

2️⃣ Что такое «Durex-код»

Durex-код — это:

  • код, написанный «под задачу»
  • код, который нельзя переиспользовать
  • код, который живёт только в одном скрипте
  • код, который нарушает контракты
  • код, который смешивает логику канала, бизнес-логику и UX

Это не плохо. Иногда это допустимо.

Но это не архитектурный стиль Metabot.


3️⃣ Что такое компонентный стиль Metabot

Компонент — это:

  • атомарная команда
  • с чёткими входами
  • с чёткими выходами
  • с понятным поведением
  • с изолированной зоной ответственности
  • со спрятанной инженерной сложностью

Пример:

const VoiceInput = require('Common.Voice.VoiceInput')

return VoiceInput.expect({
  code: "orion_profiling_q1_voice",

  lead,

  successScript: "orion_profiling_q2",
  cancelScript: "orion_profiling_cancelled",

  targetAttr: "orion_profiling_q1_text",
  sourceAttr: "orion_profiling_q1_voice_url",

  extraAttrs: {
    active_agent: "orion",
    voice_context: "orion_profiling_q1",
    input_mode: "profiling"
  },

  processorScript: "System_VoiceInput_Processor",

  stt: {
    provider: "openai",
    options: { model: "whisper-1", language: "ru" },
    asyncResponse: true,
    tokenKey: "OPENAI_API_KEY"
  }
})

Сценарист видит:

  • targetAttr
  • sourceAttr
  • successScript
  • cancelScript
  • stt-параметры

Он не видит:

  • Telegram API
  • file_id
  • webhook структуру
  • async механику
  • OpenAI SDK

Это декларативный стиль.


4️⃣ Что такое декларативный подход

Декларативный подход — это:

Мы описываем ЧТО должно произойти, а не КАК это происходит.

Сценарист пишет:

Ожидай голос.
Перейди в success.
Сохрани результат.

А не:

Проверь payload.message.voice
Достань file_id
Сходи в Telegram API
Отправь в STT
Подожди callback
Распарси JSON

5️⃣ Принципы компонентной разработки Metabot

1. Чёткий контракт

Каждый компонент обязан иметь:

  • входные параметры
  • выходные данные
  • предсказуемое поведение
  • задокументированные переходы

2. Нет неявной передачи данных

❌ Плохо:

memory.foo = 123

Потом в другом месте:

memory.foo

Это разрыв контракта.

✔ Правильно:

  • вход через параметры
  • управляемый выход, например, через targetAttr
  • всё прозрачно

3. Одна зона ответственности

Пример:

VoiceInput:

  • отвечает за голосовой ввод
  • не отвечает за каналы
  • не отвечает за Telegram

ArtifactResolver:

  • отвечает за извлечение артефактов из всех каналов
  • не отвечает за STT
  • не отвечает за сценарий

4. Никаких «всё в один файл»

Если логика растёт — создаём слой.

Пример:

  • Был Telegram-код внутри VoiceInput
  • Появился Max
  • Вместо if/else-ада — создаём Channel.ArtifactResolver

Это инженерный подход.


5. Повторное использование — обязательное требование

Если модуль нельзя переиспользовать — это не компонент.

Компонент должен быть:

  • переносимым
  • подключаемым
  • поставляемым как плагин

6️⃣ Почему это важно

  1. Сценаристам проще
  2. Интеграторам проще
  3. Код чище
  4. Проекты масштабируются
  5. Плагины можно доставлять в разные компании
  6. Мы получаем архитектурную степень свободы

И самое интересное:

⏱ По времени это занимает примерно столько же.

Разница — в мышлении.


6.1 🔓 Декларативный стиль = расширяемая палитра Metabot

Декларативный подход — это не только «красиво».

Это позволяет нам:

  • расширять палитру компонентов
  • развивать low-code платформу
  • строить визуальные конструкторы
  • делать архитектуру масштабируемой

Сейчас в Metabot есть ~21 атомарная команда.

Примеры:

  • SendMessage
  • SendImage
  • SendFile
  • Input
  • VoiceInput
  • TransferToOperator
  • RunScript
  • и т.д.

Каждый компонент:

  • имеет входы
  • имеет выходы
  • имеет чёткие параметры
  • ведёт себя предсказуемо

Это позволяет:

1️⃣ Легко расширять платформу Добавили новый компонент → он сразу становится частью палитры.

2️⃣ Делать визуальные конструкторы Если у компонента есть вход/выход — можно рисовать схемы.

3️⃣ Строить workflow-системы Компоненты становятся узлами графа.

4️⃣ Делать экспортируемые решения Компонент можно доставить в другой проект.


6.2 🧱 Атомарность = возможность визуализации

Если компонент описан как:

  • чёрный ящик
  • с чёткими входами
  • с чёткими выходами

его можно:

  • визуализировать
  • соединять стрелками
  • использовать в low-code
  • использовать в no-code
  • переносить между проектами

Если код:

  • размазан по скриптам
  • читает memory
  • меняет глобальные объекты
  • зависит от webhook структуры

его нельзя:

  • визуализировать
  • стандартизировать
  • масштабировать

7️⃣ Архитектор vs Скриптовик

Скриптовик думает:

Мне нужно сделать, чтобы работало.

Архитектор думает:

Как сделать, чтобы это работало в 10 проектах.


8️⃣ Как использовать это как AI-чеклист

Можно кидать в AI вместе с кодом и спрашивать перед коммитом:

  • Есть ли здесь чёткий контракт?
  • Понятен ли этот код сценаристу?
  • Есть ли неявная передача данных?
  • Нарушена ли зона ответственности?
  • Можно ли переиспользовать этот код?
  • Можно ли этот код вынести в компонент?
  • Можно ли визуализировать это поведение как узел workflow?
  • Является ли это Durex-кодом или системным модулем?

Если на 3+ вопроса ответ «нет» — ты пишешь Durex.


9️⃣ Когда допустим Durex-код

Иногда допустимо:

  • разовая миграция
  • временный костыль
  • hotfix

Но:

  • он не должен становиться системой
  • он не должен множиться
  • он не должен ломать архитектуру

1️⃣0️⃣ Когда допустимо писать код в скрипте

Код в скрипте допускается.

Но только если соблюдены условия.


✔ Допустимые случаи

1️⃣ Абсолютно разовая логика

  • временная миграция
  • одноразовый расчёт
  • локальный pipeline
  • подсчёт баллов
  • простая условная логика

Пример:

if (lead.getAttr("score") > 10) {
  return bot.run({ script_code: "vip_branch" })
}

Это допустимо.


2️⃣ Логика понятна сценаристу

Если сценарист:

  • может прочитать
  • может понять
  • может отредактировать

— код допустим.

Если сценарист не может понять код — код не должен находиться в сценарии.

Исключение — системные вещи без которых абсолютно нельзя обойтись, например, вызов плагинов.


3️⃣ Логика не нарушает архитектуру

Допустимый код:

  • не читает неявные memory
  • не зависит от глобальных переменных
  • не создаёт скрытых зависимостей
  • не нарушает контракт компонентов

❌ Недопустимый Durex-код

Код нельзя писать в сценарии, если:

  • он сложный
  • он интеграционный
  • он работает с API
  • он обрабатывает webhook
  • он содержит асинхронную логику
  • он требует инженерного уровня знаний

Такой код должен быть:

  • вынесен в плагин
  • спрятан под компонент
  • задокументирован
  • переиспользуем

1️⃣1️⃣ Финальная мысль

Metabot — это не набор скриптов.

Это:

  • палитра компонентов
  • декларативный конструктор
  • платформа

Если ты пишешь код, который нельзя вынести в палитру — ты... выпускаешь Durex.

Если ты пишешь код, который расширяет палитру — ты работаешь как инженер платформы.

P.S. Почему «Durex»?

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

А разовый скрипт — это расход периода: проект сделали, прибыль получили — но актива не появилось. Работу сделали один раз, в следующем проекте — делаем то же самое заново. Все при деле, задача выполняется, но капитал не создаётся.