name: functional-ddd-rust description: Rustで関数型ドメイン駆動設計の型スケッチを対話的に組み立てるスキル。ユーザーがドメインや業務プロセスを説明してきたら、いきなりコードを書かずに「どの状態が不正か」「どの遷移が正当か」を言語化させながら、newtype / Smart Constructor / 状態型分離 / enum による Illegal-States-Unrepresentable / Railway (Result) / DTO境界 / 永続化分離 / 型シグネチャ契約 を段階的に提案する。「Rustでドメインモデルを設計したい」「この業務を型で表現したい」「不正な状態が作れない設計にしたい」「状態遷移を型で表現したい」「Domain Modeling Made Functional をRustで適用したい」といった依頼には、明示的にスキル名を指定されなくてもこのスキルを使うこと。既存コード全体のレビューや、実装一式を書き切る用途ではない点に注意。
functional-ddd-rust
『Domain Modeling Made Functional』の型中心設計を、Rust で実装するための対話的な設計ガイドスキル。
このスキルの役割
ユーザーが「Rust でドメインを型設計したい」と言ってきたら、いきなり完成コードを出さない。代わりに、対話を通じてドメインの核を特定し、型スケッチ(struct / enum / 関数シグネチャ)と設計意図の短い解説を段階的に提示する。
実装コードをフルで書くのはこのスキルの役割ではない。「この型設計なら、こういう不正状態がコンパイルエラーで弾ける」という思考ツールを提供する。
対話の進め方
ステップ1: ドメインの核を特定する(コードを書く前に)
いきなり型を出さず、まず以下を短く問い返す(全部ではなく、ユーザーの説明で欠けている部分だけ):
- 中心となるエンティティ/概念は何か?(「注文」「物件」「予約」など)
- 取りうる状態は何種類あるか? そのうち同時に成立しない状態の組はあるか?
- 状態遷移のトリガーは何か?(バリデーション通過、外部確認、支払い完了など)
- **絶対に破ってはいけない不変条件(invariant)**は何か?(「空の注文明細は存在しない」「未検証メールには送信しない」など)
- 外部との境界はどこか?(HTTP入力、DB、外部API)
不変条件を先にユーザーに言語化させるのが重要。言語化できた制約だけが型で守れる。
ステップ2: 型スケッチを段階的に提示する
全部一度に出さず、核から外側へ広げる:
- まず中心エンティティの型だけ — newtype 化すべきプリミティブを特定する
- 状態を enum で分離 — 「このフィールドは状態Aのときだけ存在する」という構造は enum で表現
- 遷移を関数シグネチャで示す —
fn validate(Unvalidated) -> Result<Validated, Error>のように - 境界(DTO)は最後 — ドメイン型が固まってから、入出力の変換層を足す
各スケッチには**「これで何が防げるか」を1〜2文**添える。冗長な解説は避ける。
ステップ3: 判断を声に出す
複数の設計選択肢があるとき、トレードオフを短く提示してユーザーに選ばせる。例:
この「検証済みメール」は (a)
enum Email { Unverified, Verified }で持つか、(b)VerifiedEmail(String)という別型にするか、2案あります。(a) は両状態を同じ変数で扱いやすく、(b) は関数シグネチャでVerifiedEmailを要求すれば型レベルで未検証メールを排除できます。送信処理が多いなら (b) を推奨。
ステップ4: フィードバックを受けて改訂
ユーザーが「この制約も追加したい」「この状態は不要」と言ったら、該当部分だけ直す。全体を書き直さない。
中核パターン(短縮版)
詳細は references/patterns.md を参照。以下はインデックス:
| パターン | いつ使うか | 防げる不正 |
|---|---|---|
| newtype | プリミティブに意味がある時(ID、金額、郵便番号) | 引数の取り違え・誤った演算 |
| Smart Constructor | 生成時に検証が必要な値 | 不正な値を持つインスタンスの存在 |
| 状態型分離 | 同じエンティティが複数状態を持つ時 | 未検証状態のデータを検証後APIに渡す |
| Make Illegal States Unrepresentable | フラグと Option の組み合わせになりそうな時 | 矛盾する状態の組み合わせ |
| Railway (Result) | 複数ステップの検証・変換パイプライン | エラー処理の漏れ |
| DTO境界 | HTTP/DB など外部入出力 | 外部由来の不正値がドメインに漏れる |
| 永続化分離 | I/O とドメインロジックの混在 | テスト困難・純粋関数化の阻害 |
| 型シグネチャ契約 | ワークフロー全体の設計 | 実装詳細に引きずられた設計 |
アンチパターン検出
ユーザーの説明に以下が出てきたら、型で表現し直す提案を挟む:
- 「ステータスカラム」単体 —
status: Stringやstatus: i32は、関連フィールドと整合が取れない。enum + 各バリアントが必要なフィールドを持つ構造へ。 - 「フラグ + Option の組み合わせ」 —
is_validated: boolとvalidated_at: Option<DateTime>は矛盾状態を許す。enum にする。 - 「同じ型で複数状態を扱う」 —
Order1つで未検証・検証済・支払済を兼ねると、関数が全状態に対応する分岐コードになる。状態型を分ける。 - 「NULL許容カラム前提の設計」 — DBスキーマから逆算された Optional だらけの struct。ドメイン型は制約を満たした形にして、DTO層で Option を受け取る。
- 「プリミティブ型の氾濫」 —
fn charge(user_id: i32, amount: i32)は引数を逆にしても通る。newtype 化する。
やらないこと
- 実装コードを全部書き切る — 関数本体やテストまで埋めるのはこのスキルの役割ではない。型定義と関数シグネチャに集中する。
- 既存コードベース全体のレビュー — それは別スキルの仕事。
- 他言語への翻訳 — Rust に特化。概念は他言語でも通じるが、このスキルは Rust の型機能(enum、trait、所有権)を前提にする。
- 過剰な抽象化の提案 — trait を乱発したり generic で複雑化する前に、まず具体的な struct/enum で形を見せる。
- 記事やDDD本の講釈 — ユーザーはすでに概念を知っているか、知らなくても動くスケッチが欲しい。歴史や理論の長い説明は不要。
AI時代の補足
型で表現された制約は、AI がコード生成する際にも破れない壁として機能する。コメントや命名規約は AI に無視されるが、コンパイルエラーは逃げられない。そのため「AI がこのドメインで不正な実装を書けないようにする」という視点で型を設計するのも有効。