关于 vtex-io-client-integration
为 VTEX IO 后端开发提供服务集成指导,帮助选择和配置客户端、注册客户端并通过请求上下文调用。重点涵盖原生与自定义客户端的取舍及避免直接使用 HTTP 库。
主要功能
- 01选择原生或自定义客户端
- 02在 IOClients 中注册客户端
- 03通过 ctx.clients 调用集成
- 04配置重试、超时、缓存与指标
- 05避免后端直接发起 HTTP 请求
适用场景
- 01为 VTEX 服务选择合适客户端
- 02接入非 VTEX 外部 API
- 03审查使用原始 HTTP 库的代码
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vtex/ skills --skill vtex-io-client-integration安装后在 Claude Code 中输入/vtex-io-client-integration确认已加载。
详细介绍
客户端集成与服务访问
适用场景
当主要需要决定 VTEX IO 后端应用如何通过 VTEX IO 客户端系统调用 VTEX 服务或外部 API 时,使用此技能。
- 在
node/clients/下创建自定义客户端 - 在
@vtex/api或@vtex/clients的原生客户端与自定义客户端之间进行选择 - 在
IOClients中注册客户端,并通过ctx.clients提供访问 - 配置
InstanceOptions,例如重试、超时、请求头或缓存 - 检查当前使用原始 HTTP 库的后端集成
以下场景不适用:
- 决定
manifest.json中的应用契约 - 构建
node/index.ts的结构或调整service.json - 设计 GraphQL 架构或解析器契约
- 建模路由授权或安全权限
- 构建店面或管理后台的前端集成
决策规则
- 如果
@vtex/api或@vtex/clients中已有原生客户端可用于目标 VTEX 服务,优先使用。常见示例包括目录、结账、物流和 OMS 客户端。只有在没有合适的原生客户端或工厂时,才编写自定义客户端。 ExternalClient主要用于非 VTEX 外部 API。当存在或更适合使用@vtex/clients、JanusClient或其他有文档说明的更高层 VTEX 客户端时,避免将其用于*.myvtex.com或*.vtexcommercestable.com.br等由 VTEX 托管的端点。- Janus 是 VTEX 的 Core Commerce API 网关。只有在需要通过 Janus 调用 VTEX Core Commerce API,且
@vtex/clients中没有合适的原生客户端时,才使用JanusClient。 - 只有在有明确文档指引的情况下,才使用
InfraClient对接 VTEX IO 基础设施服务。在合作伙伴应用中,优先使用masterData或vbase等更高层客户端和工厂,而不是直接扩展InfraClient。 - 在
node/clients/index.ts中通过扩展IOClients的Clients类注册每个自定义或原生客户端。 - 通过
ctx.clients使用集成,切勿在中间件、解析器或事件处理器中实例化客户端类。 - 客户端应专注于传输、请求选项、端点路径和简单的响应处理。业务规则、授权决策和编排应放在客户端之外。
- 构建自定义客户端时,始终依赖 VTEX IO 传入的
IOContext,例如account、workspace和可用的身份验证令牌,不要硬编码账户名称、工作区或特定环境的 VTEX URL。 - 在运行时客户端配置中配置共享的
InstanceOptions;只有在某项集成确有不同需求时,才使用特定客户端的覆盖配置。 - 对重要的客户端调用使用
metric选项,以便在客户端层跟踪和监控集成,而不仅仅是在处理器层进行跟踪和监控。 - 将错误规范化处理放在客户端边界附近,但避免隐藏对可观测性和调试至关重要的 HTTP 状态码或传输故障。
- 与外部服务集成时,确认应用契约中声明了所需的出站策略;但具体的策略建模应由 auth 或 app-contract 技能负责。
- 在少数迁移或遗留场景中,可以暂时使用
ExternalClient访问由 VTEX 托管的端点,但应将其视为例外。长期目标应是转向原生客户端或有相应文档说明的 VTEX 客户端抽象,以保持路由、身份验证和可观测性的一致性。
客户端选择指南:
| 客户端类型 | 适用场景 | 不适用场景 |
|---|---|---|
ExternalClient | 调用非 VTEX 的外部 API | 已有原生客户端或基于 Janus 的抽象的 VTEX 托管 API |
JanusClient | 调用尚未由 @vtex/clients 封装的 VTEX Core Commerce API | 已有原生客户端的 VTEX 服务,例如 Catalog、Checkout、Logistics 或 OMS |
InfraClient | 仅在有明确文档指导的情况下,实现高级基础设施类客户端 | 合作伙伴应用中的一般 VTEX 或外部 API |
InstanceOptions 启发式建议:
- 从较小且明确的客户端默认值开始,例如
retries: 2,以及介于1000到3000毫秒之间的请求timeout。 - 对幂等操作使用较小的有限重试值,例如
1到3。 - 除非上游 API 明确说明幂等操作是安全的,否则避免对非幂等操作自动重试。
- 不要使用较高的重试次数来掩盖上游不稳定。应清楚地呈现反复发生的故障,并在业务层有针对性地处理。
- 优先使用客户端级别的请求头和指标,而不是在各个处理程序中分散定义请求头。
- 仅在重复读取确有需要且响应可以安全复用时,才使用内存或磁盘缓存选项。
- 将身份验证设置放在客户端构造函数或工厂配置中,不要在各个处理程序中重复设置。
硬性约束
约束:所有服务间 HTTP 调用都必须通过 VTEX IO 客户端
来自 VTEX IO 后端应用的 HTTP 通信 MUST 通过 @vtex/api 或 @vtex/clients 客户端进行。服务集成不得使用 axios、fetch、got 或 node-fetch 等原始库。
原因
VTEX IO 客户端提供原始库绕过的传输行为,包括身份验证上下文、重试、指标、缓存选项以及可感知基础设施的请求执行。原始 HTTP 调用会使集成更难观测,也更容易配置错误。
检测
如果在 VTEX IO 后端服务中看到 axios、fetch、got、node-fetch 或直接编写的临时 HTTP 代码,请立即停止,并将其替换为合适的 VTEX IO 客户端模式。
正确
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'
export class WeatherClient extends ExternalClient {
constructor(context: IOContext, options?: InstanceOptions) {
super('https://api.weather.com', context, {
...options,
headers: {
'X-VTEX-Account': context.account,
'X-VTEX-Workspace': context.workspace,
'X-Api-Key': process.env.WEATHER_API_KEY,
...options?.headers,
},
})
}
public getForecast(city: string) {
return this.http.get(`/v1/forecast/${city}`, {
metric: 'weather-forecast',
})
}
}
错误
import axios from 'axios'
export async function getForecast(city: string) {
const response = await axios.get(`https://api.weather.com/v1/forecast/${city}`, {
headers: {
'X-Api-Key': process.env.WEATHER_API_KEY,
},
})
return response.data
}
约束:客户端必须在 IOClients 中注册,并通过 ctx.clients 使用
客户端 MUST 在继承 IOClients 的 Clients 类中注册,中间件、解析器或事件处理程序 MUST 通过 ctx.clients 访问客户端。
原因
VTEX IO 客户端注册表可确保一致地应用当前请求上下文、选项、缓存行为和检测机制。在处理程序中直接实例化客户端会绕过这一共享生命周期,并产生脆弱的集成代码。
检测
如果在中间件、解析器或事件处理程序中看到 new MyClient(...),请立即停止。将客户端移至 node/clients/,在 IOClients 中注册,并通过 ctx.clients 使用。
正确
import { IOClients } from '@vtex/api'
import { Catalog } from '@vtex/clients'
export class Clients extends IOClients {
public get catalog() {
return this.getOrSet('catalog', Catalog)
}
}
export async function getSku(ctx: Context) {
const sku = await ctx.clients.catalog.getSkuById(ctx.vtex.route.params.id)
ctx.body = sku
}
错误
import { Catalog } from '@vtex/clients'
export async function getSku(ctx: Context) {
const catalog = new Catalog(ctx.vtex, {})
const sku = await catalog.getSkuById(ctx.vtex.route.params.id)
ctx.body = sku
}
约束:选择与集成边界相匹配的最具体客户端类型
每项集成 MUST 根据其边界使用正确的客户端抽象。如果已有更具体的客户端类型或原生包,不要默认对所有集成都使用 ExternalClient 或 JanusClient。
原因
客户端类型体现设计意图,并决定如何处理身份验证、URL 和服务边界。使用错误的抽象会让集成更难理解,也更容易偏离 VTEX IO 惯例。
检测
如果目标是 VTEX Core Commerce API,请立即停止,并确认使用 @vtex/clients 中的原生客户端或 JanusClient 是否比 ExternalClient 更合适。如果目标由 VTEX 托管,请立即停止,并确认不存在有文档说明的、更具体的 VTEX 客户端抽象后,再默认使用 ExternalClient。
正确
import type { IOContext, InstanceOptions } from '@vtex/api'
import { JanusClient } from '@vtex/api'
export class RatesAndBenefitsClient extends JanusClient {
constructor(context: IOContext, options?: InstanceOptions) {
super(context, options)
}
}
错误
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'
export class RatesAndBenefitsClient extends ExternalClient {
constructor(context: IOContext, options?: InstanceOptions) {
super(`https://${context.account}.vtexcommercestable.com.br`, context, options)
}
}
推荐模式
推荐的文件布局:
node/
├── clients/
│ ├── index.ts
│ ├── catalog.ts
│ └── partnerApi.ts
├── middlewares/
│ └── getData.ts
└── index.ts
在同一处注册原生客户端和自定义客户端:
import { IOClients } from '@vtex/api'
import { Catalog } from '@vtex/clients'
import { PartnerApiClient } from './partnerApi'
export class Clients extends IOClients {
public get catalog() {
return this.getOrSet('catalog', Catalog)
}
public get partnerApi() {
return this.getOrSet('partnerApi', PartnerApiClient)
}
}
使用明确的路由和选项创建自定义客户端:
import type { IOContext, InstanceOptions } from '@vtex/api'
import { ExternalClient } from '@vtex/api'
export class PartnerApiClient extends ExternalClient {
private routes = {
order: (id: string) => `/orders/${id}`,
}
constructor(context: IOContext, options?: InstanceOptions) {
super('https://partner.example.com', context, {
...options,
retries: 2,
timeout: 2000,
headers: {
'X-VTEX-Account': context.account,
'X-VTEX-Workspace': context.workspace,
...options?.headers,
},
})
}
public getOrder(id: string) {
return this.http.get(this.routes.order(id), {
metric: 'partner-get-order',
})
}
}
在运行时配置共享客户端选项:
import type { ClientsConfig } from '@vtex/api'
import { Clients } from './clients'
const clients: ClientsConfig<Clients> = {
implementation: Clients,
options: {
default: {
retries: 2,
timeout: 2000,
},
},
}
在处理程序中通过 ctx.clients 使用客户端:
export async function getOrder(ctx: Context) {
const order = await ctx.clients.partnerApi.getOrder(ctx.vtex.route.params.id)
ctx.body = order
}
如果客户端文件变得过大,请按边界明确的集成领域拆分,并将 node/clients/index.ts 保持为简洁的注册表。
常见故障模式
- 在后端处理程序中使用
axios、fetch或其他原始 HTTP 库,而不是 VTEX IO 客户端。 - 直接在处理程序中实例化客户端,而不是在
IOClients中注册客户端。 - 选择
ExternalClient,而原生 VTEX 客户端或更具体的应用客户端已经存在。 - 将业务规则、验证或编排放入客户端,而不是让客户端仅作为传输封装层。
- 将标头、身份验证设置和重试设置散落在各个处理程序中,而不是集中配置在客户端或共享客户端配置中。
- 忘记自定义客户端中声明外部集成所需的出站访问策略。
审查清单
- 每个集成是否使用了正确的 VTEX IO 客户端抽象?
- 是否优先使用
@vtex/api或@vtex/clients中可用的原生客户端? - 是否在
IOClients中注册客户端,并通过ctx.clients使用客户端? - 后端集成代码中是否未使用原始 HTTP 库?
- 重试、超时、标头和指标是否在客户端层配置,而不是散落在各个处理程序中?
- 是否避免在客户端层放置业务规则?
参考资料
- 使用 Node 客户端 - 如何通过
ctx.clients使用客户端 - 开发客户端 - 如何使用
@vtex/api构建自定义客户端 - 使用 VTEX IO 客户端 - 如何使用 VTEX 客户端调用 Core Commerce API
- 客户端 - VTEX IO 客户端架构和原生客户端目录