mcpField
M
medusa-development

medusa-development

github.com/mindrally開発30690 回インストール5 週間前に更新
リポジトリ

medusa-development について

Medusa Development は、ヘッドレスコマースフレームワーク Medusa で機能を開発する人向けのスキルです。データモデル、モジュールサービス、ステップで構成するワークフローを中心に、API、ジョブ、サブスクライバーからの呼び出し方を案内します。

主な機能

  1. 01データモデルとモジュールサービスの指針
  2. 02組み合わせ可能なワークフローステップ
  3. 03ルートやジョブからのワークフロー呼び出し
  4. 04Query によるデータ読み取り
  5. 05管理画面カスタマイズの規約

活用シーン

  1. 01コマースモジュールとデータモデルの追加
  2. 02業務ロジックのワークフロー化
  3. 03ルートやスケジュールジョブからの呼び出し

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mindrally/skills --skill medusa-development

インストール後、Claude Code で/medusa-developmentを入力して読み込まれたか確認します。

詳細

Medusa 開発

Medusa は、モジュール、データモデル、ワークフローを中心に構築されたヘッドレスコマースフレームワークです。API ルートからスケジュールされたジョブまで、ほぼすべてのビジネスロジックを、個別に組み合わせ可能なステップで構成されるワークフローとして表現する必要があります。

Medusa の機能を構築するワークフロー

  1. データモデルを定義または拡張する — @medusajs/framework/utils の model ユーティリティを使用して、src/modules/<module>/models/ の下にモジュールのデータモデルを宣言します。
  2. モジュールサービスを作成する — モジュールにデータモデルがある場合は、src/modules/<module>/service.ts に MedusaService を継承するサービスを作成し、ドメイン操作を行う非同期メソッドを公開します。
  3. モジュールを登録する — Medusa のコンテナで解決できるように、medusa-config.ts にモジュールを追加します。
  4. ステップを構築する — @medusajs/framework/workflows-sdk の createStep を使用して、作業の各単位をステップとして定義します。失敗時に取り消す必要がある処理には、補償関数を含めます。
  5. ワークフローを組み立てる — createWorkflow を使用してステップを連結し、データの整形には transform を、条件分岐には when を使用します。
  6. ワークフローを公開する — API ルート、スケジュールされたジョブ、またはサブスクライバーからワークフローを呼び出します。ルート、ジョブ、サブスクライバーのハンドラーにビジネスロジックを直接記述しないでください。
  7. Query でデータを読み取る — 読み取り時にはモジュールサービスを直接呼び出さず、Medusa の Query(req.scope.resolve("query") またはワークフローレベルの useQueryGraphStep)を使用してデータを取得します。

一般的なルール

  • ファイルをインポートする際に型エイリアスを使用しないでください。ローカルエイリアスを介して再エクスポートするのではなく、型と値をそれぞれのソースモジュールから直接インポートします。
  • エラーをスローする際は、通常の Error ではなく、必ず MedusaError(@medusajs/framework/utils から)をスローしてください。これにより、API レイヤーが適切な HTTP ステータスとエラーコードに対応付けられます。
  • 読み取り時はモジュールのサービスメソッドを直接呼び出さず、常に Query を使用してデータを取得してください。Query はモジュール間のリンクを認識し、1 回の呼び出しでモジュール間のデータを結合できます。
import { MedusaError } from "@medusajs/framework/utils"

if (!product) {
  throw new MedusaError(
    MedusaError.Types.NOT_FOUND,
    `Product with id "${productId}" was not found`
  )
}

データモデルのルール

  • @medusajs/framework/utils の model ユーティリティを使用してデータモデルを定義します。
  • データモデルの変数名はキャメルケースにし、model.define に渡す名前はスネークケースにします。
  • データモデルに id フィールドを追加する場合は、必ず .primaryKey() を指定して主キーにします。
  • データモデルに指定できる id フィールドは 1 つだけです。それ以外の識別子には text フィールドを使用します。
  • データモデルのフィールド名はスネークケースにします。
// src/modules/loyalty/models/loyalty-account.ts
import { model } from "@medusajs/framework/utils"

