commerce-app-storage について
既存の Adobe Commerce アプリに App Builder Database Storage を追加するスキルです。開発者向けに、データベースのプロビジョニング、ランタイムアクションの実装、インストール時のセットアップを案内します。
主な機能
- 01ワークスペースデータベースの構成
- 02Web・イベントアクションのパターン
- 03ドキュメントの CRUD 操作
- 04インストール時のコレクションとインデックス作成
- 05認証とリージョンの設定ガイド
活用シーン
- 01Commerce アプリのデータをドキュメントとして保存
- 02データベースを使うアクションで HTTP リクエストを処理
- 03Commerce イベントや Webhook の処理
- 04アプリのインストール時にコレクションとインデックスを準備
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ adobe/ skills --skill commerce-app-storageインストール後、Claude Code で/commerce-app-storageを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/db-action.tssetup-database.tsevals/evals.json詳細
コマースアプリにデータベースストレージを追加する
App Builder Database Storage を既存のコマースアプリに統合し、@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を実行してください(設定ファイルを書き込み、その後 init を実行します)。- 設定ファイルが存在するが、プロジェクトが初期化されていない場合(
src/commerce-extensibility-1/またはnode_modulesがない場合)は、続行する前にnpx @adobe/aio-commerce-lib-app initを実行してください。init はべき等です。既存の設定ファイルを検出して対話型プロンプトをスキップし、依存関係をインストールしてプロジェクトファイルを生成します。 - アクションとカスタムインストールスクリプトを TypeScript で記述できるのは、プロジェクトに TypeScript のビルド設定(
webpack-config.cjsとルートのtsconfig.json)がある場合のみです。この設定は、TypeScript の Commerce 設定向けにinitがスキャフォールドします。詳細はcommerce-app-initを参照してください。それ以外の場合は JavaScript で記述してください。 - App Builder Data Services API(API コード
AppBuilderDataServicesSDK)を、データベースを使用するすべてのワークスペースで、Adobe Developer Console を通じてプロジェクトに追加する必要があります(App Builder 以外の特別なライセンスは不要です)。これがないと、ランタイムアクションはデータベースサービスに認証できません。
ステップ 1 — ワークスペースのデータベースをプロビジョニングする
AIO プロジェクトのワークスペースとワークスペースデータベースには、厳密な 1 対 1 の関係があります。プロビジョニングには、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-lib-db ではなく aio app CLI プラグインのバグにより、aio app deploy は、application ランタイムマニフェストにランタイムアクションを含むパッケージが少なくとも 1 つある場合にのみ、宣言的な自動プロビジョニングを実行します。extensions のみで構築されたアプリ(提出ガイドラインで推奨されるレイアウト)には application アクションがないため、デプロイ時にプロビジョニングが気づかれないままスキップされます。空のパッケージマップと、プロビジョニングの手順で必要なディレクトリを作成する 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 のフォールバックを使って、事前に一度プロビジョニングしてください(セルフサービスで、特別な権限は不要です)。aio app db … コマンドは、storage CLI plugin をインストールした後にのみ使用できます。
aio plugins install @adobe/aio-cli-plugin-app-storage
aio app db provision --region <amer|apac|emea|aus>
リージョンの情報源は 1 か所に統一してください。 マニフェストの
databaseブロックにあるregionは、すべてのアクションとインストール手順(ステップ 6)で、各initDb({ region })呼び出し(またはAIO_DB_REGION)に渡すregionと一致している必要があります。一致しない場合、接続に失敗します。リージョンを変更するとデータは破壊的に削除されます。aio app db deleteを実行し、マニフェストのdatabase.regionを更新してから、再プロビジョニングしてください。
ステップ 2 — ライブラリをインストールする
npm install @adobe/aio-lib-db
ステップ 3 — 意図を把握する
ユーザーから以下の情報を収集します。
- アクションの種類:Web アクションまたはイベント/Webhook アクション(上記の 2 つの形式を参照)。
- 必要なコレクション名と操作(insert/find/update/delete)。
- リージョン:マニフェストの
database.regionと一致している必要があります(ステップ 1 のリージョンに関する注意を参照)。init()に渡すか、AIO_DB_REGIONを設定してください。
ステップ 4 — アクションを登録する
src/commerce-extensibility-1/ext.config.yaml 内のユーザー定義パッケージにアクションを追加します(予約済みの app-management 以外であれば、任意の名前を使用できます)。
**すべての DB アクションで
include-ims-credentials: trueが必須です。**これがないと、aio-lib-dbは認証に必要な IMS トークンを取得できず、実行時に接続に失敗します(インストール中にアクションが実行される場合は、アプリのインストールも失敗します)。このアノテーションを省略しないでください。
# 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 the injected AIO_COMMERCE_AUTH_IMS_* params, 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)。応答には、上記で使用した通常のok/buildErrorResponse形式ではなく、@adobe/aio-commerce-lib-webhooks/responsesの操作ヘルパーを使用する必要があります(commerce-app-webhooksを参照)。
// 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();
}
}
すべての CRUD 操作とカーソルの反復処理を網羅した、注釈付きの完全なリファレンスについては、assets/db-action.ts を参照してください。
ステップ 6 — インストール時にコレクションとインデックスを設定する
App Management アプリでは、カスタムインストールステップ(Commerce Admin からアプリをインストールしたときに一度だけ実行され、アンインストール時に元に戻すことができるスクリプト)を使ってコレクションとインデックスを作成します。最初のリクエスト時にその場で作成する方法より、こちらを優先してください。
defineCustomInstallationStep(install ハンドラーと、任意の uninstall ハンドラー)を使ってステップを作成します。内部では、IMS 認証パラメーターを config ではなく context.params から取得し、ステップ 5 と同じ初期化 → 接続 → 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 | プロジェクトルートからの相対パス。ES モジュール(export default)であり、.js または .ts で終わる必要があります |
name | 空でない文字列、255 文字以下。すべてのインストールステップで一意である必要があります |
description | 空でない文字列、255 文字以下 |
インストールおよびアンインストールの詳細なリファレンスは、assets/setup-database.ts を参照してください。
ステップ 7 — installation アクションを再生成する
このカスタムインストールステップが、設定内でインストールを必要とする最初のドメインである場合(その他は webhooks、eventing.commerce、eventing.external、adminUi です)、次を再実行します。
npx @adobe/aio-commerce-lib-app init
これにより、ext.config.yaml の app-management パッケージに installation アクションが追加されます。aio app build/aio app deploy は既存のファイルを読み取るだけで、再生成はしません。この手順を省略すると、コードに登録され、DB アクションがデプロイされていても、スクリプトは実行されません。アクションがすでに存在する場合でも、安全に再実行できます。
ステップ 8 — 検証する
aio app build
ビルドに失敗した場合は、問題のある設定フィールドが直接示されます。実際のデータベースに対してアクションを実行するには、デプロイして呼び出します(aio app deploy)。このステップでステップ 7 が必要だった場合は、ext.config.yaml の app-management 配下に actions.installation が追加されていることも確認してください。
ベストプラクティス
finallyブロックで必ず接続を閉じてください。接続がリークすると、リソースが枯渇します。- リージョンを一致させてください。マニフェストの
database.regionが唯一の信頼できる情報源です。すべてのinit()呼び出しとインストール手順でその値を使用する必要があります(ステップ 1 のリージョンに関する注記を参照)。値が一致しない場合、呼び出し元からは分からないまま接続に失敗します。 - 頻繁にクエリするフィールドには、プロジェクション(
.project({ field: 1 }))とインデックス(createIndex)を使用してください。インデックスのフィールドは合計 2048 バイト以下にする必要があります。 toArray()ではなく、カーソルを使って大きな結果セットを反復処理してください(for await (const doc of collection.find(...)))。メモリ使用量を抑えられます。- リージョンやシークレットをハードコードしないでください。インライン値ではなく、
AIO_DB_REGIONと注入された IMS トークンを使用してください。 - ランタイムアクションでは、
@adobe/aio-sdkという包括的なパッケージよりも、最も用途に特化した Adobe I/O ライブラリを優先してください。たとえば、IMS 認証には@adobe/aio-commerce-lib-auth、ロガーには@adobe/aio-lib-core-loggingを使用すると、アクションバンドルを小さく保てます。 - 初回リクエスト時に場当たり的に設定するのではなく、カスタムインストール手順(
defineCustomInstallationStep。ステップ 6 を参照)を使って、インストール時にコレクションとインデックスを設定してください。この手順は Commerce Admin からアプリをインストールしたときに一度だけ実行され、アンインストール時には元に戻せます。汎用の App Builderpost-app-deployフックは、アプリを App Management 経由でインストールしない場合に限り、代替手段として使用できます。
よくある問題
-
有効なトークンがあるにもかかわらず接続に失敗する:アクションに
include-ims-credentials: trueが設定されていないか、Adobe Developer Console でプロジェクトに App Builder Data Services API が追加されていません(前提条件を参照してください)。 -
aio app deploy後に DB がプロビジョニングされない(拡張機能のみのアプリ):applicationランタイムマニフェストにランタイムアクションがない場合、aio appCLI プラグインのバグにより、宣言的な自動プロビジョニングがスキップされます(aio-lib-dbのバグではありません)。手順 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がスローされます。すべてのDbError(実際の接続障害も含む)がこの名前になるため、name === "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 を参照してください。
-
インストール手順内で認証に失敗する:認証情報がない
configではなく、注入されたAIO_COMMERCE_AUTH_IMS_*認証情報を持つcontext.paramsから IMS 認証パラメーター(resolveImsAuthParams(context.params))を解決してください。@adobe/aio-lib-core-authではなく、@adobe/aio-commerce-lib-authを使用してください。前者のgenerateAccessTokenはclientId/clientSecretを直接渡すことを想定しており、注入されたパラメーターを使用できません。 -
インストール手順の読み込みに失敗する(
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がエラーなく完了する- ユーザーが作成したすべての DB アクションのアノテーションで
include-ims-credentials: trueが宣言されている - アクションが
finallyブロックでクライアントを閉じ、マニフェストのdatabaseブロックで宣言されたリージョンでライブラリを初期化する - このカスタムインストール手順で必要な場合、
ext.config.yamlにinstallationアクションが存在する
連携
- アクションをイベントに接続する —
commerce-app-eventingを呼び出し、イベントのruntimeActionsでこのアクションを参照します。 - アクションを Webhook に接続する —
commerce-app-webhooksを呼び出し、runtimeActionでこのアクションを参照します。 - Admin UI からアクションをトリガーする —
commerce-app-admin-uiを呼び出して、このランタイムアクションを呼び出す一括アクション、注文ビューのボタン、またはグリッド列を追加します。
参考資料
- assets/db-action.ts — 注釈付きハンドラーの完全版:初期化/接続、CRUD、カーソルの反復処理、クローズのライフサイクル
- assets/setup-database.ts — 注釈付きカスタムインストール手順の完全版:インストール時にコレクションと一意インデックスを作成し、アンインストール時に削除します。
context.paramsとコレクション上のcreateIndexパターンも含まれています