第12章 Commandの基本② Validationの分離(入口で守る)🔍🛡️✨
この章は「Commandを安心して実行できるように、検証(Validation)を“ちゃんと分けて”、入口で止める」の練習だよ〜!😊✨ (いったん“入口で落とす”ができると、CQRSが一気にキレイになるよ🎉)
12.0 今日のゴール 🎯💖
- ✅ 「検証」を UI検証 / 仕様検証 / 業務ルール の3つに分けて説明できる
- ✅ CommandHandlerの中が if地獄 にならないように、検証を 別クラス に逃がせる
- ✅ 失敗したときに 400(Bad Request) で分かりやすく返せる(API利用者も嬉しい) ※Minimal APIは .NET 10 で 組み込みのValidation が用意されてるのも追い風だよ⚡ (Microsoft Learn)
12.1 まず結論:Validationは「入口」で止めるのが一番ラク😇🚪
なぜ分けるの?🤔
CommandHandlerに検証を混ぜると…
- 😵 変更のたびに if が増える(読むのがつらい)
- 🧪 テストがしにくい(DB呼んだり、例外が増えたり)
- 🔁 同じ検証をあちこちで繰り返す(バグの温床)
だから、基本方針はこれ👇
「入力の正しさ」は入口で止める 「業務的にやっていいか」は業務ルールで止める (そして2つは混ぜない) 🧠✨
12.2 Validationの3分類:ここが一番大事🌟🗂️
同じ「検証」でも、性質がぜんぜん違うよ〜!
A. UI検証(フロントの都合)📱💡
- 例:必須チェック、入力補助、フォーマット補助
- 目的:ユーザー体験(入力ミスを早めに気づかせる)
- 注意:サーバー側では信用しない(改ざんできるから)😅
B. 仕様検証(サーバー入口で守る)🧾🛡️
- 例:必須、文字数、範囲、形式、列挙の範囲
- 目的:「そもそもCommandとして成立してる?」を保証する
- ここが第12章の主役👑✨
C. 業務ルール(ドメイン・ユースケース側)🏛️🔥
- 例:「在庫が足りないと注文できない」「期限切れは購入不可」
- 目的:「今その操作をしていい?」を判断する
- DB状態が絡むことが多い(在庫、重複、状態遷移など)
MVCのモデル検証でも「入力変換エラー」と「ルール違反」を分けて考える説明があるよ。(Microsoft Learn)
12.3 CQRSでの“置き場所”マップ 🗺️✨