const LoyaltyAccount = model.define("loyalty_account", {
  id: model.id().primaryKey(),
  customer_id: model.text(),
  points_balance: model.number().default(0),
  tier: model.enum(["bronze", "silver", "gold"]).default("bronze"),
})

export default LoyaltyAccount

サービスのルール

  • サービスを作成する際は、必ずメソッドを非同期にします。
  • モジュールにデータモデルがある場合は、サービスに MedusaService を継承させ、各モデル用に生成される CRUD メソッドを利用できるようにします。
// src/modules/loyalty/service.ts
import { MedusaService } from "@medusajs/framework/utils"
import LoyaltyAccount from "./models/loyalty-account"

class LoyaltyModuleService extends MedusaService({
  LoyaltyAccount,
}) {
  async addPoints(accountId: string, points: number) {
    const account = await this.retrieveLoyaltyAccount(accountId)
    return await this.updateLoyaltyAccounts({
      id: account.id,
      points_balance: account.points_balance + points,
    })
  }
}

export default LoyaltyModuleService

ワークフロールール

  • ワークフローまたはステップを作成する際は、必ず Medusa の Workflow SDK(@medusajs/framework/workflows-sdk)を使用して定義してください。
  • API ルート、スケジュールジョブ、またはサブスクライバーで機能を作成する際は、ハンドラー内にロジックを直接記述せず、必ずワークフローを作成してください。
  • ワークフローを作成する際は、ワークフロー内の個々の作業単位ごとに必ずステップを作成してください。
  • ワークフローでは、ステップ間のデータ変換には必ず transform を使用してください。ワークフロー関数本体でステップの出力を直接操作しないでください。
  • ワークフローでは、ステップ呼び出しを囲む単純な if 文ではなく、条件分岐の定義に when を使用してください。
  • ワークフロー内でステップを呼び出す際に await を使用しないでください。ステップの呼び出しはワークフローエンジンが解決する特別な参照を返します。これを await すると、補償処理や再試行を調整するワークフローの機能が損なわれます。
  • ワークフローでは、ワークフロー関数自体を async にしないでください。関数本体はステップのグラフを記述するだけで、命令型に処理を実行するものではありません。
  • 補償関数の入力に型を指定しないでください。補償関数はステップの invoke 関数が返した値を受け取り、Medusa がこれを自動的に推論します。
  • ワークフロー内ではステップのみを使用してください。ワークフロー関数内でサービス、Query、その他の副作用を伴うコードを直接呼び出さず、そのロジックはステップに記述してください。
// src/workflows/redeem-loyalty-points.ts
import {
  createStep,
  createWorkflow,
  StepResponse,
  transform,
  when,
  WorkflowResponse,
} from "@medusajs/framework/workflows-sdk"
import { MedusaError } from "@medusajs/framework/utils"
import { LOYALTY_MODULE } from "../modules/loyalty"
import LoyaltyModuleService from "../modules/loyalty/service"

type RedeemPointsInput = {
  accountId: string
  points: number
}

const deductPointsStep = createStep(
  "deduct-points-step",
  async (input: RedeemPointsInput, { container }) => {
    const loyaltyService: LoyaltyModuleService = container.resolve(LOYALTY_MODULE)
    const account = await loyaltyService.retrieveLoyaltyAccount(input.accountId)

    if (account.points_balance < input.points) {
      throw new MedusaError(
        MedusaError.Types.INVALID_DATA,
        "Insufficient points balance"
      )
    }

    const previousBalance = account.points_balance
    const updated = await loyaltyService.updateLoyaltyAccounts({
      id: account.id,
      points_balance: previousBalance - input.points,
    })

    return new StepResponse(updated, { accountId: account.id, previousBalance })
  },
  async (compensationInput, { container }) => {
    if (!compensationInput) return
    const loyaltyService: LoyaltyModuleService = container.resolve(LOYALTY_MODULE)
    await loyaltyService.updateLoyaltyAccounts({
      id: compensationInput.accountId,
      points_balance: compensationInput.previousBalance,
    })
  }
)

