mcpField
V
vtex-io-masterdata

vtex-io-masterdata

github.com/vtex数据与数据库471.2k 次安装2 周前更新
代码仓库

关于 vtex-io-masterdata

vtex-io-masterdata 是面向 VTEX IO 开发者的技能,涵盖 Master Data v2 存储设计与实现。它介绍基于 Schema 的 CRUD、搜索、触发器和存储选择,并提供治理及避免误用的指导。

主要功能

  1. 01使用 JSON Schema 定义实体
  2. 02通过 MasterDataClient 执行 CRUD
  3. 03选择搜索或滚动方式检索
  4. 04配置索引、缓存、安全和触发器
  5. 05评估 Master Data 与其他存储方案

适用场景

  1. 01规划评论或表单提交数据的存储
  2. 02实现基于 Schema 的 VTEX IO 数据访问
  3. 03设计大型文档集合的检索方式
  4. 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 或外部存储的理由,也请使用此技能。

  • 使用 masterdata builder 定义数据实体和 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 等),并确保自定义实体没有重复存储这些数据?

相关技能

参考资料

评论

支持 Markdown