关于 vtex-io-masterdata
vtex-io-masterdata 是面向 VTEX IO 开发者的技能,涵盖 Master Data v2 存储设计与实现。它介绍基于 Schema 的 CRUD、搜索、触发器和存储选择,并提供治理及避免误用的指导。
主要功能
- 01使用 JSON Schema 定义实体
- 02通过 MasterDataClient 执行 CRUD
- 03选择搜索或滚动方式检索
- 04配置索引、缓存、安全和触发器
- 05评估 Master Data 与其他存储方案
适用场景
- 01规划评论或表单提交数据的存储
- 02实现基于 Schema 的 VTEX IO 数据访问
- 03设计大型文档集合的检索方式
- 04判断数据应存于 Master Data 还是其他位置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vtex/ skills --skill vtex-io-masterdata安装后在 Claude Code 中输入/vtex-io-masterdata确认已加载。
详细介绍
MasterData v2 集成
此技能的适用场景
当你的 VTEX IO 应用需要存储自定义数据(评论、愿望清单、表单提交内容、配置记录)、查询或筛选这些数据,或设置由数据变更触发的自动化工作流时,请使用此技能;如果还必须说明选择 Master Data 而非其他 VTEX 或外部存储的理由,也请使用此技能。
- 使用
masterdatabuilder 定义数据实体和 JSON Schemas - 通过 MasterDataClient(
ctx.clients.masterdata)执行 CRUD 操作 - 配置搜索、滚动查询和索引,以高效检索数据
- 设置 Master Data 触发器,实现自动化工作流
- 管理 Schema 生命周期,避免达到 60 个 Schema 的限制
- 决定数据应存放在 Catalog(字段、规格、非结构化 SKU/产品规格)、Master Data、原生 OMS/checkout 界面,还是外部 SQL/NoSQL/数据库中
- 避免在购买关键路径(购物车、结账、支付、下单)中同步调用 Master Data,除非有充分的性能与可靠性依据
- 优先采用单一事实来源——避免为图方便在 Master Data 中重复存储订单标头或 OMS 列表;应优先使用 OMS,或采用带有缓存的 BFF(请参阅相关技能)
以下情况不应使用此技能:
- 通用后端服务模式(请改用
vtex-io-service-apps) - GraphQL Schema 定义(请改用
vtex-io-graphql-api) - Manifest 和 builder 配置(请改用
vtex-io-app-structure)
决策规则
选择 Master Data 之前
架构师、开发者以及任何设计或实现解决方案的人员都应深入思考,并将本节视为审视默认选择的检查清单:再次确认 Master Data 是否是合适的持久化层,而不是自动选择它。本技能旨在对出于便利而做出的选择提出质疑。
- 用途——Master Data 是一种面向文档的存储(理念上类似于文档数据库/DynamoDB 风格的访问模式)。它只是众多选项之一;未经工作负载适配性审查,仅仅因为它“现成可用”或“成本低”就选择它,是一种设计异味。
- 与产品相关的数据——如果信息本质上是关于产品或 SKU 的,应优先评估 Catalog:先考虑规格、非结构化产品/SKU 规格以及原生 Catalog 字段,再创建反映 Catalog 事实的并行 MD 实体。
- 购买路径——除非有证据(延迟预算、故障模式),否则不要在 checkout 的热路径(购物车变更、支付、下单)中执行同步的 Master Data 读写。应优先使用原生商务存储,并采用异步或订单完成后的补充处理。
- 订单和列表——将 OMS 或订单数据复制到 Master Data 来支持我的订单或类似功能,通常会破坏单一事实来源。应优先通过 BFF 或 IO 层调用 OMS API(或市场平台协议),并使用应用缓存及正确的 HTTP/路径语义——不要仅仅“因为更容易”就在 MD 中再建一个订单数据库。
- 暴露 MD——Master Data 仅负责存储。任何店面或合作伙伴访问都应经过一个能够执行身份验证、授权和速率限制的服务——通常是遵循 headless-bff-architecture 模式的 VTEX IO 或外部 BFF。
- 适合使用 MD 的情况——完成存储适配性审查后,如果 MD 仍然合适,请按下文实施 CRUD 并遵守 Schema 规范;从 IO 暴露 HTTP 或 GraphQL 时,还应结合 vtex-io-application-performance 和 vtex-io-service-paths-and-cdn。
实体治理与规范管理
在创建新实体或扩展现有实体之前,请先了解整体情况:
- 原生实体 — 平台会管理
CL(客户端)、AD(地址)、OD(订单)、BK(书签)、AU(身份验证)等实体。切勿创建与原生实体用途重复的自定义实体。添加新实体之前,请先了解已有实体。 - 实体用途审计 — 对于拥有数十个自定义实体的账户,请按用途对每个实体分类:日志/监控、缓存/临时数据、订单扩展、客户扩展、营销、CMS/内容、集成/同步、身份验证/身份标识、物流/地理信息或自定义业务逻辑。属于日志或缓存类别的实体往往表明存在误用——IO 应用日志应写入 logger,而不是 MD;缓存应存放在 VBase,而不是 MD。
- 关键路径标记 — 确认实体是否用于结账、购物车、支付或登录流程。关键路径上的实体必须满足严格的延迟和可用性要求。如果 MD 实体位于关键路径上,请质疑它是否应该放在那里。
- 文档数量意识 — 使用
GET /search?_fields=id返回的REST-Content-Range标头,并设置REST-Range: resources=0-0,即可高效统计文档数量,而无需获取文档。大型实体(10 万份及以上文档)需要使用scrollDocuments、制定分页策略,并视情况增加 BFF 缓存层。
批量操作与数据迁移
导入、导出或迁移大型数据集时:
- 导入前验证 — 将导入数据与权威来源交叉核对(例如,使用目录导出数据验证 SKU,使用 CL 实体或用户管理 API 验证电子邮件允许列表)。在接触 MD 之前,先生成无效行的异常报告。
- JSONL 负载 — 在
.jsonl文件中为每份 MD 文档生成一个 JSON 对象,用于批量导入。这样可以逐行处理,并支持断点续传。 - 速率限制 — MD API 会执行速率限制。请在调用之间使用可配置的延迟(例如 400 ms),并在收到 HTTP 429 响应时采用指数退避。
- 检查点 — 对于大型导入(1 万份及以上文档),请将进度持久化到检查点文件(记录最后成功的文档 ID 或行索引)。发生故障或超时时,从检查点恢复,而不是重新开始。
- 使用有界并发进行并行处理 — 使用并发池(例如,设置并发数为 5–10 的
p-queue)并行执行POST或PATCH操作。并行度过高会触发速率限制;过低则会导致速度缓慢。 - 重新导入前批量删除 — 替换某个实体中的所有文档时,请先使用 scroll + delete,再进行导入;或者使用单独的删除流程,并采用相同的检查点和退避模式。
- 架构对齐 — 确保导入负载与实体的 JSON Schema 完全一致。缺少必填字段或类型不匹配会导致验证静默失败。
实现规则
- 数据实体是一个有名称的文档集合(类似数据库表)。JSON Schema 用于定义结构、验证和索引。
- 使用
masterdata构建器时,通过文件夹结构定义实体:masterdata/{entityName}/schema.json。构建器会创建名为{vendor}_{appName}_{entityName}的实体。 - 所有 CRUD 操作均使用
@vtex/clients中的ctx.clients.masterdata或masterDataFor——切勿直接调用 REST。 where子句中使用的所有字段都必须在架构的v-indexed数组中声明,以便高效查询。- 对于结果集范围有限(已知数量较少,最大页大小为 100)的情况,使用
searchDocuments。对于大型或无界结果集,使用scrollDocuments。 masterdata构建器会为每个应用版本创建一个新架构。请清理未使用的架构,避免达到每个实体 60 个架构的硬性上限。
v-* 架构扩展(v-indexed、v-cache、v-security、v-triggers 等)
Master Data v2 使用 v-* 属性扩展标准 JSON Schema,用于控制索引、缓存、安全性、默认值、触发器和架构继承。有关每项扩展的完整参考——包括适用和不适用场景,以及详细配置——请参阅 vtex-io-masterdata-strategy 技能。
IO 应用开发要点:
v-indexed—where子句中使用的所有字段都必须列在此处。不要过度创建索引;每个索引都会增加写入延迟。v-cache— 对读操作较多的实体保持true(默认值)。对需要即时读取一致性的高写入量实体设置为false。v-default-fields— 尽量精简。它用于控制调用方未提供fields时返回的内容。v-security— 设置allowGetAll: false,并且绝不要在publicRead/publicFilter中公开个人身份信息(PII)。v-triggers— 用于简单的自动化操作(邮件、webhook)。复杂的编排请使用 IO 事件。
MasterDataClient 方法:
| 方法 | 说明 |
|---|---|
getDocument | 按 ID 获取单个文档 |
createDocument | 创建新文档并返回生成的 ID |
createOrUpdateEntireDocument | 完整地创建或更新文档 |
createOrUpdatePartialDocument | 创建或更新部分字段(补丁) |
updateEntireDocument | 替换现有文档的所有字段 |
updatePartialDocument | 仅更新指定字段 |
deleteDocument | 按 ID 删除文档 |
searchDocuments | 使用筛选条件、分页和字段选择进行搜索 |
searchDocumentsWithPaginationInfo | 搜索并返回总数元数据 |
scrollDocuments | 遍历大型结果集 |
where 搜索子句语法:
where: "productId=12345 AND approved=true"
where: "rating>3"
where: "createdAt between 2025-01-01 AND 2025-12-31"
架构:
VTEX IO App (node builder)
│
├── ctx.clients.masterdata.createDocument()
│ │
│ ▼
│ Master Data v2 API
│ │
│ ├── Validates against JSON Schema
│ ├── Indexes declared fields
│ └── Fires triggers (if conditions match)
│ │
│ ▼
│ HTTP webhook / Email / Action
│
└── ctx.clients.masterdata.searchDocuments()
│
▼
Master Data v2 (reads indexed fields for efficient queries)
硬性约束
约束:使用 MasterDataClient — 绝不直接调用 REST
VTEX IO 应用中的所有 Master Data 操作都必须通过 MasterDataClient(ctx.clients.masterdata)或 @vtex/clients 中的 masterDataFor 工厂执行。绝不能直接调用 /api/dataentities/ 端点的 REST API。
为何这很重要
MasterDataClient 会处理身份验证令牌注入、请求路由、重试逻辑、缓存和适当的错误处理。直接调用 REST 会绕过所有这些功能,因此需要手动处理身份验证标头、分页和重试逻辑。VTEX 身份验证令牌格式发生变化时,直接调用会失效,而客户端会透明地处理这些变化。
检测
如果发现直接向匹配 /api/dataentities/、api.vtex.com/api/dataentities 的 URL 发起 HTTP 调用,或使用原始 fetch/axios 调用 Master Data 端点,请提醒开发者改用 ctx.clients.masterdata。
正确
// Using MasterDataClient through ctx.clients
export async function getReview(ctx: Context, next: () => Promise<void>) {
const { id } = ctx.query;
const review = await ctx.clients.masterdata.getDocument<Review>({
dataEntity: "reviews",
id: id as string,
fields: [
"id",
"productId",
"author",
"rating",
"title",
"text",
"approved",
],
});
ctx.status = 200;
ctx.body = review;
await next();
}
错误
// Direct REST call to Master Data — bypasses client infrastructure
import axios from "axios";
export async function getReview(ctx: Context, next: () => Promise<void>) {
const { id } = ctx.query;
// No caching, no retry, no proper auth, no metrics
const response = await axios.get(
`https://api.vtex.com/api/dataentities/reviews/documents/${id}`,
{
headers: {
"X-VTEX-API-AppKey": process.env.VTEX_APP_KEY,
"X-VTEX-API-AppToken": process.env.VTEX_APP_TOKEN,
},
},
);
ctx.status = 200;
ctx.body = response.data;
await next();
}
约束:为所有数据实体定义 JSON Schema
你的应用使用的每个数据实体都必须有相应的 JSON Schema,可以通过 masterdata builder(推荐)定义,也可以在部署应用前通过 Master Data API 创建。
为什么这很重要
如果没有 schema,Master Data 会将文档存储为非结构化 JSON。这意味着没有字段验证、没有索引(在大型数据集上搜索会极其缓慢)、没有类型安全,也不支持触发器。查询未编入索引的字段时会进行全表扫描,可能超时或触及速率限制。
检测
如果应用在某个数据实体中创建或搜索文档,但该实体不存在 JSON Schema(无论是在 masterdata/ builder 目录中,还是通过 API 创建),请提醒开发者定义 schema。
正确
{
"$schema": "http://json-schema.org/schema#",
"title": "review-schema-v1",
"type": "object",
"properties": {
"productId": {
"type": "string"
},
"author": {
"type": "string"
},
"rating": {
"type": "integer",
"minimum": 1,
"maximum": 5
},
"title": {
"type": "string",
"maxLength": 200
},
"text": {
"type": "string",
"maxLength": 5000
},
"approved": {
"type": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": ["productId", "rating", "title", "text"],
"v-default-fields": [
"productId",
"author",
"rating",
"title",
"approved",
"createdAt"
],
"v-indexed": ["productId", "author", "approved", "rating", "createdAt"]
}
错误
// Saving documents without any schema — no validation, no indexing
await ctx.clients.masterdata.createDocument({
dataEntity: "reviews",
fields: {
productId: "12345",
rating: "five", // String instead of number — no validation!
title: 123, // Number instead of string — no validation!
},
});
// Searching on unindexed fields — full table scan, will time out on large datasets
await ctx.clients.masterdata.searchDocuments({
dataEntity: "reviews",
where: "productId=12345", // productId is not indexed — very slow
fields: ["id", "rating"],
pagination: { page: 1, pageSize: 10 },
});
约束:管理 schema 版本,避免达到 60 个 schema 的上限
Master Data v2 数据实体每个最多只能有 60 个 schema。使用 masterdata builder 时,每个关联或安装的应用版本都会创建一个新 schema。你必须定期删除未使用的 schema。
为什么这很重要
达到 60 个 schema 的上限后,masterdata builder 将无法创建新 schema,关联或安装新的应用版本也会失败。这是平台的硬性限制,无法提高。
检测
如果应用经历过多次关联/安装,请提醒开发者使用 Delete Schema API 检查并清理旧 schema。
正确
# Periodically clean up unused schemas
# List schemas for the entity
curl -X GET "https://{account}.vtexcommercestable.com.br/api/dataentities/reviews/schemas" \
-H "X-VTEX-API-AppKey: {appKey}" \
-H "X-VTEX-API-AppToken: {appToken}"
# Delete old schemas that are no longer in use
curl -X DELETE "https://{account}.vtexcommercestable.com.br/api/dataentities/reviews/schemas/old-schema-name" \
-H "X-VTEX-API-AppKey: {appKey}" \
-H "X-VTEX-API-AppToken: {appToken}"
错误
Never cleaning up schemas during development.
After 60 link cycles, the builder fails:
"Error: Maximum number of schemas reached for entity 'reviews'"
The app cannot be linked or installed until old schemas are deleted.
约束:不要将 Master Data 用作日志、缓存或临时存储
用于应用日志记录、缓存(IO 应用状态、查询结果)或临时暂存数据的实体不应存放在 Master Data 中。日志请使用 ctx.vtex.logger,应用专用缓存和临时状态请使用 VBase,审计记录则请使用外部日志聚合服务。
为什么这很重要 — 日志和缓存实体会累积数百万份文档、触及速率限制、导致实体无法用于正常查询,并浪费存储空间。MD 并非为高写入量、高数据量、可丢弃的数据而设计。
检测 — 名称类似于 LOG、cache、temp、staging、debug 的实体,或文档数量会随流量而非业务事件不断增长的实体。
正确
// Logs: use structured logger
ctx.vtex.logger.info({ action: "priceUpdate", skuId, newPrice });
// Cache: use VBase
await ctx.clients.vbase.saveJSON("my-cache", cacheKey, data);
错误
// Using MD as a log store — creates millions of documents
await ctx.clients.masterdata.createDocument({
dataEntity: "appLogs",
fields: {
level: "info",
message: `Price updated for ${skuId}`,
timestamp: new Date(),
},
});
约束:没有合理理由时,不要在 Master Data 中创建并行的事实来源
使用 Master Data 镜像已在 OMS、Catalog 或外部 ERP 中拥有记录系统的数据——例如,为自定义列表视图存储订单标头,或存储本应属于目录规格的 SKU 属性——会造成数据漂移、增加对账成本并带来事故风险。
为什么这很重要
发生部分故障、重试或手动编辑后,两个事实来源会出现不一致。团队会把精力花在同步和调试上,而不是实现客户价值。
检测
新增的 MD 实体字段与 OMS 订单字段重复,理由是“提升性能”,但没有 BFF 缓存方案;产品属性存储在 MD 中,而 Catalog 规格已经足够;或者设置了定时任务,在 MD 与 OMS 数据出现偏差时“修复”MD。
正确
1. Identify the authoritative system (OMS, Catalog, partner API).
2. Read from that source via BFF or IO, with caching (application + HTTP semantics) as needed.
3. Use MD only for data without a native home or after explicit architecture sign-off.
错误
"We store order snapshots in MD so the storefront is faster" while OMS remains canonical
and no reconciliation strategy exists — eventual inconsistency is guaranteed.
推荐模式
添加 masterdata builder 和策略:
{
"builders": {
"node": "7.x",
"graphql": "1.x",
"masterdata": "1.x"
},
"policies": [
{
"name": "outbound-access",
"attrs": {
"host": "api.vtex.com",
"path": "/api/*"
}
},
{
"name": "ADMIN_DS"
}
]
}
定义数据实体 schema:
{
"$schema": "http://json-schema.org/schema#",
"title": "review-schema-v1",
"type": "object",
"properties": {
"productId": {
"type": "string"
},
"author": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
},
"rating": {
"type": "integer",
"minimum": 1,
"maximum": 5
},
"title": {
"type": "string",
"maxLength": 200
},
"text": {
"type": "string",
"maxLength": 5000
},
"approved": {
"type": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": ["productId", "rating", "title", "text"],
"v-default-fields": [
"productId",
"author",
"rating",
"title",
"approved",
"createdAt"
],
"v-indexed": ["productId", "author", "approved", "rating", "createdAt"],
"v-cache": false
}
使用 masterDataFor 设置客户端:
// node/clients/index.ts
import { IOClients } from "@vtex/api";
import { masterDataFor } from "@vtex/clients";
interface Review {
id: string;
productId: string;
author: string;
email: string;
rating: number;
title: string;
text: string;
approved: boolean;
createdAt: string;
}
export class Clients extends IOClients {
public get reviews() {
return this.getOrSet("reviews", masterDataFor<Review>("reviews"));
}
}
实现 CRUD 操作:
// node/resolvers/reviews.ts
import type { ServiceContext } from "@vtex/api";
import type { Clients } from "../clients";
type Context = ServiceContext<Clients>;
export const queries = {
reviews: async (
_root: unknown,
args: { productId: string; page?: number; pageSize?: number },
ctx: Context,
) => {
const { productId, page = 1, pageSize = 10 } = args;
const results = await ctx.clients.reviews.search(
{ page, pageSize },
[
"id",
"productId",
"author",
"rating",
"title",
"text",
"createdAt",
"approved",
],
"", // sort
`productId=${productId} AND approved=true`,
);
return results;
},
};
export const mutations = {
createReview: async (
_root: unknown,
args: {
input: { productId: string; rating: number; title: string; text: string };
},
ctx: Context,
) => {
const { input } = args;
const email = ctx.vtex.storeUserEmail ?? "[email protected]";
const response = await ctx.clients.reviews.save({
...input,
author: email.split("@")[0],
email,
approved: false,
createdAt: new Date().toISOString(),
});
return ctx.clients.reviews.get(response.DocumentId, [
"id",
"productId",
"author",
"rating",
"title",
"text",
"createdAt",
"approved",
]);
},
deleteReview: async (_root: unknown, args: { id: string }, ctx: Context) => {
await ctx.clients.reviews.delete(args.id);
return true;
},
};
配置触发器(可选):
{
"name": "notify-moderator-on-new-review",
"active": true,
"condition": "approved=false",
"action": {
"type": "email",
"provider": "default",
"subject": "New review pending moderation",
"to": ["[email protected]"],
"body": "A new review has been submitted for product {{productId}} by {{author}}."
},
"retry": {
"times": 3,
"delay": { "addMinutes": 5 }
}
}
接入 Service:
// node/index.ts
import type { ParamsContext, RecorderState } from "@vtex/api";
import { Service } from "@vtex/api";
import { Clients } from "./clients";
import { queries, mutations } from "./resolvers/reviews";
export default new Service<Clients, RecorderState, ParamsContext>({
clients: {
implementation: Clients,
options: {
default: {
retries: 2,
timeout: 5000,
},
},
},
graphql: {
resolvers: {
Query: queries,
Mutation: mutations,
},
},
});
常见故障模式
- 直接调用 /api/dataentities/ 的 REST 接口:使用
axios或fetch调用 Master Data 端点会绕过客户端基础设施——没有身份验证、缓存或重试。请改用ctx.clients.masterdata或masterDataFor。 - 搜索时未使用已索引字段:查询未索引字段会触发全量文档扫描。对于大型数据集,这会导致超时和速率限制错误。请确保
where子句中的所有字段都位于架构的v-indexed数组中。 - 搜索结果未分页:Master Data v2 的每页大小上限为 100 个文档。请求超过上限时,只会静默返回不超过上限的结果。请使用正确的分页方式,或对大型结果集使用
scrollDocuments。 - 忽略 60 个架构的限制:每个关联/安装的应用版本都会创建一个新架构。关联循环超过 60 次后,构建器会失败。请定期通过 Delete Schema API 清理未使用的架构。
- 使用 MD 存储日志或缓存:实体随着流量而非业务事件增长。数百万条日志或缓存文档会降低账户的 MD 性能。
- 批量导入时未限制速率:向 MD 并行写入过多数据会触发 429 错误和账户范围的限流。请始终使用有界并发和退避机制。
- 导入时未进行验证:导入数据时若不与目录或用户存储交叉核对,会导致孤立文档、引用失效,以及未能通过架构验证的数据被静默写入。
- 批量操作没有检查点:如果一个包含 5 万个文档的导入任务在第 3 万个文档处失败,没有检查点文件就必须从头开始。
审查清单
- 是否在
manifest.json中声明了masterdata构建器? - 所有数据实体是否都有包含正确字段定义的 JSON Schemas?
-
where子句中的所有字段是否都已在v-indexed中声明? - CRUD 操作是否使用
ctx.clients.masterdata或masterDataFor(没有直接调用 REST 接口)? - 是否正确处理了分页(每页最多 100 个,大型结果集使用 scroll)?
- 是否制定了清理架构的计划,以避免触及 60 个架构的限制?
- 是否在清单中声明了必需的策略(
outbound-access、ADMIN_DS)? - 在为 product 或 order 数据使用 MD 之前,是否已排除或确认使用 Catalog 或原生存储?
- 除非有明确理由,否则 MD 是否已从 购买关键路径中移除?
- 如果要在外部公开 MD,是否通过具有 身份验证的受控 IO/BFF 层进行访问?
- 该实体是否未用于日志记录、缓存或临时数据?
- 对于批量操作,是否已实现速率限制、退避和检查点?
- 在写入 MD 之前,是否根据权威数据源对导入数据进行了验证?
- 是否已识别原生实体(CL、AD、OD 等),并确保自定义实体没有重复存储这些数据?
相关技能
- vtex-io-masterdata-strategy — 何时使用 MD、
v-*架构扩展参考、索引策略、触发器、容量规划 - vtex-io-application-performance — IO 性能模式(缓存层、面向 BFF 的行为)
- vtex-io-service-paths-and-cdn — MD 支持的 API 的公开与私有路由
- vtex-io-session-apps — 可能读取或补充 MD 存储状态的会话转换
- architecture-well-architected-commerce — 跨领域存储与原则对齐
- headless-bff-architecture — 不通过 IO 访问 MD 时的 BFF 边界
参考资料
- 创建 Master Data v2 CRUD 应用 — 使用 masterdata builder 构建 Master Data 应用的完整指南
- 在 Master Data v2 中使用 JSON Schema — Schema 结构、验证和索引配置
- Schema 生命周期 — Schema 如何演进,以及随时间推移对数据实体产生的影响
- 在 Master Data v2 中设置触发器 — 自动化工作流的触发器配置
- Master Data v2 API 参考 — 所有 Master Data v2 端点的完整 API 参考
- 客户端 — MasterDataClient 方法及其在 VTEX IO 中的用法