convex-ddd-architecture について
convex-ddd-architecture は、DDD とヘキサゴナルアーキテクチャに基づく Convex プロジェクト構成のリファレンススキルです。規約やパターン、参考資料を通じて、ビジネスルール、永続化、外部連携を分離する方法を示します。
主な機能
- 01DDD とヘキサゴナルアーキテクチャの指針
- 02ドメイン、リポジトリ、アダプターの境界
- 03Convex のスキーマとファイル規約
- 04カスタム関数とトリガーのパターン
- 05移行と命名の参考資料
活用シーン
- 01Convex の新しいサブドメインの構成
- 02既存 Convex コードの DDD への移行
- 03ハンドラーからビジネスルールを分離
- 04Convex プロジェクト構成の標準化
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ sebas5384/ agentic-stuff --skill convex-ddd-architectureインストール後、Claude Code で/convex-ddd-architectureを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/adapters.mdcustom-functions.mddomain-models.mdeslint-rules.mdexamples.mdlearnings.mdmigrations.mdnaming.mdrepositories.mdtriggers.mdvalue-objects.md詳細
Convex DDD アーキテクチャ
DDD とヘキサゴナルアーキテクチャを用いて Convex プロジェクトを整理するためのリファレンススキルです。ドメインロジックをデータベースや外部 API に関する処理から分離し、変更の影響範囲を局所化して、安全に進化させられるようにします。
使用する場面
次のような兆候が1つ以上ある場合に、このスキルを使用してください。
- 新しい Convex サブドメインの設計(
schema、queries、mutations、domain、adapters) - レガシーな Convex コードを DDD/ヘキサゴナルアーキテクチャの境界へ移行する
- ビジネスルールが集約ではなくハンドラーに入り込んでいる
ctx.dbへの直接アクセスがリポジトリの外に広がっている- リトライ、オーケストレーション、変換レイヤーが必要な外部 API 呼び出し
- Convex プロジェクトで一貫したファイル構成と命名規則がチームレベルで必要
- 識別子(変数、関数)の選択またはリファクタリング:エクスポート/モデルではドメインの名詞を使用し、スコープから意味が明らかなモジュール/関数内では短い名前を使用する(naming.md)
アーキテクチャ上の境界よりも速度が重要な小規模なプロトタイプでは、厳密なテンプレートとして使用しないでください。
プロジェクト構成
./convex/
_generated/ # Auto-generated by Convex (do not edit)
_shared/ # Cross-domain utilities
_libs/
aggregate.ts # Base aggregate interface
repository.ts # Base repository interface
_triggers.ts # Central trigger registry
customFunctions.ts # Wrapped mutation/query exports
schema.ts # Composed schema from all sub-domains
[subDomainName]/ # Each sub-domain folder (camelCase)
_libs/
stripeClient.ts # Libs or helpers
_tables.ts # Database schema tables
_triggers.ts # Sub-domain trigger handlers
_seeds.ts # Seeds for models
_workflows.ts # Convex workflows
queries/
[queryName].ts # One query per file, export default
mutations/
[mutationName].ts # One mutation per file, export default
domain/
[modelName].model.ts # Model schema, types, Aggregate
[modelName].repository.ts # Repository interface
adapters/
[actionName].action.ts # External API actions
[modelName].repository.ts # Repository implementation
命名規則
- ファイル:camelCase を使用します(
contactRepository.ts、sendInvoice.action.ts) - アンダースコアのプレフィックス:ドメイン以外のファイルに使用します(
_tables.ts、_triggers.ts) - ディレクトリとファイル:まずファイルから始め(例:
_workflows.ts)、規模が大きくなったらディレクトリに分割します - 識別子:エクスポート、モデル、API ではドメイン用語を使用します。関数内では、親の名前/型から文脈がすでに明らかな場合、短い名詞を優先します。ローカル名すべてにファイル名や親の名前を繰り返さないでください。詳細:naming.md。
クイックリファレンス
| 懸念事項 | ルール |
|---|---|
| Convex のインポート | customFunctions.ts から mutation、query、internalMutation をインポートします |
| 関数のエクスポート | 1ファイルにつき1つの関数を export default でエクスポートします |
| ドメインモデルの形状 | _id、_creationTime、および withoutSystemFields を使ってシステムフィールドを除いた New<Model> を含めます |
| 永続化の境界 | adapters/ のリポジトリを通じてデータベースにアクセスします |
| 外部連携 | 変換処理はアクションに置き、ビジネス上の判断はミューテーション/集約に置きます |
_tables.ts | defineTable(NewXModel.fields) とインデックスのみを記述します。フィールド定義はドメインモデルのファイルに置き、インラインで記述しません |
| スキーマ | 各サブドメインの _tables エクスポートからルートスキーマを構成します |
中核となるパターン
1) カスタム関数の境界
_generated/server ではなく、必ず customFunctions.ts から mutation、query、internalMutation をインポートしてください。custom-functions.md を参照してください。
// ✅ Correct
import { mutation } from "../../customFunctions";
// ❌ Wrong - bypasses trigger integration
import { mutation } from "../../_generated/server";
2) API パスの規約
名前付き定義を持つ関数を1ファイルにつき1つ作成し、デフォルトエクスポートします。
// convex/combat/mutations/createBattle.ts
import { mutation } from "../../customFunctions";
import { v } from "convex/values";
const createBattle = mutation({
args: { heroId: v.id("heroProfiles") },
handler: async (ctx, args) => {
// ...
},
});
export default createBattle;
.default サフィックスを使うフロントエンドでの使用方法:
import { api } from "@/convex/_generated/api";
useMutation(api.combat.mutations.createBattle.default);
useQuery(api.economy.queries.getHeroProfile.default);
export const createBattle のような名前付きエクスポートは避けてください。api.combat.mutations.createBattle.createBattle のような冗長なパスが生成されます。
3) スキーマの構成
サブドメインのテーブルからスキーマを構成します。
// convex/schema.ts
import { defineSchema } from "convex/server";
import { combatTables } from "./combat/_tables";
import { economyTables } from "./economy/_tables";
export default defineSchema({
...combatTables,
...economyTables,
});
4) ドメイン、リポジトリ、アダプターの役割
- ドメインモデルと集約で不変条件を定義します(domain-models.md)
- リポジトリで永続化ロジックを分離します(repositories.md)
- アクションで外部 DTO を変換し、ビジネス上の状態遷移のためにミューテーションを呼び出します(adapters.md)
- トリガーとワークフローで、副作用を確実にオーケストレーションします(triggers.md)
5) ワークフローとトリガーの安全性
- 一方向のフローを優先します:UI ミューテーション → スケジュール済みアクション/ワークフロー → ミューテーション → リアクティブクエリ
- トリガーハンドラーは軽量に保ち、可能であれば非同期処理をスケジュールします
- トリガーコードはトランザクションの影響を受けるものとして扱います
よくある間違い
- スコープを絞ったり、より小さな単位を抽出したりせず、ファイルや関数のコンテキストを繰り返す冗長なローカル変数(naming.md)
_generated/serverからハンドラーを直接インポートし、共有ラッパーを迂回する- 集約ではなく、アクションやハンドラーにビジネスルールを記述する
- 集約の状態遷移ではなく、その場限りのフィールド変更でレコードを更新する
- 集約の振る舞いが期待される箇所で、生のレコードを返す
- 段階的な移行戦略を立てずに、必須のスキーマフィールドを追加する(migrations.md)
defineTable(NewXModel.fields)を使わずに、_tables.ts内でフィールドを直接定義する — これはスキーマを重複させ、モデルを唯一の信頼できる情報源とする不変条件を破ります- 新しいサブドメインを始める際に、既存の(DDD に準拠しているとは限らない)モジュールを参考にする — 常に
examples.mdからパターンを導き出してください。これらのガイドラインより前に作られた可能性のある隣接コードを参考にしないでください
参考資料
補足資料は references/ ディレクトリにあります。
新しいサブドメインのコードを書く前に読むこと:
- examples.md — インボイスドメインと RPG ゲームの完全な実例です。モデル →
_tables.ts→ リポジトリインターフェース → アダプター → ミューテーションの全スタックを示しています。まずここから読んでください。 - learnings.md — 移行から得られた重要なルールをまとめています。
_tables.tsの落とし穴、DTO の配置、段階的なリファクタリング戦略を扱います。
タスクが該当する項目に関わる場合に読むこと:
- domain-models.md —
*Modelバリデーターの形式、withoutSystemFields、Aggregate クラスの構造と状態遷移 - repositories.md —
createRepositoryファクトリー、インターフェースの規約、メソッドの命名(by、list、with) - adapters.md — 外部 API アダプターの構造、DTO の変換境界
- triggers.md — トリガーの登録、安全規則、副作用のスケジューリング
- migrations.md — スキーマ変更戦略、拡張・移行・縮小
- custom-functions.md — 関数ラッパーの詳細と認証のバリエーション
- naming.md — 識別子の規約、ユビキタス言語
- value-objects.md — 不変な値オブジェクトのパターン(
_idなし、静的カタログ) - eslint-rules.md — インポート境界に関する強制適用の lint ルール