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

Стандартизация успешных ответов и ошибок в Metabot

Общий подход к обработке результатов между модулями, API и бизнес-логикой

В Metabot разные части системы постоянно обмениваются результатами выполнения операций:

  • JS-плагины вызывают другие плагины;
  • low-code сценарии запускают кастомный код;
  • API-эндпоинты принимают запросы извне;
  • внутренние модули возвращают данные друг другу.

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

  • где-то true / false,
  • где-то throw error,
  • где-то { status: "ok" },
  • где-то просто строка.

Чтобы этого избежать, в Metabot используется единый стандарт ответов — плагин Common.Utils.Response.


Зачем вообще стандартизировать ответы

Стандартизация решает сразу несколько задач:

  • 📦 единый формат для успешных результатов и ошибок;
  • 🔗 предсказуемое взаимодействие между модулями;
  • 🧠 упрощение логики: не нужно каждый раз гадать, что вернёт функция;
  • 🌐 удобный проброс результатов во внешние API;
  • 🧪 возможность централизованно логировать и анализировать ошибки.

Ключевая идея простая:

Любая функция либо возвращает success-response, либо error-response. Исключения — редкость, а не норма.


Где используется Common.Utils.Response

Плагин применяется практически везде:

  • в JS-плагинах (Common.Helpers.*, Common.CJM.*, AI-модули);
  • во внутренних API-эндпоинтах;
  • в бизнес-логике;
  • в интеграциях с внешними системами;
  • в helper-функциях, которые могут вызываться цепочкой.

Если вы пишете свой модуль — используйте этот подход, либо создайте свой аналог по той же схеме.


Базовая идея формата ответа

Ответ всегда — это объект.

Успешный результат

{
  success: true,
  ...data
}

Ошибка

{
  success: false,
  error: true,
  message: "Описание ошибки",
  error_code: "OPTIONAL_CODE"
}

Это минимальный контракт, который:

  • легко проверять (if (!result.success));
  • удобно сериализовать;
  • безопасно передавать между слоями системы.

Подключение плагина Common.Utils.Response

Перед использованием стандартных функций для возврата успешных ответов и ошибок плагин необходимо подключить в вашем JS-модуле.

Пример подключения:

// Подключаем Response плагин для обработки ответов
const { getErrorResponse, getSuccessResponse } = require('Common.Utils.Response');

После подключения функции getSuccessResponse и getErrorResponse доступны для использования в:

  • helper-плагинах;
  • бизнес-логике;
  • API-эндпоинтах;
  • кастомных скриптах внутри Metabot.

📌 Плагин Common.Utils.Response является общим и доступен на всех серверах Metabot, поэтому дополнительной установки не требуется.


Основные функции плагина

Плагин Common.Utils.Response предоставляет набор helper-функций.

getSuccessResponse()

Используется при успешном выполнении операции.

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

return getSuccessResponse("Операция выполнена успешно")

или

return getSuccessResponse({
  message: "Скрипт создан",
  script_id: 12345
})

getErrorResponse()

Используется для возврата ошибки без выбрасывания исключения.

return getErrorResponse("Не удалось отправить сообщение")

или

return getErrorResponse(
  { message: "Ошибка валидации", field: "email" },
  "VALIDATION_ERROR"
)

Почему это важно: ошибка становится данными, а не неконтролируемым исключением.


Дополнительные типы ответов

Плагин также содержит готовые шаблоны для типовых ситуаций:

  • getValidationErrorResponse() — ошибки валидации;
  • getNotFoundResponse() — сущность не найдена;
  • getForbiddenResponse() — доступ запрещён.

Их удобно использовать в API и сервисных методах, чтобы не изобретать форматы заново.


Почему мы не кидаем exceptions везде

В классическом backend-подходе ошибки часто бросаются через throw. В Metabot это не основной сценарий, по нескольким причинам:

  1. JS-код выполняется внутри движка (V8), а не в отдельном Node-процессе.

  2. Исключения могут:

    • прервать выполнение сценария;
    • привести к неочевидным последствиям;
    • быть плохо читаемыми в логах.
  3. Большинство ошибок — ожидаемые:

    • не найден скрипт,
    • пустой параметр,
    • недоступен внешний сервис,
    • неверный формат данных.

