mcpField
V
vtex-io-http-routes

vtex-io-http-routes

github.com/vtex开发471.2k 次安装2 周前更新
代码仓库

关于 vtex-io-http-routes

为 VTEX IO 服务开发者提供 HTTP 路由设计指导,涵盖路由声明、处理器边界与中间件组织。重点是明确请求响应契约,并将耗时工作留给异步流程。

主要功能

  1. 01配置 service.json 路由并连接处理器
  2. 02在处理器边界验证请求并规范响应
  3. 03通过中间件复用校验与认证逻辑
  4. 04区分 HTTP 请求与异步批处理工作

适用场景

  1. 01构建回调或 Webhook 端点
  2. 02为合作伙伴或后台流程提供集成接口
  3. 03整理路由处理器与中间件链
  4. 04检查路由的参数、响应及状态码约定

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/vtex/skills --skill vtex-io-http-routes

安装后在 Claude Code 中输入/vtex-io-http-routes确认已加载。

详细介绍

HTTP 路由与处理器边界

适用场景

当 VTEX IO 服务需要通过 service.json 路由公开明确的 HTTP 端点,并在 node/ 下实现相应的处理器时,请使用此技能。

  • 构建回调或 webhook 端点
  • 为合作伙伴或后台流程公开集成端点
  • 组织路由处理器和中间件链
  • 验证参数、查询字符串、标头或请求正文
  • 统一响应格式和状态码行为

以下场景请勿使用此技能:

  • 调整或优化服务运行时
  • 在 manifest.json 中决定应用策略
  • 设计 GraphQL API
  • 建模异步事件或工作进程流程

决策规则

  • 当集成需要明确的 URL 合约、webhook 或回调式请求-响应行为时,请使用 HTTP 路由。
  • 在 VTEX IO 中,service.json 声明路由 ID、路径和 public 等公开范围,而 Node 入口点会将这些路由 ID 连接到从 node/routes 导出的处理器。中间件在代码中组合,而非直接在 service.json 中声明。
  • 路由处理器应保持精简且明确。路由代码应验证输入、调用领域或集成服务,并构造响应。
  • 将验证、请求规范化或共享身份验证检查等横切关注点放入中间件,而不是在各个处理器中重复实现。
  • 尽可能在处理器边界附近定义路由参数、查询预期和正文结构。
  • 对相似的路由系列使用一致的状态码和响应结构。
  • 对于 webhook 或回调端点,应遵循调用方文档中对状态码和错误正文的要求,并保持响应简洁且确定,以避免重试情况不明确。
  • 为关键路由记录结构化日志或指标,以便诊断故障、延迟和集成健康状况,同时不改变处理器合约。
  • 优先使用按有界领域分组的明确路由文件,例如 routes/orders.ts 或 routes/catalog.ts。
  • 将公开路由视为明确的外部合约。未经验证、身份验证预期和响应安全性审查,不要将路由扩展为公开用途。

严格约束

约束:路由处理器必须明确 HTTP 合约

每个路由处理器都必须让请求和响应合约在处理器边界处清晰易懂。不要将必需参数、正文字段或状态码决策隐藏在无关服务的深处。

原因

HTTP 集成依赖可预测的合约。当验证和响应构造隐含存在或分散在各处时,合作伙伴集成会变得脆弱,错误也更难诊断。

检测

如果处理器立即委托其他逻辑,却未验证必需参数、查询值、标头或请求正文结构,请停止操作,并先明确合约。

正确

export async function getOrder(ctx: Context, next: () => Promise<void>) {
  const { id } = ctx.vtex.route.params

  if (!id) {
    ctx.status = 400
    ctx.body = { message: 'Missing route param: id' }
    return
  }

  const order = await ctx.clients.partnerApi.getOrder(id)
  ctx.status = 200
  ctx.body = order
  await next()
}

错误

export async function getOrder(ctx: Context) {
  ctx.body = await handleOrder(ctx)
}
约束:共享路由关注点必须放在中间件中,而非在每个处理器中重复实现

验证、请求规范化或通用身份验证检查等重复关注点应由中间件实现,并通过路由链组合。

原因

在多个处理器中重复相同检查会造成行为偏差,导致路由行为不一致。中间件让 HTTP 接口更易审查和演进。

检测

如果多个处理器重复执行相同的正文验证、标头检查或上下文准备,请停止操作,并先提取中间件,再添加更多重复逻辑。

正确

export async function validateSignature(ctx: Context, next: () => Promise<void>) {
  const signature = ctx.request.header['x-signature']

  if (!signature) {
    ctx.status = 401
    ctx.body = { message: 'Missing signature' }
    return
  }

  await next()
}

错误

