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

Как писать API-эндпоинты в Metabot

Контракты ответов, обработка ошибок и рекомендации по архитектуре

В этом уроке мы разберём, как правильно проектировать API-эндпоинты в Metabot, чтобы они:

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

⚠️ Этот урок продолжает и опирается на предыдущий:

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

Если вы ещё не знакомы с Common.Utils.Response — начните с него.


Архитектурный контекст

В Metabot API — это граничный слой системы:

  • с одной стороны — HTTP / внешние клиенты / интеграции;
  • с другой — бизнес-логика, плагины, сценарии, CJM, low-code.

Ключевой принцип:

API-эндпоинты используют тот же контракт ответов, что и внутренняя логика Metabot.

Это означает:

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

Базовый шаблон API-эндпоинта

Каждый API-эндпоинт в Metabot — это явная композиция зависимостей:

  • стандарт ответа;
  • бизнес-модели;
  • сервисы;
  • авторизация (если требуется).

Никакие классы не считаются “доступными по умолчанию” — всё подключается явно.

Базовый шаблон

Практически все API в Metabot строятся по одной схеме:

const { getSuccessResponse, getErrorResponse } = require('Common.Utils.Response')

// 1. Валидация входных данных
if (!request.json || !request.json.required_field) {
  return getErrorResponse("Missing required field: required_field")
}

// 2. Авторизация (если нужна)
// 3. Вызов бизнес-логики
try {
  const result = SomeService.doSomething(request.json)
  return getSuccessResponse({ result })
} catch (e) {
  return getErrorResponse(e.message || "Operation failed")
}

📌 Это не шаблон ради шаблона — это зафиксированная архитектурная договорённость.


Валидация входных данных

Простой случай

if (!request.json || !request.json.event_id) {
  return getErrorResponse("Missing required field: event_id")
}

Почему так:

  • ошибка ожидаемая;
  • это не исключение;
  • клиент получает читаемое сообщение;
  • выполнение сценария не ломается.

Расширенная валидация (через helper)

const { getValidationErrorResponse } = require('Common.Utils.Response')
const { validateRequiredRequestFields } = require('Common.Utils.Validation')

const validation = validateRequiredRequestFields(request.json, [
  'email',
  'password',
  'nickname'
])

if (validation !== true) {
  return getValidationErrorResponse(validation)
}

📌 Такой ответ можно:

  • отобразить во фронте;
  • проанализировать в AI-агенте;
  • использовать в автоматических сценариях.

Авторизация внутри API

Авторизация всегда делается до бизнес-логики.

Пример с JWT:

const Auth = require('Common.Users.Auth')

const jwt = request.json?.jwt || null
const user = jwt ? Auth.verifyToken(jwt) : null

if (!user || !user.id) {
  return getErrorResponse("Unauthorized: invalid or missing token")
}

Почему именно так:

  • авторизация — это тоже ожидаемая ошибка;
  • API не кидает throw;
  • клиент всегда получает success: false.

Работа с бизнес-логикой (корректные примеры)

Пример: получение судей события

const { getSuccessResponse, getErrorResponse } = require('Common.Utils.Response')
const Judges = require('Business.Events.Judges')

const { event_id } = request.json || {}

if (!event_id) {
  return getErrorResponse("Missing required field: event_id")
}

try {
  const judges = Judges.find({ eventId: event_id })
  return getSuccessResponse({ judges })
} catch (error) {
  return getErrorResponse(error.message || "Failed to load judges")
}

✔ Явно видно:

  • откуда берётся Judges;
  • где бизнес-логика;
  • где контракт ответа.

📌 API не знает деталей реализации Judges.find 📌 API знает только: успех или ошибка


Пример: работа с Event и Round (без магии)

const { getSuccessResponse, getErrorResponse } = require('Common.Utils.Response')
const Event = require('Business.Events.Event')
const Round = require('Business.Events.Round')

if (!request.json || !request.json.event_id) {
  return getErrorResponse("Missing required field: event_id")
}

let event
try {
  event = new Event().setEventById(request.json.event_id)
} catch (e) {
  return getErrorResponse("Event not found: " + e.message)
}

const currentRoundId = event.getCurrentRoundId()

if (!currentRoundId) {
  return getErrorResponse("Current round not set for this event")
}

let round
try {
  round = new Round().setRoundById(currentRoundId)
} catch (e) {
  return getErrorResponse("Round not found: " + e.message)
}

return getSuccessResponse(round.getAllData())

📌 Здесь принципиально важно, что:

  • Event и Round подключены явно;
  • API-слой не «угадывает», что есть в окружении;
  • пример можно скопировать и использовать без сюрпризов.

Каждый шаг:

  • либо возвращает валидные данные;
  • либо завершает API предсказуемым error-response.

Возврат данных

Один объект

return getSuccessResponse(event.getAllData())

Коллекция

return getSuccessResponse({ submissions })

Комбинированный результат

return getSuccessResponse({
  event_id,
  rounds,
  can_participate: true
})

📌 Нет жёсткого правила, что возвращать 📌 Есть правило, как возвращать


Цепочки логики без исключений

const { getSuccessResponse, getErrorResponse } = require('Common.Utils.Response')
const Auth = require('Common.Users.Auth')

const result = Auth.login(email, password)

if (!result.success) {
  return getErrorResponse(result)
}

return getSuccessResponse(result)

📌 API не пересобирает ошибку, а прокидывает её дальше.

Почему это правильно:

  • бизнес-метод уже использует Common.Utils.Response;
  • API не ломает контракт;
  • ошибка поднимается наверх без искажения.

HTTP-статусы и success

В Metabot принято:

  • HTTP-статус почти всегда 200;
  • реальный статус операции — в теле ответа.

Почему:

  • API используется не только браузерами;
  • сценарии и AI-агенты не всегда работают с HTTP-кодами;
  • единый формат упрощает автоматическую обработку.

📌 success: false важнее, чем HTTP 400


Антипаттерны (так делать не стоит)

❌ Возвращать true / false ❌ Кидать throw для валидации ❌ Возвращать строки вместо объектов ❌ Мешать разные форматы ошибок ❌ Делать API, который возвращает «что попало»

Если ловишь себя на мысли:

«Да тут проще просто вернуть true…»

— это хороший момент остановиться и проверить: а не ломаешь ли ты контракт всей платформы?


Итог

API в Metabot — это:

  • не просто HTTP-эндпоинты;
  • а часть общей архитектуры выполнения.

Ключевые принципы:

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

Если вы придерживаетесь этих правил:

  • API легко читать;
  • его просто документировать;
  • его удобно использовать во фронте, интеграциях и AI-агентах;
  • система остаётся масштабируемой.