Поэтому:

  • ошибки возвращаются как результат;
  • throw используется только для действительно критических ситуаций.

Как это работает в цепочках вызовов

Типичный сценарий:

const result = sendFormattedMessage(text, 'HTML')

if (!result.success) {
  // можно залогировать
  // можно показать fallback
  // можно вернуть ошибку выше
  return result
}

// продолжаем логику

Таким образом:

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

Использование в API-эндпоинтах

Внутренние и внешние API Metabot обычно выглядят так:

try {
  // бизнес-логика
  return getSuccessResponse({ result })
} catch (e) {
  return getErrorResponse(e.message)
}

Снаружи клиент всегда получает предсказуемый JSON, независимо от источника ошибки.

Это критично для:

  • фронтендов;
  • интеграций;
  • автоматических сценариев;
  • AI-агентов, которые анализируют ответы.

Исходный код плагина Common.Utils.Response

/**
 * 📦 Плагин: Common.Response.Wrapper
 * Автор: @ArtemGarashko
 * Версия: 1.4
 * Дата последнего обновления: 26 апр 2025
 *
 * Назначение:
 * - Формирование ответов об успешных и неудачных операциях.
 * - Используется в API, между компонентами, в бизнес-логике.
 */

function getErrorResponse(errorOrData, errorCode = null) {
  const result = { success: false, error: true }

  if (typeof errorOrData === "string") {
    result.message = errorOrData
  } else if (typeof errorOrData === "object") {
    Object.assign(result, errorOrData)
  }

  if (errorCode) {
    result.error_code = errorCode
  }

  return result
}

function getSuccessResponse(dataOrMessage = {}) {
  const result = { success: true }

  if (typeof dataOrMessage === "string") {
    result.message = dataOrMessage
  } else if (typeof dataOrMessage === "object") {
    Object.assign(result, dataOrMessage)
  }

  return result
}

function getValidationErrorResponse(details) {
  return {
    success: false,
    error: true,
    message: details.message || "Validation failed",
    fields: details.fields || []
  }
}

function getNotFoundResponse(entity = "Entity") {
  return {
    success: false,
    error: true,
    message: `${entity} not found`
  }
}

function getForbiddenResponse(reason = "Access denied") {
  return {
    success: false,
    error: true,
    message: reason
  }
}

exports.getErrorResponse = getErrorResponse
exports.getSuccessResponse = getSuccessResponse
exports.getValidationErrorResponse = getValidationErrorResponse
exports.getNotFoundResponse = getNotFoundResponse
exports.getForbiddenResponse = getForbiddenResponse

⚠️ Версия Common.Utils.Response, приведённая в статье, может отличаться от версии, установленной на сервере Metabot. Используйте код как ориентир и архитектурный шаблон.


Связь с предыдущим уроком

В уроке про форматированные сообщения мы использовали этот подход:

  • плагин sendFormattedMessage отправляет сообщение;

  • но возвращает стандартизированный результат;

  • вызывающая сторона может:

    • проигнорировать результат;
    • обработать ошибку;
    • использовать данные дальше.

То есть:

helper-плагины делают действие response-плагин описывает результат

Это разделение ответственности — важный архитектурный принцип.


Можно ли сделать свой стандарт

Да, конечно.

Common.Utils.Response — это:

  • рекомендованный стандарт;
  • доступный на всех серверах Metabot;
  • проверенный в бою.

Но если вам нужен:

  • другой формат;
  • дополнительные поля;
  • специфичная структура под внешний API —

вы можете:

  • скопировать подход;
  • реализовать свой response-helper;
  • использовать его внутри своего продукта.

Главное — консистентность.


Итог

В этом уроке мы зафиксировали ключевую практику Metabot:

  • ошибки — это данные, а не исключения;
  • успешные результаты и ошибки имеют единый формат;
  • плагины и модули легко комбинируются;
  • API и внутренняя логика говорят на одном языке.

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

Именно такие мелкие решения потом определяют, будет система масштабируемой или нет.