export async function routeA(ctx: Context) {
  if (!ctx.request.header['x-signature']) {
    ctx.status = 401
    return
  }
}

export async function routeB(ctx: Context) {
  if (!ctx.request.header['x-signature']) {
    ctx.status = 401
    return
  }
}
约束:HTTP 路由不应承载应由事件或工作进程处理的异步或批量工作

路由 MUST 保持请求-响应延迟可控。如果路由会触发耗时、容易重试或面向批量的工作,请将这些工作移至异步流程,并让路由仅作为轻量触发器或确认边界。

重要原因

长时间运行的 HTTP 处理程序会导致集成行为不佳、增加超时风险,并造成运行不稳定。VTEX IO 服务应将即时路由契约与后台处理分开。

检测

如果路由执行大量循环、批量导入、繁重的重试工作,或执行无需在响应前完成的工作,请 STOP,并围绕异步处理重新设计流程。

正确

export async function triggerImport(ctx: Context) {
  await ctx.clients.importApi.enqueueImport(ctx.request.body)
  ctx.status = 202
  ctx.body = { accepted: true }
}

错误

export async function triggerImport(ctx: Context) {
  for (const item of ctx.request.body.items) {
    await ctx.clients.importApi.importItem(item)
  }

  ctx.status = 200
}

推荐模式

推荐的文件布局:

node/
├── routes/
│   ├── index.ts
│   ├── orders.ts
│   └── webhooks.ts
└── middlewares/
    ├── validateBody.ts
    └── validateSignature.ts

在 VTEX IO 服务中连接路由:

在 VTEX IO 中,service.json 声明路由 ID 和路径,Node 入口点在 new Service(...) 中注册 routes 对象,而 node/routes/index.ts 则将每个路由 ID 映射到最终处理程序。中间件通过代码组合,而不是直接在 service.json 中声明。

{
  "routes": {
    "orders-get": {
      "path": "/_v/orders/:id",
      "public": false
    },
    "reviews-create": {
      "path": "/_v/reviews",
      "public": false
    }
  }
}
// node/index.ts
import type { ClientsConfig, RecorderState, ServiceContext } from '@vtex/api'
import { Service } from '@vtex/api'
import { Clients } from './clients'
import routes from './routes'

const clients: ClientsConfig<Clients> = {
  implementation: Clients,
  options: {
    default: {
      retries: 2,
      timeout: 800,
    },
  },
}

declare global {
  type Context = ServiceContext<Clients, RecorderState>
}

export default new Service<Clients, RecorderState>({
  clients,
  routes,
})

精简路由模式:

// node/routes/index.ts
import type { RouteHandler } from '@vtex/api'
import { createReview } from './reviews'
import { getOrder } from './orders'

const routes: Record<string, RouteHandler> = {
  'orders-get': getOrder,
  'reviews-create': createReview,
}

export default routes
// node/routes/orders.ts
import { compose } from 'koa-compose'
import { validateSignature } from '../middlewares/validateSignature'

async function rawGetOrder(ctx: Context, next: () => Promise<void>) {
  const { id } = ctx.vtex.route.params

  if (!id) {
    ctx.status = 400
    ctx.body = { message: 'Missing route param: id' }
    return
  }

  const order = await ctx.clients.partnerApi.getOrder(id)
  ctx.status = 200
  ctx.body = order

  await next()
}

export const getOrder = compose([validateSignature, rawGetOrder])
export async function createReview(ctx: Context, next: () => Promise<void>) {
  const body = ctx.request.body

  if (!body?.productId) {
    ctx.status = 400
    ctx.body = { message: 'Missing productId' }
    return
  }

  const review = await ctx.clients.reviewApi.createReview(body)
  ctx.status = 201
  ctx.body = review
  await next()
}

将领域逻辑放在服务或集成中,并让路由处理程序负责 HTTP 相关事项,例如验证、状态码、标头和响应格式。

常见故障模式

  • 将请求验证隐藏在无关服务中,而不是明确说明路由的预期要求。
  • 在多个处理程序中重复相同的身份验证或规范化逻辑,而不是使用中间件。
  • 让 HTTP 处理程序执行长时间运行的异步或批量工作。
  • 为相似端点返回不一致的状态码或响应格式。
  • 未审查信任边界,就扩大路由的公开范围。

审查清单

  • HTTP 是此契约的合适暴露方式吗?
  • 必需的参数、标头、查询值和请求正文字段是否已在路由边界处验证?
  • 是否已将重复的关注点提取到中间件中?
  • 处理程序是否精简,并专注于 HTTP 相关事项?
  • 是否应将部分工作移至异步事件或工作进程?

相关技能

参考资料

评论

支持 Markdown