mcpField
V
vtex-io-service-configuration-apps

vtex-io-service-configuration-apps

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

关于 vtex-io-service-configuration-apps

面向 VTEX IO 开发者的编码技能,用于通过独立应用向服务传递结构化配置。内容涵盖基于 schema 的契约、运行时注入,以及通过 `ctx.vtex.settings` 读取配置。

主要功能

  1. 01分离服务与配置应用的职责
  2. 02使用 JSON Schema 定义配置
  3. 03通过 VTEX IO 上下文注入配置
  4. 04从 `ctx.vtex.settings` 读取设置
  5. 05显式开放配置入口

适用场景

  1. 01在多个服务或应用间共享配置
  2. 02将配置变更与服务发布周期分离
  3. 03为服务路由或 GraphQL 查询配置参数

安装

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

$npx skills add https://github.com/vtex/skills --skill vtex-io-service-configuration-apps

安装后在 Claude Code 中输入/vtex-io-service-configuration-apps确认已加载。

详细介绍

服务配置应用

此技能的适用场景

当 VTEX IO 服务需要通过 configuration builder 从其他应用接收结构化配置,而不是仅依赖本地应用设置时,请使用此技能。

  • 使用 configuration builder 创建配置应用
  • 从服务应用公开配置入口
  • 在多个服务或应用之间共享同一配置契约
  • 将配置生命周期与运行时应用生命周期分离
  • 通过 ctx.vtex.settings 读取注入的配置

以下情况不要使用此技能:

  • 仅通过 settingsSchema 管理的简单应用本地配置
  • 通过 contentSchemas.json 配置的 Store Framework 区块设置
  • 与配置无关的通用服务运行时接线
  • 超出配置所需权限范围的策略设计

决策规则

  • 将服务应用和配置应用视为职责分离的两个部分。
  • 服务应用负责运行时(node、graphql 等),在 manifest.json 中声明 configuration builder,定义 configuration/schema.json,并通过 ctx.vtex.settings 读取注入的值。
  • 配置应用不负责服务运行时。它不应声明 node 或 graphql builders,通常只使用 configuration builder。
  • 配置应用通过 configuration 字段指向目标服务,并在 <service-app>/configuration.json 中提供具体值。
  • 当配置契约需要独立于使用它的应用时,使用配置应用。
  • 当多个应用或服务需要共享同一配置模型时,优先使用配置应用。
  • 在服务应用中,如果服务需要从配置应用接收配置,请通过 node/service.json 路由或事件中的 settingsType: "workspace",或通过 GraphQL 中的 @settings,显式公开配置入口。
  • 在配置应用中,configuration/ 下的文件夹名称必须与 configuration 字段中的键匹配,且应对应目标服务应用 ID,例如 vendor.shipping-service 中的 shipping-service。
  • configuration.json 的结构必须符合服务应用声明的 JSON Schema。
  • 在服务运行时中,从 ctx.vtex.settings 读取收到的配置,而不是仅为获取这些值自行发起 HTTP 请求。
  • 处理程序和解析器应将 ctx.vtex.settings 强制转换为或验证为符合配置架构的结构,并应用与该架构一致的默认值。
  • 将配置应用视为通过 VTEX IO 上下文注入结构化运行时配置的方式,而非任意运营数据存储的替代方案。
  • 当配置仅适用于单个应用,且应直接在 Apps > App Settings 中编辑时,使用 settingsSchema。当配置契约需要共享、版本化或与使用它的应用生命周期解耦时,使用配置应用。
  • 如果通过配置应用配置的服务因权限问题无法解析工作区应用配置,请明确评估该场景是否需要在 manifest 中添加 read-workspace-apps 策略。不要默认将此策略添加到无关服务。
  • 对于服务配置契约,优先使用带有 additionalProperties: false 的封闭架构;当结构变得更复杂时,使用 definitions 和 $ref。

硬性约束

约束:服务应用必须显式选择接收配置

服务应用必须使用 settingsType: "workspace" 在 node/service.json 的路由或事件中声明配置可以注入的位置,或者在 GraphQL 中使用 @settings 指令。

原因

配置应用不会自动应用于所有服务入口。服务必须显式标记哪些路由、事件或查询会解析运行时配置。

检测

如果预期服务会接收配置,但其路由、事件或 GraphQL 查询未声明 settingsType 或 @settings,请停止并先明确配置边界。

正确

{
  "routes": {
    "status": {
      "path": "/_v/status/:code",
      "public": true,
      "settingsType": "workspace"
    }
  }
}

错误

{
  "routes": {
    "status": {
      "path": "/_v/status/:code",
      "public": true
    }
  }
}
约束:必须使用显式 schema 文件定义配置结构

配置应用及其配置的服务必须使用显式 schema 文件,而不是隐式或未记录的负载。

原因

如果没有 configuration/schema.json 和与之匹配的 configuration.json 契约,跨应用共享的配置就会变得含糊且容易出错。

检测

如果引入配置应用时没有清晰的 schema 文件,或者服务接受结构松散的配置负载,请停止并先定义 schema。

正确

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/ServiceConfiguration",
  "definitions": {
    "ServiceConfiguration": {
      "type": "object",
      "properties": {
        "bank": {
          "type": "object",
          "properties": {
            "account": { "type": "string" },
            "workspace": { "type": "string", "default": "master" },
            "version": { "type": "string" },
            "kycVersion": { "type": "string" },
            "payoutVersion": { "type": "string" },
            "host": { "type": "string" }
          },
          "required": ["account", "version", "kycVersion", "payoutVersion", "host"],
          "additionalProperties": false
        }
      },
      "required": ["bank"],
      "additionalProperties": false
    }
  }
}

错误

