第3章:関心の分離(SoC)だけ先に押さえる(ACLの前提)🚪🧠
この章でできるようになること 🎯✨
- 「混ぜちゃダメ!」な理由を、自分の言葉で説明できる 🗣️
- コードを UI / アプリ / ドメイン / 外部I/O に分けて考えられる 🧩
- ACL(腐敗防止層)が どの“境界”に立つのかイメージできる 🧱🌊
3-1. SoC(関心の分離)ってなに?🧼
SoC(Separation of Concerns)は一言でいうと…
**「役割の違うものを、同じ場所に混ぜない」**ルールです ✋🧩
たとえば、料理でいうと…
- まな板で切る(下ごしらえ)🔪
- フライパンで焼く(調理)🍳
- お皿に盛る(盛り付け)🍽️
これを全部「まな板の上でやる」と、カオスですよね😇 コードも同じで、役割が混ざると壊れやすくなります。(ウィキペディア)
3-2. 「混ぜる」と何が起きるの?😵💫⚡(最短で体感)
よくある“混ぜ混ぜ地獄”の例 🔥
「注文を作る処理」の中に、こんなのが全部入ってると…👇
- 画面入力のチェック(UIの都合)🖥️
- 決済API呼び出し(外部I/O)🌐
- JSONの解析(外部形式)📦
- DB保存(外部I/O)🗄️
- 「注文とは何か」のルール(ドメイン)📘
この状態だと…
- 決済APIが仕様変更 → 注文ロジックまで巻き添え💥
- UIの項目が増えた → ビジネス処理まで改修🌀
- テストしたい → ネットワークやDBが邪魔😇
つまり、「外側の都合」で内側(大事なルール)が腐っていきます🧟♀️🧼
3-3. 4つの“部屋”で考える🏠🧠(UI / アプリ / ドメイン / 外部I/O)

