mcpField
D
domain-modeling

domain-modeling

github.com/jwilger開発5153 回インストール17 週間前に更新
リポジトリ

domain-modeling について

domain-modelingは、型を通じてビジネス概念を表現するための、開発者やエージェント向け設計スキルです。コンパイル時の正しさ、明示的なドメイン状態、システム境界での検証を重視します。

主な機能

  1. 01ドメインのプリミティブを意味型に置換
  2. 02入力を検証済みドメイン型に解析
  3. 03不正な状態を構築不能にする
  4. 04識別子ごとに専用の新しい型を使用
  5. 05ドメイン状態を網羅的にマッチ
  6. 06ドメイン原則に基づく設計レビュー

活用シーン

  1. 01ビジネス概念の型設計
  2. 02プリミティブ依存のコードレビュー
  3. 03入力境界への検証処理の移動
  4. 04矛盾するドメイン状態の防止

インストール

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

$npx skills add https://github.com/jwilger/agent-skills --skill domain-modeling

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

詳細

ドメインモデリング

価値: コミュニケーション — ドメイン型によって、コードがビジネスの言葉を話すようになります。暗黙の知識を、人間や AI が推論できる、明示的でコンパイラーによる検証が可能な契約に変換します。

目的

実行時にバグを検出するのではなく、コンパイル時に防ぐ、豊かなドメインモデルの構築方法を学びます。プリミティブ型偏重の検出、「検証するのではなくパースする」、無効な状態を表現不能にする方法、意味的な型設計を扱います。コードレビューや設計タスク全般に単独で役立ち、TDD サイクルにおけるドメインレビューのチェック項目の原則も提供します。

プラクティス

プリミティブ型偏重を避ける

ドメイン概念に生のプリミティブ型(String、int、number)を使用しないでください。ビジネス上の意味を表す型を作成してください。

推奨:

fn transfer(from: AccountId, to: AccountId, amount: Money) -> Result<Receipt, TransferError>

非推奨:

fn transfer(from: String, to: String, amount: i64) -> Result<(), String>

コードをレビューする際は、プリミティブ型がドメイン概念を表しているすべてのパラメーター、フィールド、戻り値の型を指摘してください。修正するには、コンストラクターで検証を行う newtype または値オブジェクトを作成します。

状態を表す bool のアンチパターン: 名前がドメインの状態を表す bool フィールド(already_exists、is_initialized、is_published、has_been_reviewed)は、プリミティブ型でエンコードされた状態機械です。現在は状態が 2 つでも、明日には 3 つになるかもしれませんが、bool では 3 つ目を表現できません。

// BAD: bool encodes a two-state machine as a primitive
struct Article { is_published: bool }

// GOOD: enum names the states and extends safely
enum ArticleState { Draft, Published, Archived }

「この条件は真か?」ではなく、「これはどの状態か?」に答える bool フィールドをすべて指摘してください。修正するには、ドメインの状態を表すバリアントを持つ enum を使います。このチェックは「無効な状態を表現不能にする」とは別のものです。前者のルールは不可能な組み合わせを見つけ、こちらのルールは boolean の中に隠れたドメイン概念を見つけます。

検証するのではなく、パースする

境界で検証します。内部では強い型を使います。型によってすでに保証されているデータを、再検証しないでください。

  1. システム境界(ユーザー入力、API レスポンス)で生の入力を受け取ります。
  2. 構築時に有効性を保証するドメイン型にパースします。
  3. ドメイン型をシステム内で受け渡します。それ以降の検証は不要です。
# Boundary: parse raw input into domain type
email = Email(raw_input)  # raises if invalid

# Interior: trust the type
def send_welcome(email: Email) -> None:
    # No need to validate -- Email guarantees validity

ビジネスロジックの深いところに検証ロジックがある場合は、構築時の境界に移してください。

無効な状態を表現不能にする

型システムを使い、不正な組み合わせを構築できないようにします。

問題 — boolean フラグによって無効な組み合わせが生じます:

struct User { email: Option<String>, email_verified: bool }
# Can have email_verified=true with email=None

解決策 — 状態を型でエンコードします:

enum User {
    Unverified { email: Email },
    Verified { email: Email, verified_at: Timestamp },
}

コードをレビューする際は、「この型は、ドメイン上で意味をなさない状態を表現できますか?」と確認してください。できる場合は、設計を見直してください。

構造型より意味型を使う

型の名前には、その構成要素ではなく、ドメインにおける型の役割を付けます。

誤り(構造型)正しい(意味型)
NonEmptyStringUserName
PositiveIntegerOrderQuantity
ValidatedEmailCustomerEmail

テスト方法は次のとおりです。2 つのフィールドが同じ構造型の場合、コンパイラーはそれらを取り違えても検出できません。意味型を使えば、それを防げます。

// BAD: title and name are both NonEmptyString -- swappable
{ title: NonEmptyString, name: NonEmptyString }

// GOOD: distinct types catch mix-ups at compile time
{ title: UserTitle, name: UserName }