{
  "anything": true
}
约束:使用注入配置的应用必须从运行时上下文读取配置,不得自行发起额外请求

通过配置应用配置服务时,服务必须从 ctx.vtex.settings 读取注入的值,而不是自行发起临时 HTTP 调用来获取相同的配置。

原因

配置应用的作用是让 VTEX IO 将结构化配置直接注入服务上下文。在此基础上增加自定义 fetch 层会带来不必要的复杂性,也会失去构建器的主要运行时优势。

检测

如果服务已公开 settingsType 或 @settings,但仍会自行发起后端 fetch 来获取相同的配置,请停止并改为从 ctx.vtex.settings 读取。

正确

export async function handleStatus(ctx: Context) {
  const settings = ctx.vtex.settings
  const code = ctx.vtex.route.params.code

  const status = resolveStatus(code, settings)
  ctx.body = { status }
}

错误

export async function handleStatus(ctx: Context) {
  const settings = await ctx.clients.partnerApi.getSettings()
  ctx.body = settings
}

推荐模式

将服务和配置应用建模为独立的契约:

  1. 服务应用公开可解析配置的位置。
  2. 服务应用在 configuration/schema.json 中定义接受的结构。
  3. 配置应用将服务声明为构建器,并在 configuration.json 中提供值。
  4. 服务通过 ctx.vtex.settings 读取注入的配置。

示例:服务应用 vendor.shipping-service

manifest.json:

{
  "vendor": "vendor",
  "name": "shipping-service",
  "version": "1.0.0",
  "builders": {
    "node": "7.x",
    "configuration": "1.x"
  }
}

configuration/schema.json:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/ShippingConfiguration",
  "definitions": {
    "ShippingConfiguration": {
      "type": "object",
      "properties": {
        "carrierApi": {
          "type": "object",
          "properties": {
            "baseUrl": { "type": "string" },
            "apiKey": { "type": "string", "format": "password" },
            "timeoutMs": { "type": "integer", "default": 3000 }
          },
          "required": ["baseUrl", "apiKey"],
          "additionalProperties": false
        }
      },
      "required": ["carrierApi"],
      "additionalProperties": false
    }
  }
}

示例:配置应用 vendor.shipping-config

manifest.json:

{
  "vendor": "vendor",
  "name": "shipping-config",
  "version": "1.0.0",
  "builders": {
    "configuration": "1.x"
  },
  "configuration": {
    "shipping-service": "1.x"
  }
}

configuration/shipping-service/configuration.json:

{
  "carrierApi": {
    "baseUrl": "https://api.carrier.com",
    "apiKey": "secret-api-key-here",
    "timeoutMs": 5000
  }
}

示例:读取注入配置的 Node 服务

export async function createShipment(ctx: Context, next: () => Promise<void>) {
  const settings = ctx.vtex.settings as {
    carrierApi: {
      baseUrl: string
      apiKey: string
      timeoutMs?: number
    }
  }

  const timeoutMs = settings.carrierApi.timeoutMs ?? 3000

  const response = await ctx.clients.carrier.createShipment({
    baseUrl: settings.carrierApi.baseUrl,
    apiKey: settings.carrierApi.apiKey,
    timeoutMs,
    payload: ctx.state.shipmentPayload,
  })

  ctx.body = response
  await next()
}

示例:使用 @settings 的 GraphQL 查询

type ShippingStatus {
  orderId: ID!
  status: String!
}

type Query {
  shippingStatus(orderId: ID!): ShippingStatus
    @settings(type: "workspace")
}
export const resolvers = {
  Query: {
    shippingStatus: async (_: unknown, args: { orderId: string }, ctx: Context) => {
      const settings = ctx.vtex.settings as {
        carrierApi: { baseUrl: string; apiKey: string }
      }

      return ctx.clients.carrier.getStatus({
        baseUrl: settings.carrierApi.baseUrl,
        apiKey: settings.carrierApi.apiKey,
        orderId: args.orderId,
      })
    },
  },
}

服务配置应用的最低工作清单:

  • 服务应用在 manifest.json 中声明 configuration 构建器。
  • 服务应用定义有效的 configuration/schema.json。
  • 配置应用提供 <service-app>/configuration.json,其中的值与 schema 兼容。
  • 需要配置的服务路由或事件声明 settingsType: "workspace"。
  • 如果流程依赖工作区应用解析,服务清单会评估是否需要 read-workspace-apps。

当配置应由 VTEX IO 运行时共享、版本化并注入,而非由服务代码临时获取时,请使用此方法。

常见失败模式

  • 实际需求是在多个应用之间共享配置契约,却使用了应用设置。
  • 创建配置应用时,没有提供明确的架构文件。
  • 忘记在应接收配置的服务中设置 settingsType 或 @settings。
  • 配置已注入 ctx.vtex.settings,却仍通过 HTTP 获取配置。
  • 将配置应用当作通用的运营数据存储。

审查清单

  • 是否真的需要配置应用,而不是使用普通的 settingsSchema?
  • 能否通过本地应用设置和 settingsSchema 解决此问题,而无需单独的配置应用?
  • 服务是否通过 settingsType 或 @settings 明确选择启用配置解析?
  • 通过服务路由或事件注入配置时,是否在需要的位置声明了 settingsType: "workspace"?
  • 配置契约是否通过 configuration/schema.json 定义,并与 configuration.json 保持一致?
  • 服务是否从 ctx.vtex.settings 读取配置,而不是自行发起额外的获取请求?
  • 如果工作流依赖于读取已安装的工作区应用或其配置,是否经过审慎评估后才使用 read-workspace-apps,而不是默认添加?
  • 配置架构是否保持封闭且足够明确,以便作为共享契约?
  • 配置契约是否与运营数据存储明确区分?

参考资料

评论

支持 Markdown