プリンシパル駆動ドメイン設計
著者: SigmaStudiozのLex
パス、名前、契約、依存関係からビジネスの所有権を明確にします。 正しい動作、読みやすい変更、測定可能なパフォーマンス、経済的な進化のために最適化します。 このスキルを要求された変更に適用します。これはプロジェクトを再設計するための許可ではありません。
自動実行
本番コードの計画または編集を行う前に、このベースラインを適用します。スキルの呼び出し、トリガーフレーズ、またはアーキテクチャ要求を待たないでください。単に提案するだけでなく、コーディングタスクの一部として、承認された実装、再利用、ギャップ修正、検証、ハンドオフを実行します。レビューのみ、または計画のみのタスクについては、コードを編集せずに同じ推論を適用します。作業をスコープとリスクに比例させてください。自動使用は、無関係な変更や委任を許可しません。
エージェントのポータビリティとハンドオフ
これらは、Markdownを読み取りプロジェクトを検査できるエージェント向けのツールに依存しない指示です。利用可能な検索、ビルド、テスト、ランタイムツールを使用します。特定のベンダー、プラグイン、CLIは必要ありません。ホストの指示階層、権限、明示的なタスクスコープを尊重します。このスキルは、無効な機能を有効にしたり、デプロイ、公開、破壊的な変更を許可したりしません。
委任がすでに承認されている場合、割り当てにスキルのアクセス可能なパスまたは関連する指示、所有するファイル/モジュール、受け入れ条件、トレース義務を含めます。子エージェントが親の会話、ロードされたスキル、またはグローバル指示ファイルを継承することを想定しないでください。各ワーカーから証拠と未解決のエッジを要求し、ワーカー境界を越えてトレースを調整します。検証された不変条件を委任し、提案されたアルゴリズムを仮定としてラベル付けし、矛盾に挑戦します。子がスキルを読めない場合、許可されたコンテキスト内で関連する内容を提供します。このセクションでハンドオフについて説明しているからといって、エージェントを生成しないでください。別のワークフローを採用する前に、ユーザーが承認したチームメイトの数と役割を記録します。すべての探索、実装、レビューエージェントはその予算を消費します。予算が尽きた場合、フォローアップレビューはコーディネーターに留まります。オプションのワークフロースキルは、ユーザーの制限を拡張できません。タスクブリーフと擬似コードは提案として扱います。それらのアルゴリズムを元の契約に対して独立してチェックします。欠陥のある計画を実装要件として維持するのではなく、修正します。
ハンドオフ契約
承認されたエージェント割り当てまたは再開可能なハンドオフは、以下を特定する必要があります。
| フィールド | 必要なコンテキスト |
|---|---|
| ゴールと制限 | ユーザー成果、受け入れチェック、承認されたアクション、除外事項 |
| 場所 | アクセス可能なワークスペースルート、実際のブランチ/ワークツリーまたはリビジョン、関連する指示/スキルパス |
| 所有権 | エージェントが所有する正確なファイル/モジュール、他のアクティブな所有者と共有インターフェースの依存関係 |
| アーキテクチャ | ドメイン所有者、公開エントリポイント/契約、重要な呼び出し元とコンシューマのパス |
| 現在の状態 | 実装済みと保留中の作業、ローカル編集、決定とその理由、既知の障害 |
| 証拠 | コマンドと結果、トレース元帳のパス/ID、検証済みリビジョン、利用できない環境 |
| 次のアクション | 具体的な次のステップ、ブロッカー、必要な入力、期待される戻り形式 |
ホストは、委任ガードを使用してこの契約を強制できます。
オプションのClaudeガードは、このベースラインを提供し、ネイティブのユーザーターン内で明示的な数値のエージェント制限(Use N teammates/agents/workers)をチェックします。履歴がない場合、強制はコーディネーターに委ねられます。ツールを付与したり、意味的な正確性を保証したりしません。
そのガードがインストールされている場合、割り当ての前に HANDOFF_JSON= と、goal、limits、workspace、revision、owns、interfaces、state、evidence、next、return、trace、checks、baseline を含むJSONオブジェクトをプレフィックスとして付けます。return は、空でない paths、contracts、checks、gaps、remaining の指示を持つオブジェクトである必要があります。trace は追跡するパスと障害ケースに名前を付けます。checks は実行するチェックまたはその理由を名前付けます。baseline はこのスキルのアクセス可能なパスです。これは、探索およびレビュー割り当てにも適用されます。拒否された割り当ては、再試行する前に修正する必要があります。
受信側のエージェントのホスト上の実際のパスを使用します。パスが異なるかアクセスできない場合は、必要なコンテンツを含めます。コマンドに作業ディレクトリと必要な非秘密の設定名を指定します。ハンドオフノートに資格情報やプライベートランタイムペイロードを決して配置しないでください。ブランチ名だけでは、子がコミットされていない作業を認識していることの証明にはなりません。チェックアウトが共有されているか分離されているかを確認します。
同時実行作業の場合、重複する変更の前に公開契約とファイル所有権に同意します。エージェントに自分だけではないことを伝えます。他の人の編集を保持し、割り当てられた所有者を通じて共有ファイルを調整します。統合を主張する前に、分離された変更をリベースまたは調整します。黙って作業を破棄しないでください。戻ってきたら、変更されたパス、公開契約の変更、結果を含むチェック、ギャップID/ステータス、残りのアクションを報告します。コーディネーターはトレース元帳を調整し、エージェント間の継ぎ目と影響を受けるコンシューマを検証します。いくつかのワーカーチェックが成功しても、動作する統合ワークフローが確立されたことにはなりません。
中断またはコンテキスト損失の場合、既存のタスク/トレースレコードにタイムスタンプとコンテンツリビジョンを含むハンドオフを保存し、そのレコードにリンクします。再開時には、行動する前にチェックアウトと現在の差分を検査します。古くなった前提を再検証します。タスクの真実の2番目のソースを作成しないでください。
ローカルインストールは、クラウド、リモートホスト、または無関係なエージェントへのインストールを意味しません。各実行ホストでディスカバリを確立します。ホストネイティブのグローバル指示を使用して、関連する作業をここにルーティングします。ディスカバリまたはファイルアクセスが利用できない場合、その制限を報告し、グローバルな強制を主張しないでください。
プリンシパルエンジニアリング契約
- ビジネス機能、不変条件、ユーザーワークフロー、運用制約から開始します。
- ドメイン内の高い凝集度とドメイン間の低い結合度を優先します。
- ドメイン固有のコードはその所有者と一緒に配置します。その境界内でコードを機能ごとにグループ化します。
- 各ビジネスルールと信頼できる書き込みモデルに、1つの明確な所有者を設定します。
- 別の実装を追加する前に既存の実装を再利用します。重複するビジネス知識を避けます。
- 実証されたニーズを満たし、可能性のある変更をサポートする最小のアーキテクチャを選択します。
- 観察、仮定、決定、証拠を分離します。要件やベンチマークをでっち上げないでください。
- 動作の変更が要求されない限り、構造変更中に動作する公開動作を維持します。
- 触れたパスとその必要な依存関係を改善します。無関係なコードをタスクに巻き込まないでください。
1. 編集前にコンテキストを確立する
該当するリポジトリの指示、マニフェスト、パッケージエクスポート、エントリポイント、既存のドメインモジュール、マイグレーション、テスト、関連するランタイム証拠を検査します。同等の動作と既存の共有パッケージをファイルを作成する前に検索します。リポジトリの言語とツールチェーンを使用します。
ユーザーまたはイベント入力から、認可、サービス、ドメインルール、永続化、下流への影響、返されるか表示される状態に至るまで、1つの関連するワークフローをトレースします。エラーパスを含めます。実際のコンシューマとライターを特定します。ディレクトリ名だけでは所有権を確立できません。
実質的なアーキテクチャ作業については、コンパクトな決定フレームを確立します。
| 質問 | 必要な回答 |
|---|---|
| ビジネス成果 | どのユーザーワークフローまたは不変条件が変更されますか? |
| 所有権 | どの境界付けられたコンテキストが意味と信頼できる書き込みを所有しますか? |
| コンシューマ | どのアプリ、ジョブ、モジュール、または外部システムが機能を必要としますか? |
| 制約 | 互換性、データ量、レイテンシ、テナンシ、可用性、コスト、デプロイ? |
| 現在の証拠 | 関連するパス、契約、障害、タイミング、またはクエリ動作? |
| 最小の変更 | 何を再利用でき、実際に変更する必要がある境界はどこですか? |
| 完了 | 観察可能な受け入れ条件と適切な検証? |
未解決のビジネス上の決定が正しい作業を妨げる場合にのみ質問し、それ以外の場合は仮定を述べます。
2. ドメイン境界を定義する
境界付けられたコンテキストは、一貫したビジネス語彙、ルール、モデルを所有します。フィーチャー、テーブル、画面、フレームワークレイヤー、デプロイ、またはチーム名は、自動的に境界付けられたコンテキストになるわけではありません。billing、scheduling、playback、execution などのビジネス機能名を使用します。
- 同じ用語が異なる意味、不変条件、ライフサイクル、または所有者を持つ場合、コンテキストを分離します。
- 不変条件を1つのアトミックな操作で強制する必要がある場合、エンティティを一緒に保ちます。
- 過度に大きな集約を避けます。真のアトミックな不変条件と、共有トランザクションではなく明示的な整合性、オーケストレーション、または補償を必要とするクロスコンテキストワークフローを区別します。
- コアビジネスの差別化要因と、サポートおよびコモディティ機能を区別します。
- 複雑さやビジネス価値がそれを正当化する場合にモデリングに投資します。単純なCRUDは単純に保ちます。
- 重要なコンテキスト境界で上流/下流の所有権と変換を記録します。
- 別のコンテキストをその公開API、ポート、または契約を通じて消費します。内部をインポートしないでください。
- 外部/プロバイダーモデルは、ビジネスルールに漏洩させるのではなく、境界で変換します。
- 独立したデプロイ、スケーリング、障害分離、または所有権がサービス分割を正当化するまで、モジュラーモノリスを優先します。モノレポはマイクロサービスを意味しません。
変更がドメインにまたがる場合、オーケストレーションするユースケースと各ドメインの責任を特定します。オーケストレーションはワークフローの所有者に配置し、グローバルヘルパーやUIコンポーネントには配置しません。
3. ドメインごと、次に関数ごとに構造化する
最初に所有スコープを選択し、次に機能カテゴリを選択します。そのスコープ内で:
| カテゴリ | 責任 |
|---|---|
model/ | エンティティ、値オブジェクト、不変条件、ポリシー、ドメインイベント |
services/ | 名前付きアプリケーションユースケースとオーケストレーション |
ports/ | 永続化、クロック、アイデンティティ、キュー、プロバイダーのインターフェース |
adapters/ | 具体的なデータベース、トランスポート、プロバイダー、またはプラットフォームの実装 |
contracts/ | 公開DTO、イベントスキーマ、バリデーション、互換性契約 |
components/、hooks/ | そのドメインがフロントエンドを所有する場合のドメインUIとUI動作 |
shared/ | そのスコープ内のコンシューマ向けの再利用可能なコード |
tests/ またはコロケートされたテスト | リポジトリの規則を使用した、動作と境界の検証 |
サービスの実装は、所有するレイヤーの services/ に属します。エンティティにまたがるルールのためにドメインサービスが必要な場合、model/services/ に配置します。オーケストレーションはドメインのメイン services/ に残ります。プロバイダーサービスは、有用な場合 adapters/services/ に配置できます。
1つのアプリの場合、必要に応じて src/domains/<domain>/{model,services,ports,adapters,contracts,shared} を使用し、公開エントリポイント、薄いフレームワークルート、コンポジションルートを作成します。ドメインに依存しないプリミティブは src/shared/ に配置します。実際のコードを含むディレクトリのみを作成します。
モノレポの例。既存のパッケージ規則と明示的なエクスポートを尊重します:
apps/
web/ # アプリのコンポジションとプレゼンテーション
api/ # 薄いトランスポートエントリポイント
worker/ # ジョブ登録とコンポジション
packages/
billing/ # 所有ワークスペースパッケージ
package.json # 公開エクスポートとランタイム互換性
src/
services/ # API/ワーカーで共有されるユースケース
shared/ # 請求ローカルの再利用
index.ts # 明示的な公開ファサード
shared/
contracts/ # ドメインに依存しないワークスペースパッケージ
ui/ # 互換性のあるフロントエンドコンシューマ
実際のパッケージをワークスペースに登録します。パスだけではインポート可能になりません。 ポリグロットワークスペースでは、正規のスキーマから互換性のあるライブラリまたは生成されたクライアントを公開します。互換性のないランタイム間でコードをインポートしようとしないでください。
配置ルール
- 1つのコンシューマとドメイン固有の意味:そのドメインに保持します。
- 1つのドメイン内の複数のコンシューマ:そのドメインの
services/またはshared/を使用します。 - 複数の互換性のあるアプリが再利用可能なドメインコードを必要とする場合:ワークスペースのドメインパッケージに配置します。特権サーバー動作は、承認されたAPIまたはクライアント契約の背後にあるサーバー側に残ります。
- 複数のドメインが同じドメインに依存しないプリミティブを必要とする場合:スコープされた
shared/コードを使用します。 - コンシューマが異なるビジネス上の意味を必要とする場合:別々のモデルを維持し、明示的に変換します。
- クロスドメインワークフロー:所有アプリケーションサービスと公開ドメイン契約を使用します。
- ルート、画面、コントローラー、ジョブ:ルールをサービスに委任し、トランスポートの関心事をローカルに保ちます。
インポートを便利にするためだけに、機能を複数のアプリにコピーしないでください。あるアプリを別のアプリのライブラリにしないでください。再利用可能な所有権を、両方が依存できるパッケージに下げます。必要なフレームワークディレクトリを維持します。フレームワークと戦うのではなく、それらをドメイン構造にルーティングします。言語標準のパッケージングと有効な既存のアーキテクチャを尊重します。
4. 依存関係の方向を強制する
エントリポイント -> 公開アプリケーションサービス -> モデル + ポート
アダプター -> ポート + モデル
ブートストラップ -> サービス + アダプター # 具体的な実装を配線
ドメインパッケージ -> ドメインに依存しない共有パッケージ
- モデルはHTTP、UIフレームワーク、データベース、キュー、プロバイダーSDKに依存しません。
- サービスは明示的な依存関係を通じてユースケースをオーケストレーションします。アダプターはポートを実装します。
- 共有パッケージはアプリやドメイン内部をインポートしません。循環依存関係を避けます。
- クロスドメイン呼び出しには公開契約を使用します。同期呼び出しサイクルとチャットなチェーンを避けます。
- 意図的なエントリポイントをエクスポートします。既存のパッケージ、lint、またはビルドツールを使用して深いインポートをブロックします。
- ブラウザセーフな契約をサーバー専用の実装および特権依存関係から分割します。
- 特権SDK、データベースクライアント、シークレット、ファイルシステムコードをクライアントの依存関係グラフから遠ざけます。
- プロバイダーSDKはランタイム条件の下でテストする必要があるアダプターで実行される場合があります。
5. 変更をトレースする
変更されたすべてのパス、理由、ファイル間の関係をファイルシステムの場所を使用して記録します。統合ギャップと予期しないコンシューマを特定します。
| テーマ | 追跡内容 | 対象読者 | | --- | --- | | 所有権 | 各変更されたモジュールの明示的なドメイン所有者 | エンティティの割り当て、手戻りの防止 | 範囲 | どのユーザーワークフローまたは運用シナリオが影響を受けるか | QA、リスク評価、リリースノート | 意図 | 各変更の理由(修正、フィーチャー、リファクタリング、パフォーマンス) | コードレビュー、監査、将来のリファクタリング | 依存関係 | 変更されたファイルとそのポート/契約の消費者 | 統合テスト、調整 |
トレース情報を保存するには、統一されたタスク/トレースレコードを使用します。まだ存在しない場合は作成します。レコードは /docs/tasks またはリポジトリに存在する /doc/tasks にあります。そのレコードに変更されたパス、各パスのテーマと値(例:billing.services.invoice)、各変更の理由(修正、フィーチャー、リファクタリング、パフォーマンス)、各変更の原因となった外部契約または依存関係を記録します。新しく作成されたファイルは所有権テーマでマークし、READMEまたは標準のファイルヘッダーにモジュールの責任を文書化します。
タスクトレースレコードの例
{
"change_id": "a1b2c3d4",
"timestamp": "2025-04-01T10:00:00Z",
"scope": "Checkout flow performance",
"paths": {
"packages/billing/src/services/invoice_service.py": {
"themes": ["billing.services.invoice"],
"reason": "performance",
"external_contract": null
},
"packages/billing/src/tests/test_invoice_service.py": {
"themes": ["billing.tests"],
"reason": "refactoring",
"external_contract": null
}
},
"gaps": [],
"evidence": {
"verified": "2025-04-01T11:00:00Z",
"checks_passed": ["unit:invoice", "integration:billing"],
"checks_skipped": [],
"unverified_assumption": null
},
"remaining": []
}
ギャップを特定するには、paths の下にある、変更されたがトレースレコードの対応する gaps 配列に記載されていない各ファイルを探します。エージェントはそれをギャップとして記録します。変更された各ファイルは1つの理由(修正、フィーチャー、リファクタリング、パフォーマンス)を持つ必要があります。エントリポイントとサービスは external_contract 値を持つことができます。それ以外はヌルです。remaining には、このスキルおよびホストによって許可された将来の作業の説明を入れます。
6. 維持する
このベースラインを維持するために毎日行うことはほとんどありません。アーキテクチャコストと、それらのコストによって可能になる節約を理解します。編集するたびにこの文書を読み直すと、ドメインと配置の選択を回避することは困難です。この文書を使用して新しいエンジニアをオンボードします。この設計を継続する、または移行する準備をします。
コストが便益を上回ったときに設計を変更します:依存関係のループを検出したとき、フレームワークの更新ごとに多くのファイルを変更したとき、ビルドがメトリック全体より遅いとき、同じコードベースから2つの異なる製品を提供するとき、または1つのチームがデプロイに影響されるバグを修正するとき。その時点で、分割、移行、または新しい制約を説明する次の設計を書きます。
- 正しい分離と結合の初期コストは、後の期間にわたって返済されます。
- コードは作成されるよりもはるかに頻繁に読まれ、変更されます。読みやすさと推論のしやすさに投資します。
- 設計決定は特定のビジネスと運用ニーズに対応します。決定を事前にテストするか、それを証明する既存のデータを参照します。
- 自分で強制できないルールを書かないでください。このスキルは人間のレビューとツールチェーンによってサポートされる必要があります。
- すべての変更はトレースに値するわけではありません。エントリポイント、契約、不変条件は常にマークします。CRUDフィールドの追加は頻繁には必要ありません。
- 複雑さとリスクに比例してトレースします。移植とリファクタリングは、フィールドの追加やバグ修正よりも多くのトレースを必要とします。
- 公開契約と仕様をソースリポジトリの真実と調整します。承認を先取りしないでください。
より小さなプロジェクト向けの調整
バリューオブジェクト、エンティティ、サービスのルールは同じままです。ディレクトリ構造は反復して簡単にできます。ユースケースごとに1つのファイル、またはアーキテクチャ全体として1〜3のモジュールから始めます。グループ化は、境界が明らかになったときにコードベースが進化するにつれて自然に行われます。ディレクトリを早期に作成しすぎないでください。プロジェクトが有機的に成長するにつれて、命名の一貫性を維持します。
一般的に、構造と区切りはチームサイズとコードベースの年齢の増加関数です。段階的に作業します。最終状態を設計してからその状態へのリファクタリングに着手するのではなく、次のステップのための適切なスコープと所有権を決定します。
7. 例
ユーザーは「チェックアウトが遅い」と言います
- コンテキスト:
/apps/web/checkout/を検査し、トレースを要求します。 - 証拠:プロファイリングにより、請求書生成のためのN+1クエリが特定されます。
- ドメイン:ECドメインの
checkout。コア差別化。 - モデル:
model/Invoiceは集約ルートに属します。 - サービス:
services/checkout_serviceはadapter/pricing_gatewayとadapter/invoice_repoを使用します。 - 最小の変更:InvoiceRepository のバッチ読み込み。リポジトリを変更します。
- スコープのトレース:
billing/invoice_repoを修正理由でマークします。 - 完了:テストを実行し、レイテンシを受け入れチェックと比較します。
- ハンドオフが必要な場合:子エージェントに具体的なパスと契約を提供します。
ユーザーは「pricing パッケージを追加する」と言います
- コンテキスト:現在のパッケージは商品と注文を処理します。
- DDD質問フレームを使用します:
- 結果?動的な価格設定と割引。
- 所有権?新しいPricingコンテキスト。
- コンシューマ?チェックアウトとカタログAPI。
- 制約?外部Pricing APIへの低レイテンシ。
packages/pricingを作成します:packages/pricing/ src/ model/ Price.ts, Discount.ts services/ pricing_service.ts ports/ pricing_gateway.ts adapters/ external_pricing_gateway.ts- 依存関係:
pricingの内部実装はドメインに依存しません。 - 統合:
checkout_serviceはPortを通じてPricingを消費します。 - トレース:新しいパッケージを所有権でマークします。
- ハンドオフ:必要に応じて、公開契約と依存関係を明確にします。