关于 commerce-app-storage
面向 Adobe Commerce App Builder 项目的开发技能,指导将数据库存储接入现有 Commerce 应用,并创建读写文档的运行时操作。涵盖数据库配置、认证、安装初始化和验证流程。
主要功能
- 01配置并预置工作区数据库
- 02创建 Web 或事件处理操作
- 03使用集合执行文档 CRUD
- 04安装时创建集合和索引
- 05构建与部署前验证配置
适用场景
- 01为 Commerce 应用保存订单记录
- 02响应事件或 Webhook 并读写数据
- 03安装应用时初始化集合与索引
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ adobe/ aio-commerce-sdk --skill commerce-app-storage安装后在 Claude Code 中输入/commerce-app-storage确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/db-action.tssetup-database.tsevals/evals.json详细介绍
为 Commerce 应用添加数据库存储
将 App Builder Database Storage 集成到现有 Commerce 应用,并生成一个使用 @adobe/aio-lib-db 读取和写入文档的运行时操作。该库类似 MongoDB:数据存储在文档集合中,并可使用熟悉的筛选条件进行查询。
无论操作类型如何,数据库访问代码都完全相同;不同之处在于操作的注册方式以及处理器的返回内容:
- Web 操作——可通过 HTTP 调用(
web: "yes");使用@adobe/aio-commerce-lib-core中的responses辅助函数构建并返回响应。 - 事件/Webhook 操作——由 Commerce 事件或 Webhook 调用;通过
commerce-app-eventing(runtimeActions)或commerce-app-webhooks(runtimeAction)在app.commerce.config.ts中引用。
前置条件
- 确认应用已生成脚手架并完成初始化,不能仅确认配置文件存在。必须同时满足以下两项:
- 项目根目录中存在
app.commerce.config.ts,并且 - 项目已初始化——这可通过已生成的
src/commerce-extensibility-1/目录和已安装的node_modules(@adobe/aio-commerce-lib-app依赖)来确认。
- 项目根目录中存在
- 如果缺少
app.commerce.config.ts,请停止操作,先调用commerce-app-init(它会写入配置文件,然后运行初始化)。 - 如果配置文件存在但项目尚未初始化(没有
src/commerce-extensibility-1/或node_modules),请先运行npx @adobe/aio-commerce-lib-app init再继续。初始化是幂等的——它会找到现有配置,跳过交互式提示,安装依赖并生成项目文件。 - 只有在项目已配置 TypeScript 构建环境(
webpack-config.cjs+ 根目录下的tsconfig.json)后,才能使用 TypeScript 编写操作和自定义安装脚本;对于 TypeScript Commerce 配置,该环境由init生成——参见commerce-app-init。否则,请使用 JavaScript 编写。 - 必须在 Adobe Developer Console 中将 App Builder Data Services API(API 代码
AppBuilderDataServicesSDK)添加到项目中——所有使用数据库的工作区都必须添加(除 App Builder 外无需特殊许可证)。如果未添加,运行时操作将无法通过数据库服务的身份验证。
步骤 1 — 配置工作区数据库
AIO 项目工作区与工作区数据库之间严格一一对应。建议在 app.config.yaml 中以声明式方式配置:执行 aio app deploy 时,如果数据库尚不存在,就会自动配置:
application:
runtimeManifest:
database:
auto-provision: true
region: emea # amer | apac | emea | aus — the single source of truth for the region
仅包含扩展的应用需要变通方案。 由于 aio app CLI 插件中的一个 bug(并非 aio-lib-db 的问题),只有当 application 运行时清单中至少有一个包含运行时操作的包时,aio app deploy 才会执行声明式自动配置。纯粹由 extensions 构建的应用(提交指南推荐的布局)没有 application 操作,因此部署时会悄悄跳过配置。添加一个空的 packages 映射,以及一个用于创建配置步骤所需目录的 post-app-build 钩子,让 application 块“足够完整”,以便运行配置:
application:
hooks:
post-app-build: "mkdir -p dist/application/actions" # provisioning expects this dir to exist
runtimeManifest:
packages: {} # empty map — required by the config schema so the application block validates with no actions
database:
auto-provision: true
region: emea # single source of truth — see the region callout below
对于本地开发,在 aio app run / aio app dev 期间不会执行声明式自动配置。请先使用 CLI 后备方案配置一次(自助操作,无需特殊权限)。只有安装了 存储 CLI 插件后,才能使用 aio app db … 命令:
aio plugins install @adobe/aio-cli-plugin-app-storage
aio app db provision --region <amer|apac|emea|aus>
区域是唯一数据源。 清单
database块中的region必须与每次initDb({ region })调用(或AIO_DB_REGION)传入的region一致——每个操作以及安装步骤(步骤 6)都必须如此。不一致会导致连接失败。更改区域具有破坏性:运行aio app db delete,更新清单中的database.region,然后重新配置。
步骤 2 — 安装库
npm install @adobe/aio-lib-db
第 3 步 — 理解意图
向用户了解:
- 操作类型:Web 操作或事件/Webhook 操作(请参见上面的两种结构)。
- 集合名称和所需的操作(插入 / 查找 / 更新 / 删除)。
- 区域:必须与清单中的
database.region一致(请参见第 1 步中的区域说明)。将其传递给init(),或设置AIO_DB_REGION。
第 4 步 — 注册操作
将操作添加到 src/commerce-extensibility-1/ext.config.yaml 中的用户自定义包(名称可以任意,但不能使用保留名称 app-management)。
每个数据库操作都必须设置
include-ims-credentials: true。 如果不设置,aio-lib-db将没有用于身份验证的 IMS 令牌,运行时连接会失败(如果操作在安装期间运行,应用安装也会失败)。请勿省略此注解。请勿使用
AIO_COMMERCE_AUTH_IMS_*输入手动配置 OAuth Server-to-Server 凭据。 此旧版回退方案已弃用,并计划在未来的主要版本中移除。
# src/commerce-extensibility-1/ext.config.yaml
runtimeManifest:
packages:
app-management:
# ... auto-generated — do not edit
my-app: # any name except "app-management"
actions:
store-record:
function: actions/store-record/index.js # relative to src/commerce-extensibility-1/
runtime: nodejs:24
web: "yes" # "yes" for a web action; "no" for an event/webhook action
annotations:
include-ims-credentials: true # REQUIRED for aio-lib-db auth
| 字段 | 限制 |
|---|---|
| 包名称 | 小写字母数字字符和连字符;不能使用保留名称 app-management |
function | 相对于 src/commerce-extensibility-1/ 的路径 — 不能以 src/... 开头,也不能是相对于根目录的路径 |
include-ims-credentials | 必须为 true — 不设置时,init() 没有 IMS 令牌,连接会失败 |
web | HTTP 可调用的 Web 操作设为 "yes";事件/Webhook 处理程序设为 "no" |
| 集合名称 | 非空字符串;如果集合不存在,则在首次写入时创建 |
| 区域 | 必须与清单中的 database.region 一致(amer | apac | emea | aus) |
第 5 步 — 实现处理程序
每个处理程序都遵循相同的生命周期:解析 IMS 身份验证信息 → 生成令牌 → init → connect → 使用集合 → 始终在 finally 中调用 close。
// Web action — src/commerce-extensibility-1/actions/store-record/index.ts
import { buildErrorResponse, ok } from "@adobe/aio-commerce-lib-core/responses";
import {
getImsAuthProvider,
resolveImsAuthParams,
} from "@adobe/aio-commerce-lib-auth";
import { init as initDb } from "@adobe/aio-lib-db";
export async function main(params: Record<string, unknown>) {
let client;
try {
// Resolve credentials injected by include-ims-credentials, then mint a raw token string.
const authProvider = getImsAuthProvider(resolveImsAuthParams(params));
const token = await authProvider.getAccessToken();
const db = await initDb({ token, region: "emea" }); // must match the manifest database.region
client = await db.connect();
const records = client.collection("records");
const result = await records.insertOne({
...(params.document as object),
createdAt: new Date().toISOString(),
});
return ok({ body: { result } });
} catch (error: any) {
return buildErrorResponse(error.statusCode || 500, {
body: { message: error.message },
});
} finally {
if (client) await client.close(); // always close — avoids connection leaks
}
}
事件和 Webhook 操作都通过 web: "no" 注册,但它们的有效负载结构不同 — 请勿将两者视为相同:
- 事件处理程序(通过
commerce-app-eventing连接):提取的字段位于params.data.value中。params.data还包含_metadata(Commerce 实例元数据)和source(Commerce 事件配置中的商家/环境 ID 对)— 这两者都不是处理程序输入。 - Webhook 处理程序(通过
commerce-app-webhooks连接):没有data包装层 — Commerce 操作的有效负载直接位于params中(例如params.order);响应必须使用@adobe/aio-commerce-lib-webhooks/responses中的操作辅助方法(请参见commerce-app-webhooks),而不能使用上文所示的普通ok/buildErrorResponse结构。
// Event handler — src/commerce-extensibility-1/actions/store-order/index.ts
export async function main(params: Record<string, unknown>) {
const data = params.data as Record<string, unknown>;
const value = data.value as Record<string, unknown>;
let client;
try {
const authProvider = getImsAuthProvider(resolveImsAuthParams(params));
const token = await authProvider.getAccessToken();
const db = await initDb({ token, region: "emea" });
client = await db.connect();
await client
.collection("orders")
.insertOne({ orderId: value.order_id, receivedAt: new Date() });
return ok({ body: { processed: true } });
} finally {
if (client) await client.close();
}
}
// Webhook handler — src/commerce-extensibility-1/actions/log-order/index.ts
import {
ok,
successOperation,
} from "@adobe/aio-commerce-lib-webhooks/responses";
export async function main(params: Record<string, unknown>) {
const order = params.order as Record<string, unknown>; // Commerce operation payload, directly on params
let client;
try {
const authProvider = getImsAuthProvider(resolveImsAuthParams(params));
const token = await authProvider.getAccessToken();
const db = await initDb({ token, region: "emea" });
client = await db.connect();
await client
.collection("orders")
.insertOne({ orderId: order.entity_id, receivedAt: new Date() });
return ok(successOperation());
} finally {
if (client) await client.close();
}
}
请参见 assets/db-action.ts,了解涵盖所有 CRUD 操作和游标迭代的完整注释参考。
Step 6 — 安装时设置集合和索引
对于 App Management 应用,请通过自定义安装步骤创建集合和索引——这是一段从 Commerce Admin 安装应用时运行一次、卸载时可以撤销的脚本。优先使用这种方式,而不是在首次请求时临时创建。
使用 defineCustomInstallationStep 编写该步骤(包含 install 处理程序和可选的 uninstall)。在其中,从 context.params(而非 config)解析 IMS 身份验证参数,然后遵循与 Step 5 相同的 init → connect → close 生命周期,并在集合对象上调用 createIndex:
// ./scripts/setup-database.ts — referenced from config as ./scripts/setup-database.ts
import { defineCustomInstallationStep } from "@adobe/aio-commerce-lib-app/management";
import {
getImsAuthProvider,
resolveImsAuthParams,
} from "@adobe/aio-commerce-lib-auth";
import { init as initDb } from "@adobe/aio-lib-db";
export default defineCustomInstallationStep({
install: async (config, context) => {
let client;
try {
// context.params carries the injected IMS credentials — NOT config.
const authProvider = getImsAuthProvider(
resolveImsAuthParams(context.params),
);
const token = await authProvider.getAccessToken();
const db = await initDb({ token, region: "emea" }); // must match the manifest database.region
client = await db.connect();
const orders = client.collection("held_orders"); // get the collection object first
await orders.createIndex({ order_id: 1 }, { unique: true }); // createIndex on the collection, not a name string
return { status: "success" };
} finally {
if (client) await client.close(); // always close — avoids connection leaks
}
},
uninstall: async (config, context) => {
// Tear down your database state here.
// Leave empty to preserve data across reinstalls.
},
});
请使用
export default将安装脚本编写为 ES 模块——切勿使用module.exports。 安装操作会通过import * as step from "<script>"加载每个步骤,并读取step.default,因此脚本必须默认导出defineCustomInstallationStep(...)的结果。使用 CommonJS 会导致问题:module.exports.default会显示为step.default.default,从而导致验证失败。script路径必须以.js或.ts结尾——可以直接使用 TypeScript 编写,无需单独的编译步骤。
在 app.commerce.config.ts 的 installation.customInstallationSteps 下注册该步骤。script 路径直接指向你的 .ts 文件:
// app.commerce.config.ts
installation: {
customInstallationSteps: [
{
script: "./scripts/setup-database.ts",
name: "Set up held-orders collection",
description: "Creates the held_orders collection and a unique index on order_id",
},
],
},
| 字段 | 限制 |
|---|---|
script | 相对于项目根目录的路径;必须是以 .js 或 .ts 结尾的 ES 模块(export default) |
name | 非空字符串,最多 255 个字符;所有安装步骤中的名称必须唯一 |
description | 非空字符串,最多 255 个字符 |
完整的安装/卸载参考(含注释)请参阅 assets/setup-database.ts。
Step 7 — 重新生成 installation 操作
如果此自定义安装步骤是配置中的第一个需要安装的域(其他域为 webhooks、eventing.commerce、eventing.external、adminUi),请重新运行:
npx @adobe/aio-commerce-lib-app init
此操作会将 installation 操作添加到 ext.config.yaml 的 app-management 包中——aio app build/aio app deploy 只会读取现有文件,不会重新生成该文件。若跳过此步骤,即使脚本已在代码中注册且 DB 操作已部署,脚本也不会运行。即使该操作已存在,也可以安全地重新运行。
Step 8 — 验证
aio app build
构建失败时会直接指出有问题的配置字段。要使用真实数据库测试该操作,请进行部署并调用它(aio app deploy)。如果此步骤需要执行 Step 7,还要检查 ext.config.yaml 中的 app-management 下是否现在包含 actions.installation。
最佳实践
- 始终在
finally块中关闭连接——泄漏的连接会耗尽资源。 - 匹配区域——清单中的
database.region是唯一可信来源;每次init()调用和安装步骤都必须使用该值(参见步骤 1 中的区域提示)。区域不匹配时,连接会静默失败,调用方无法得知。 - 对频繁查询的字段使用投影(
.project({ field: 1 }))和索引(createIndex);索引字段总长度必须 ≤ 2048 字节。 - 使用游标遍历大型结果集(
for await (const doc of collection.find(...))),而不是调用toArray(),以限制内存占用。 - 不要硬编码区域或密钥——优先使用
AIO_DB_REGION和注入的 IMS 令牌,而不是内联值。 - 在运行时操作中,优先使用最具体的 Adobe I/O 库,而不是统包的
@adobe/aio-sdk——例如,使用@adobe/aio-commerce-lib-auth进行 IMS 身份验证,使用@adobe/aio-lib-core-logging作为日志记录器,以缩小操作包的体积。 - 使用自定义安装步骤(
defineCustomInstallationStep,参见步骤 6)在安装期间设置集合和索引,而不是等到首次请求时再临时设置——该步骤会在通过 Commerce Admin 安装应用时运行一次,并可在卸载时撤销。只有当应用不是通过 App Management 安装时,通用的 App Builderpost-app-deploy钩子才是替代方案。
常见问题
-
令牌有效但连接失败:操作缺少
include-ims-credentials: true,或者尚未在 Adobe Developer Console 中将 App Builder Data Services API 添加到项目(请参阅先决条件)。 -
运行
aio app deploy后未预配数据库(仅扩展应用):aio appCLI 插件存在一个错误(与aio-lib-db无关):当application运行时清单没有运行时操作时,会跳过声明式自动预配。请按步骤 1 应用仅扩展应用的解决方法——在application下添加packages: {}和post-app-build: "mkdir -p dist/application/actions"钩子;或者在本地开发时使用 CLI 后备方案预配一次。 -
更改区域后连接失败:库的区域与清单中的
database.region不匹配。更改区域具有破坏性——运行aio app db delete,更新清单中的database.region,然后重新预配(运行aio app deploy,或在本地开发时使用 CLI 后备方案)。 -
使用字符串按
_id查询无结果:先使用bson中的new ObjectId(idString)转换。原始字符串永远无法匹配已存储的ObjectId。 -
DbError与意外错误:服务抛出的错误具有name === "DbError";根据它进行分支处理,以区分数据库故障和应用错误。 -
findOne未匹配时会抛出错误,而不是返回null:与 MongoDB 不同,未匹配并不会成功返回null,而是会抛出消息包含"Document not found"的DbError。仅检查name === "DbError"不足以检测这种情况,因为每个DbError(包括真正的连接失败)都有这个名称;还要检查error.message:function isDocumentNotFoundError(error: unknown) { const e = error as { name?: string; message?: string }; return e.name === "DbError" && e.message?.includes("Document not found"); } let existing; try { existing = await records.findOne({ order_id: orderId }); } catch (error) { if (!isDocumentNotFoundError(error)) { throw error; } }完整参考请参阅 assets/db-action.ts。
-
安装步骤中身份验证失败:使用
resolveImsAuthParams(context.params)从context.params解析注入的 IMS 身份验证参数,而不是从不包含凭据的config中解析。确保操作已标注include-ims-credentials: true;手动配置的AIO_COMMERCE_AUTH_IMS_*凭据已弃用。 -
安装步骤无法加载(
must export a default function or object):脚本是用 CommonJS 编写的。请将其编写为带有export default的 ES 模块;module.exports(或module.exports.default)经框架的import * as加载器处理后会成为.default.default,导致验证失败。 -
createIndex报错或不起作用:必须在集合对象上调用(client.collection("name").createIndex({ field: 1 })),不能使用集合名称字符串调用。先获取集合,再对其调用createIndex。 -
自定义安装步骤已注册并部署,但从未运行:在注册第一个需要安装的域(步骤 7)后没有重新运行
init——没有installation操作,也没有安装端点。
质量标准
aio app build无错误地完成- 每个用户编写的数据库操作都在其注解中声明
include-ims-credentials: true - 操作在
finally块中关闭客户端,并在清单database块声明的区域中初始化库 - 此自定义安装步骤需要
installation操作时,ext.config.yaml中已包含该操作
链接
- 将操作连接到事件——调用
commerce-app-eventing,并在事件的runtimeActions中引用此操作。 - 将操作连接到 Webhook——调用
commerce-app-webhooks,并通过runtimeAction引用此操作。 - 从管理界面触发操作——调用
commerce-app-admin-ui,添加批量操作、订单视图按钮或网格列,以调用此运行时操作。
参考资料
- assets/db-action.ts — 完整注释版处理程序:初始化/连接、CRUD、游标迭代和关闭生命周期
- assets/setup-database.ts — 完整注释版自定义安装步骤:安装时创建集合和唯一索引,卸载时删除集合,并展示
context.params和在集合上调用createIndex的模式