export const redeemLoyaltyPointsWorkflow = createWorkflow(
  "redeem-loyalty-points",
  (input: RedeemPointsInput) => {
    const account = deductPointsStep(input)

    const tierDowngrade = when(account, (acc) => acc.points_balance === 0)
      .then(() => {
        return transform({ account }, (data) => ({
          ...data.account,
          tier: "bronze" as const,
        }))
      })

    return new WorkflowResponse(account)
  }
)

API ルートとデータの読み取り

  • src/api/ 配下の API ルートを通じてワークフローを公開してください。ルートハンドラーでは入力を検証し、ワークフローを呼び出して、HTTP レスポンスを整形してください。それ以外の処理は行わないでください。
  • 読み取り用のデータ(一覧/詳細エンドポイント)の取得には、モジュールサービスを直接解決するのではなく、必ず Query を使用してください。
// src/api/store/loyalty/[id]/route.ts
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"

export async function GET(req: MedusaRequest, res: MedusaResponse) {
  const query = req.scope.resolve("query")

  const { data: accounts } = await query.graph({
    entity: "loyalty_account",
    fields: ["id", "points_balance", "tier"],
    filters: { id: req.params.id },
  })

  res.json({ loyalty_account: accounts[0] })
}

export async function POST(req: MedusaRequest, res: MedusaResponse) {
  const { result } = await redeemLoyaltyPointsWorkflow(req.scope).run({
    input: req.validatedBody as { accountId: string; points: number },
  })

  res.json({ loyalty_account: result })
}

サブスクライバーとスケジュールジョブ

  • サブスクライバーとスケジュールジョブでは、API ルートとまったく同様にワークフローを呼び出してください。同じビジネスロジックを起動する、別のトリガーにすぎません。
// src/subscribers/order-placed.ts
import type { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
import { redeemLoyaltyPointsWorkflow } from "../workflows/redeem-loyalty-points"

export default async function orderPlacedHandler({ event, container }: SubscriberArgs<{ id: string }>) {
  await redeemLoyaltyPointsWorkflow(container).run({
    input: { accountId: event.data.id, points: 0 },
  })
}

export const config: SubscriberConfig = { event: "order.placed" }

管理画面のカスタマイズルール

  • 管理画面のカスタマイズ(ウィジェット、カスタムページ)からリクエストを送信する際は、生の fetch ではなく、必ず Medusa の JS SDK(@medusajs/js-sdk)を使用してください。
  • 管理画面のカスタマイズのスタイリングには TailwindCSS を使用し、Medusa Admin の UI 自体の規約に合わせてください。
// src/admin/widgets/loyalty-widget.tsx
import { defineWidgetConfig } from "@medusajs/admin-sdk"
import { useQuery } from "@tanstack/react-query"
import { sdk } from "../lib/sdk"

const LoyaltyWidget = ({ data }: { data: { id: string } }) => {
  const { data: result } = useQuery({
    queryFn: () => sdk.admin.customer.retrieve(data.id),
    queryKey: ["customer", data.id],
  })
  return <p className="text-ui-fg-subtle">{result?.customer.email}</p>
}

export const config = defineWidgetConfig({ zone: "customer.details.after" })
export default LoyaltyWidget

よくある間違い

  • ワークフローを通さず、API ルートハンドラーからモジュールサービスを直接呼び出すこと。これにより、ワークフローが提供する再試行、補償処理、標準的なイベント/オブザーバビリティのフックがスキップされます。
  • MedusaError ではなく通常の Error をスローすること。これにより、API レスポンスで対応する HTTP ステータスコードと構造化されたエラータイプが失われます。
  • ワークフロー関数内でステップ呼び出しを await すること。これにより、ワークフローエンジンがステップのグラフを構築できなくなります。
  • Query を使用せず、モジュールサービスを解決してデータを読み取ること。これにより、Query が自動的に解決するモジュール間の結合やリンクを取得できません。
  • データモデルのフィールド名や model.define の名前にスネークケースではなくキャメルケースを使用すること。これにより、スキーマ全体の一貫性が損なわれます。

追加リソース

コメント