第5章:ValueObject入門(住所・金額・期間を“型”で守る)🧱💰📦
5-1. ValueObject(値オブジェクト)ってなに?🧠✨
ValueObject(VO)は、ざっくり言うと 「値そのものに意味がある小さな型」 です😊 たとえば👇
1000円(金額)💴komi@example.com(メール)📩2026/01/01〜2026/01/31(期間)📅東京都○○区…(住所)🏠
こういうのって、ただの string や int のままだと、変な値が簡単に入り込むんだよね…😵💫
そこで VO にすると 「変な値を入口で止める」 ことができるようになるよ🔒✨
5-2. ACL(腐敗防止層)とVOは相性バツグン🧱🌊✨
21:
外部APIやレガシー連携って、だいたいこんな “クセ” がある😇
- 金額が
cents(セント)で来る(円じゃない)💸 nullや空文字が平気で来る🫠- メールが
aaaaとか来る(それメールじゃない!)📩❓ - 日付が
"2026-01-28T12:34:56Z"みたいな文字列で来る🕒
ACLの仕事は 外のクセを内側に持ち込まないこと。 その時、内側の「受け皿」として VO があるとめちゃ強いよ💪✨
- 外:DTO(ただの箱)📦
- 境界:ACLで変換&検証🧱
- 内:VO(ちゃんと意味が保証された型)🛡️
5-3. VOの“お約束” 3つだけ覚えよ🧁✨
41:
お約束①:同一性じゃなく「値」で等価になる🟰
VOは「IDが同じか」じゃなくて、「値が同じか」で同じ扱いにするよ😊
1000円 はどこから来ても 1000円。
お約束②:基本はイミュータブル(作ったら変えない)🧊
後から書き換えできると、いつの間にか壊れるので… 作る瞬間にチェックして、作れたら安心が理想✨
record は “値で比較” が得意で、VOに向いてるよ(record class / record struct)📘 (Microsoft Learn)
お約束③:バリデーションは「入口」でやる🔒
VOを作るときにチェックする! そうすると 内側は「VOなら信用できる」 が成立するよ😌✨
5-4. ハンズオン①:Money を VO にする💰🧱
まずは「ただのdecimal」だと何がつらい?😵
- マイナス金額が入る(返品?割引?意図不明)🫠
- 通貨が分からない(円?ドル?)💱
- 「税込?税抜?」が混ざる(地獄)🔥
ここでは最小でいくよ👇
- 0円以上
- 通貨はJPY固定(まずは簡単に)💴
Money.cs(Domain/ValueObjects あたりに置く想定📁)
namespace MiniECommerce.Domain.ValueObjects;
public sealed record Money
{
public decimal Amount { get; } // 円
private Money(decimal amount) => Amount = amount;
public static Money Create(decimal amount)
{
if (amount < 0)
throw new ArgumentOutOfRangeException(nameof(amount), "金額は0以上にしてね💦");
// 例:円は小数いらない方針にしたいなら丸める/弾く、などもここで決められるよ😊
return new Money(amount);
}
public override string ToString() => $"{Amount:N0}円";
}
使ってみる👀✨
var price = Money.Create(1200m);
Console.WriteLine(price); // 1,200円
// var bad = Money.Create(-1m); // ここで止まる(内側に入らない)🧱
“値で等価” を体感しよう🟰
var a = Money.Create(1000m);
var b = Money.Create(1000m);
Console.WriteLine(a == b); // True ✅(recordだから値で比較しやすい)
5-5. ハンズオン②:Email を VO にする📩🧱
外部から来るメールって、ほんとに自由😇 だから「Emailとして成立してるか」を入口で守ろう🔒✨
ここでは シンプル判定でOK(教材なので)😊
- 空文字NG
@を含む- 前後の空白は許さない(Trimしてからチェック)✂️
Email.cs
namespace MiniECommerce.Domain.ValueObjects;
public sealed record Email
{
public string Value { get; }
private Email(string value) => Value = value;
public static Email Create(string? value)
{
var v = value?.Trim();
if (string.IsNullOrEmpty(v))
throw new ArgumentException("Emailが空だよ💦", nameof(value));
if (!v.Contains('@'))
throw new ArgumentException("Emailに @ がないよ💦", nameof(value));
return new Email(v);
}
public override string ToString() => Value;
}
使ってみる📩✨
var email = Email.Create(" komi@example.com ");
Console.WriteLine(email.Value); // komi@example.com
// var bad = Email.Create("aaaa"); // 入口で止まる🧱
5-6. ハンズオン③:DateRange(期間)を VO にする📅🧱
期間は「開始〜終了」だけど、外部の値が変だと壊れがち😵💫
- 開始が終了より後(逆転)🙃
- そもそも日付が未指定(null)🫠
DateRange.cs
namespace MiniECommerce.Domain.ValueObjects;
public sealed record DateRange
{
public DateOnly Start { get; }
public DateOnly End { get; }
private DateRange(DateOnly start, DateOnly end)
{
Start = start;
End = end;
}
public static DateRange Create(DateOnly start, DateOnly end)
{
if (end < start)
throw new ArgumentException("期間が逆転してるよ💦(EndはStart以上にしてね)");
return new DateRange(start, end);
}
public int Days => End.DayNumber - Start.DayNumber + 1; // ざっくり日数
}
5-7. よくあるミス集(ここで潰すと成長が早い)🚀🧯
ミス①:VOなのに set できるようにしちゃう😇
public set; があると、後から壊せちゃう…
VOは 作るときに決めて、作ったら固定 が基本だよ🧊✨
ミス②:VOを作らずに string がドメイン中に広がる🫠
最初は楽だけど、あとで「どれがEmail?どれが住所?」って崩壊しやすい💥 “意味がある値” は VO にする のがコツ😊
ミス③:検証があちこちに散らばる🌀
UIでもチェック、APIでもチェック、ドメインでもチェック…で二重三重😵💫 まずは VOのCreateに寄せる のがスッキリするよ🔒✨
5-8. ミニ課題(手を動かすと一気に身につく)📝💪✨
課題A:VOにしたい項目を3つ考えよう🧠💡
例👇
PhoneNumber📞PostalCode🏣ProductName🏷️
「どんな値を弾きたいか」も一言書けたら満点💯✨
課題B:Money にルールを1つ足してみよう➕💰
例👇
- 1円未満はダメ(小数禁止)🚫
- 上限を決める(例:1,000万円まで)🏦
- 税込み/税抜きフラグを持つ(将来版)🧾
5-9. AI活用(雛形はAI、ルールは自分)🤖✍️✨
そのまま使えるお願いテンプレ(コピペOK)📋✨
-
VOの雛形を作ってもらう
- 「C#のrecordで
PostalCodeのValueObjectを作って。Createで空・桁数・数字以外を弾いて。例外メッセージも付けて😊」
- 「C#のrecordで
-
“弾くべき悪い入力” を出してもらう(超便利)😈
- 「
Emailの入力で想定される悪いパターンを20個、理由つきで列挙して」
- 「
-
テスト観点を増やしてもらう✅
- 「
DateRangeの境界値(同日/逆転/極端な未来日など)のテストケース案を出して」
- 「
C# 14 は .NET 10 SDK で使えるよ(最新の言語機能まとめはここ)📘 (Microsoft Learn) .NET 10 は 2025-11-11 にリリースされて、LTSでサポートが続くよ🧱✨ (Microsoft Learn)
5-10. まとめ(この章で持ち帰ること)🎁✨
- VOは 「意味のある値を、型で守る」 🧱
- VOを作る瞬間にチェックして 内側を安全地帯にする 🔒
- record は 値で等価 を作りやすく、VOに向いてる😊 (Microsoft Learn)
- ACLで外部DTOを受けたら、内側に入れる前に VOに変換 できると最強🛡️✨