イメージはこんな感じ👇
-
🚪 API入口(Controller / Minimal API)
- 仕様検証(DataAnnotations / Validatorクラス)
- ここで落ちたら 400 で返す
-
🧑🍳 CommandHandler(ユースケース)
- 業務ルール(在庫チェックなど)
- 失敗したら 409/422 など(※ここは後章で整える)
-
🏛️ ドメイン(Entity / ValueObject)
- “不変条件”として守る(絶対に壊れないルール)
12.4 実装①:.NET 10 の「組み込みValidation」で入口ガード🚪⚡
.NET 10 の Minimal API は、DataAnnotations を使った組み込みValidationを AddValidation() で有効化できるよ。
失敗したら 400 を自動で返してくれるのが超便利! (Microsoft Learn)
例:CreateOrder の “入口仕様検証” 🍰🧾
using System.ComponentModel.DataAnnotations;
public record CreateOrderRequest(
[Required, StringLength(50, MinimumLength = 1)]
string CustomerName,
[Required, EmailAddress]
string Email,
[Range(1, 99)]
int Quantity
);
Program.cs 側でValidationをON👇
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddValidation(); // ✅ Minimal API built-in validation (.NET 10)
var app = builder.Build();
app.MapPost("/orders", (CreateOrderRequest req) =>
{
// ここに来た時点で req は “仕様的に成立” してる✨
// あとは Command に変換して Handler へ
return Results.Ok(new { message = "accepted" });
});
app.Run();
💡ポイント
- ✅ 「必須」「範囲」「形式」みたいな 仕様検証に強い
- ✅ 仕組みとしては エンドポイントフィルターが自動で入って動くよ (Microsoft Learn)
- ✅ 例外や if で汚れないから、Controller/Endpointがスッキリ😍
ちなみに MVC/API Controller でも DataAnnotations のモデル検証は王道で、Web API は無効なとき自動で 400 を返す流れがあるよ。(Microsoft Learn)
12.5 実装②:Validatorクラスに分けて “設計” をきれいにする🧼✨
DataAnnotationsは強いけど、条件が増えると読みづらくなることがあるよね😵💫 そこで「Validatorクラス」を作って分離するのがCQRS的にキレイ💖
FluentValidationはどうする?🤖📦
FluentValidationは超人気なんだけど、ASP.NETの自動検証パイプラインに“魔法みたいに統合する”方式は新規では推奨されない流れが明確だよ。 (非同期ルールに弱い / MVC限定 / デバッグしづらい、など) (GitHub)
なのでこの教材では、**「手動で呼ぶ」or「自分のDispatcher/Filterで呼ぶ」**をおすすめにするね😊✨
例:Command用Validator(手動で入口で呼ぶ)🚪🧑🍳
まず Command を用意(RequestとCommandは分けるのがコツ!)📦
public record CreateOrderCommand(
string CustomerName,
string Email,
int Quantity
);
Validator(FluentValidation)を用意👇
using FluentValidation;
public class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand>
{
public CreateOrderCommandValidator()
{
RuleFor(x => x.CustomerName)
.NotEmpty()
.MaximumLength(50);
RuleFor(x => x.Email)
.NotEmpty()
.EmailAddress();
RuleFor(x => x.Quantity)
.InclusiveBetween(1, 99);
}
}
DI登録(Validatorsをまとめて登録できる)✨ (FluentValidation)
using FluentValidation;
using FluentValidation.DependencyInjectionExtensions;
builder.Services.AddValidatorsFromAssemblyContaining<CreateOrderCommandValidator>();
入口(Endpoint)で検証して、ダメなら400を返す👇
using FluentValidation;
app.MapPost("/orders", async (
CreateOrderRequest req,
IValidator<CreateOrderCommand> validator) =>
{
var cmd = new CreateOrderCommand(req.CustomerName, req.Email, req.Quantity);
var result = await validator.ValidateAsync(cmd);
if (!result.IsValid)
{
// 400: Validation errors
var errors = result.Errors
.GroupBy(e => e.PropertyName)
.ToDictionary(
g => g.Key,
g => g.Select(e => e.ErrorMessage).ToArray()
);
return Results.ValidationProblem(errors);
}
// OKなら Handler へ(この章では省略)
return Results.Ok(new { message = "accepted" });
});
💖この形の良いところ
- ✅ CommandHandlerが「業務ルール」に集中できる
- ✅ 仕様検証は Validator に集約される
- ✅ テストがしやすい(Validator単体でテストできる🧪)
12.6 エラーメッセージのコツ(地味に重要)🗣️✨
Validationのメッセージは「人に見せる」前提で書くと嬉しいよ😊
- 😺 NG:
Email is invalid - ✅ OK:
メールアドレスの形式が正しくありません
さらにおすすめ👇
CustomerNameみたいな内部名は、必要なら表示名に変換- 仕様検証は「直せば成功する」メッセージにする(怒らない💞)
12.7 ミニ演習(この章のメイン練習)🧪🎀
演習①:チェックを3分類しよう🗂️✅
「注文」を例に、次を分類してみてね👇
- CustomerName が空 →( )
- Quantity が 0 →( )
- 在庫が足りない →( )
- その注文はキャンセル済みなので更新できない →( )
- UIで半角数字以外を弾く →( )
👉答え合わせ目安
- 1,2 = 仕様検証
- 3,4 = 業務ルール
- 5 = UI検証
演習②:Validatorを1つだけ作る🍰
CreateOrderCommandValidatorを作る- ルールは 3個だけでOK(欲張らない😇)
演習③:Validator単体テスト(最小でOK)🧪✨
- 正常ケース1本
- 異常ケース2本(空、範囲外)
(テストがあると、安心してルール変えられるよ〜!💖)
12.8 AI活用プロンプト集 🤖💬✨
コピペで使えるやつ置いとくね!
-
🧠 仕様検証の洗い出し 「CreateOrder の入力項目(CustomerName/Email/Quantity)について、仕様検証ルールを“必須/形式/範囲/長さ”で列挙して。過剰なルールは作らないで」
-
🧪 テストケース案 「CreateOrderCommandValidator のテストケースを、正常1・異常2で提案して。境界値を意識して」
-
🧹 メッセージ改善 「このValidationメッセージを、ユーザー向けに優しく短く直して:『{メッセージ一覧}』」
12.9 今日のチェックリスト(卒業条件🎓✨)
- ✅ 「UI/仕様/業務ルール」を混ぜずに説明できる
- ✅ 仕様検証は 入口(Endpoint/Controller)で止める
- ✅ CommandHandlerの中に、入力チェックの if が増えてない
- ✅ バリデーション失敗は 400 で返してる(エラー内容付き)
- ✅ (できたら)Validator単体テストがある🧪💕
おまけ:いまの.NETまわり超短メモ📌✨
- .NET 10 は LTS で、2026年1月の時点だと 10.0.2(2026-01-13) が最新パッチとして案内されてるよ。(Microsoft)
- C# 14 の新機能は Microsoft Learn にまとまってるよ(必要なときにチラ見でOK😊)。(Microsoft Learn)
次の第13章(業務ルールの置き場所🏛️)に行くと、 「仕様検証で落とせないやつ(在庫とか状態遷移)」を、どうキレイに守るかが見えてくるよ〜!😺✨