構造型は、意味型がラップする構成要素として役立ちます。意味型はドメイン上の識別性を加え、構造型は再利用可能な検証を提供します。

識別子には newtype を使う

識別子にはそれぞれ専用の型を用意します。ID に生の String や int を使わないでください。

struct AccountId(Uuid);
struct UserId(Uuid);

// Compiler catches: transfer(user_id, account_id) won't compile
fn transfer(from: AccountId, to: AccountId, user: UserId) -> Result<(), Error>
使いやすい変換

構築時の検証と、簡単な値の取り出しを可能にします。

  • 構築(IN): 必ず検証を行うコンストラクターを通します。プリミティブ型からの自動変換は認めません。
  • 取り出し(OUT): Display、AsRef、Into、または同等の機能を提供し、型を使いやすくします。内部の値は簡単に取り出せるようにしてください。

プリミティブ型からの自動変換は決して提供しないでください。検証を迂回し、「検証するのではなくパースする」という原則を損ないます。

網羅的なマッチング

すべてのケースが処理されるように、網羅的な match/switch を持つ enum を使います。ドメインの状態に対して包括的な default を使わないでください。新しいバリアントが追加されても、黙って無視されてしまいます。

拒否権

TDD サイクル中か単独のレビューかを問わず、コードをレビューする際は、これらの原則に違反する設計を拒否する権限があります。この権限を行使する場合:

  1. 具体的な違反を示します(例:「プリミティブ型への執着:email は String ではなく Email 型にするべきです」)。
  2. 具体的な型定義を示して、代替案を提案します。
  3. その影響を一文で説明します。
  4. 相手が同意しない場合は、最大 2 回まで実質的な議論を行います。その後、人間にエスカレーションします。

衝突を避けるために、妥当なドメイン上の懸念を引っ込めてはいけません。これらの原則に違反する設計を、黙って受け入れてはいけません。

適用に関する注記

  • 単独モード:助言です。エージェントは慣例に従ってドメインモデリングの原則を適用します。
  • TDD ドメインレビュー(連鎖):助言です。拒否権は自己適用されます。エージェントは、別のエージェントがレビューする場合と同じ厳密さで、これらの原則に違反する自分の作業を拒否する必要があります。
  • TDD ドメインレビュー(サブエージェント):ゲートとして機能します。ドメインに関する拒否権によって、フェーズの進行が止まります。レビュー担当エージェントは引き継ぎを拒否できます。

厳守事項:

  • これらの原則に違反する設計を黙って受け入れてはいけません:[RP] -- 人間が明示的に上書きした場合は、その上書きと違反する原則を記録してください。

制約

  • 拒否権 --「引き下がらない」:エスカレーションによる摩擦を避けるために、妥当な懸念を正当化して退けてはいけません。反論に応じるなという意味ではありません。反論に真に説得力がある場合、つまりその原則がここでは当てはまらない場合は、評価を更新してください。エスカレーションが面倒だから引き下がることは、違反です。
  • 状態を表す Bool と条件の違い:判断基準は、そのブール値がドメインで意味を持つ状態遷移(例:有効/無効、開/閉)を表すかどうかです。該当する場合は、列挙型を使用してください。ドメインの状態に影響しない yes/no の質問(例:「この計算結果は正か」)に真に答えるものであれば、ブール値で問題ありません。迷った場合は列挙型を使用してください。小さな列挙型のコストは、ドメイン上の意味が知らないうちに蓄積するブール値のコストよりも低いためです。

検証

ドメインモデリングの原則を適用した後、次の点を確認してください。

  • ドメイン概念にプリミティブ型(String、int、number)を使用していない
  • ドメインの状態を表す bool フィールドがない(状態機械には列挙型を使用する)
  • すべての識別子が、生のプリミティブ型ではなく新しい型のラッパーを使用している
  • 無効な状態を表現できない(矛盾するフィールドの組み合わせがない)
  • 検証がビジネスロジックの深部ではなく、構築境界で行われる
  • 型名が構造ではなく、ドメインの意味(セマンティクス)を表している
  • 基になる型が同じ 2 つのフィールドを誤って入れ替えられない
  • 列挙型の網羅的なパターンマッチングを行い、ドメイン状態に対する包括的なデフォルトを設けていない

いずれかの基準を満たしていない場合は、先にドメイン型を作成するか、改善してください。

依存関係

このスキルは単独で動作します。より高度なワークフローでは、次と連携します。

  • tdd: ドメインレビューは、TDD サイクルにおける必須チェックポイントです。このスキルは、レビューで確認する原則を提供します。
  • code-review: ドメインの整合性は、3 段階レビューの第 3 段階です。このスキルは、確認すべき内容を定義します。
  • architecture-decisions: アーキテクチャパターン(イベントソーシング、CQRS、ヘキサゴナルアーキテクチャ)は、ドメイン境界の位置に影響します。

依存関係が不足していますか?次のコマンドでインストールしてください:

npx skills add jwilger/agent-skills --skill tdd

コメント