この教材では、まずこの4部屋で考えます✨
| 部屋(層) | 役割 | 具体例 |
|---|---|---|
| UI(Presentation)🖥️ | 入力を受け取り、結果を返す | Controller / API Endpoint / ViewModel |
| アプリ(Application)🧭 | 手順を進める(ユースケース) | PlaceOrder(注文する手順) |
| ドメイン(Domain)💎 | ルールの中心(意味) | 注文の状態、合計金額の扱い、制約 |
| 外部I/O(Infrastructure)🌐 | 外の世界と話す | DB、HTTP、ファイル、時計、外部API |
ポイントはこれ👇
- ドメインは最強に大事💎
- 外部I/Oは気まぐれ(仕様変更・不安定・遅い)🌪️
- だから 混ぜない!🧱✨
3-4. ACLはどこに立つの?🧱🌊(SoCとの関係)
ACL(Anti-Corruption Layer / 腐敗防止層)は、ざっくり言うと
「外部の言葉・クセ」を「自分たちの言葉」に翻訳して、内側を守る壁🧱✨
外部と内側の間に立って、翻訳・変換・吸収をする層です。(Microsoft Learn)
3-5. まずは“線引き図”を作ろう✍️🗺️(ハンズオン)
3-5-1. いったん、これを紙に書く(超おすすめ)📝✨
紙でもメモアプリでもOK!まずは図で理解しちゃおう🥰
図の読み方(ここ大事)👀
- UI:入力受付と表示担当(人間と話す)🖥️
- Application:手順担当(注文→決済→保存、みたいな流れ)🧭
- Domain:意味担当(注文のルール、状態の変化)💎
- Infrastructure:外部と話す担当(DB/HTTPなど)🌐
- ACL:外部のクセを吸収して、内側を守る翻訳係🧱
3-6. “仕分けゲーム”👀🧾(内側に入れていい?ダメ?)
次の要素を、どの部屋に置くべきか仕分けしてみてね👇✨
仕分けリスト(例)🎒
HttpClient🌐JsonSerializer📦Order(注文)🛒- 「注文は合計0円にできない」🚫
- DBの接続文字列 🔑
- 「決済が成功したら注文をPaidにする」✅
- APIの入力DTO(リクエスト用)📥
- 例外ログ出力 🧾
目安(答えの方向性)🧭
- 外部I/O寄り:
HttpClient/ JSON / DB / 設定 / ログ - ドメイン寄り:
Order/ ルール(〜してはいけない) - アプリ寄り:成功したらPaidにする…みたいな「手順」
- UI寄り:入力DTO、画面都合のバリデーション
3-7. “混ぜ混ぜコード”を見て、分割する(ミニ演習)🧪🧩
3-7-1. まずは悪い例(わざと混ぜる)😈
「こうなりがち」っていう例だよ!見て“混ざってる箇所”を探してね👀
public sealed class OrderService
{
public async Task<string> PlaceOrderAsync(string memberId, decimal amount)
{
// UI都合のチェックっぽいもの(本当はUI寄り)
if (string.IsNullOrWhiteSpace(memberId)) return "memberId is required";
// 外部I/O(HTTP)
using var http = new HttpClient();
var json = await http.GetStringAsync($"https://legacy-pay.example/pay?m={memberId}&a={amount}");
// 外部形式(JSON)に依存
if (json.Contains("\"status\":\"ok\""))
{
// ドメインっぽい状態変更(でもドメイン型が無い)
// DB保存(外部I/O)もここに入ってきがち…
return "Paid";
}
return "Failed";
}
}
どこが“混ざってる”?(チェック)✅
HttpClientがいる → 外部I/O混入🌐- JSON文字列を直に触ってる → 外部仕様が直撃📦
- 「Paid」など状態が文字列 → ドメインの意味が弱い💦
using var HttpClient()→ テストしづらい&管理しづらい😇
3-8. SoCっぽく分ける(ここがゴール)🏁✨
3-8-1. まずは“形”だけ分ける(この章ではここまででOK)👌
ここでは「ちゃんとしたACLの実装」はまだ先! まずは線引きだけやろうね🧱✨
✅ ドメイン(意味)側:注文の状態を型で持つ💎
public enum PaymentStatus
{
Pending,
Paid,
Failed
}
public sealed class Order
{
public string MemberId { get; }
public decimal Amount { get; }
public PaymentStatus Status { get; private set; } = PaymentStatus.Pending;
public Order(string memberId, decimal amount)
{
if (string.IsNullOrWhiteSpace(memberId)) throw new ArgumentException("memberId is required");
if (amount <= 0) throw new ArgumentException("amount must be positive");
MemberId = memberId;
Amount = amount;
}
public void MarkPaid() => Status = PaymentStatus.Paid;
public void MarkFailed() => Status = PaymentStatus.Failed;
}
✅ アプリ(手順)側:外部への依存は“抽象”にする🧭
public interface IPaymentGateway
{
Task<bool> PayAsync(string memberId, decimal amount);
}
public sealed class PlaceOrderUseCase
{
private readonly IPaymentGateway _paymentGateway;
public PlaceOrderUseCase(IPaymentGateway paymentGateway)
=> _paymentGateway = paymentGateway;
public async Task<Order> ExecuteAsync(string memberId, decimal amount)
{
var order = new Order(memberId, amount);
var ok = await _paymentGateway.PayAsync(memberId, amount);
if (ok) order.MarkPaid();
else order.MarkFailed();
return order;
}
}
✅ 外部I/O側:ここにHTTPやJSONが住む🌐
(この章では“雰囲気だけ”でOK!)
public sealed class LegacyPaymentGateway : IPaymentGateway
{
private readonly HttpClient _http;
public LegacyPaymentGateway(HttpClient http) => _http = http;
public async Task<bool> PayAsync(string memberId, decimal amount)
{
var json = await _http.GetStringAsync($"https://legacy-pay.example/pay?m={memberId}&a={amount}");
return json.Contains("\"status\":\"ok\"");
}
}
ここまでで、最低限これが達成できてます🎉
- ドメインに HttpClient が入ってない💎✨
- 外部の変更があっても、まず外側を直せばいい🌐
- テストは
IPaymentGatewayを差し替えればできる🔁
※ HttpClient の管理は IHttpClientFactory が定番だよ(後の章で扱いやすくなるやつ)🧰(Microsoft Learn)
3-9. 「内側に入れていいもの / ダメなもの」早見表🧾🚫
✅ 内側(Domain / Application)に入れていいもの
- 業務の言葉:Order / PaymentStatus など🛒
- ルール:0円禁止、状態遷移の制約など🚫
- 手順:注文→決済→状態更新(ただし外部詳細は知らない)🧭
- 抽象(インターフェース):
IPaymentGatewayみたいな“口”👄
❌ 内側に入れちゃダメなもの(腐敗の種)🌱🧟♀️
HttpClient/ URL / ヘッダー / 認証方式🌐- 外部JSONの形式・フィールド名📦
- DBテーブル名・SQL・接続文字列🗄️
- 外部のエラーコードや例外型(そのまま)💥
3-10. AI活用(この章のおすすめ)🤖✨
図の説明文をAIに作らせる📝
コピペして使える例👇
- 「この図(UI→Application→Domain→Infrastructure)の説明文を、初心者向けに200字で」
- 「この仕分けリストを UI/Application/Domain/Infrastructure に分類して、理由も一言ずつ」
- 「混ぜ混ぜコードの問題点を“テストしづらさ/変更に弱い/責務が曖昧”の観点で箇条書きにして」
コツはこれ💡
- AIは叩き台作りが得意
- 最終判断(どこに置くか)は人間が決める🧠✨
3-11. ミニ課題📝🌸
課題A:仕分け表を作る(3分)⏱️
自分のプロジェクトの中で、次を10個探して分類してね👇
- UIっぽいもの 3つ🖥️
- 外部I/Oっぽいもの 3つ🌐
- ドメインのルールっぽいもの 2つ💎
- アプリの手順っぽいもの 2つ🧭
課題B:一文で言う(30秒)🗣️
- 「SoCとは____」
- 「ACLは____を守るために、____する層」
3-12. 章末チェック(○×)✅🎯
- ドメイン層に
HttpClientがあっても、動くならOK → ○ or × - 「注文は0円にできない」はUIの関心 → ○ or ×
- 外部APIのJSON形式は変わりうるので、内側に直結させると危ない → ○ or ×
- ACLは外部モデルを内側モデルへ翻訳する役になりやすい → ○ or ×
次章への“つながり”🔗
SoCで「混ぜない線」が引けたら、次は **内側の言葉(ドメインの用語)**を整えていきます🗣️📘 (C# 14 は .NET 10 / Visual Studio 2026 で試せるよ〜!)(Microsoft Learn)