メインコンテンツまでスキップ

第16章:モジュール境界(迷子防止の土台づくり)📁🧭🔒

この章のゴール🎯✨

この章が終わると、こんなことができるようになるよ〜😊🌸

  • 「このコード、どこに置く?」で迷わなくなる🧭✅
  • UI / Application / Domain / Infrastructure の“4つの箱”で整理できる📦📦📦📦
  • 「依存の向き」をそろえて、変更に強い形を作れる🛡️➡️
  • 公開範囲(public)を絞って、壊れにくい“外に出す顔”を作れる🔒✨

1) そもそも、モジュール境界ってなに?🤔📦

プロジェクトが大きくなると起きがちなのがこれ👇

  • 似た処理が増える(どこにあるか分からない)😵‍💫
  • 画面の変更なのに業務ロジックが壊れる😱
  • DBを変えたいのに、あちこち直す羽目になる💥
  • 「ちょっと直しただけ」で影響範囲が読めない📉

モジュール境界は、かんたんに言うと ✅「置き場所ルール」+✅「依存の向きルール」+✅「公開面(外に見せる面)を小さくする」 この3点セットだよ〜📁🧭🔒


2) まずは“4つの箱”で考える📦✨(超重要!)

4 Layer Architecture

この章の主役はコレ👇

  • UI(Presentation):画面・入力・表示(Console/WPF/WinForms/Web もここ)🖥️🎨
  • Application:アプリのやりたいこと(ユースケース)🎬✅
  • Domain:業務ルールそのもの(大事な本体)💎📜
  • Infrastructure:DB・ファイル・HTTP・外部APIなど「外の世界」🌍🔌

「どこに置く?」迷子になったら、まずこの4箱に仕分けしよう🧭💕


3) 依存の向きは“内側を守る”🛡️➡️

Domain Protection

いちばん大事なルールはこれ👇

Domain は外側(DBやUI)を知らない外側が内側を使う(逆はダメ🙅‍♀️)

イメージ図(依存の矢印)👇

この向きにしておくと、

  • UIを変えても Domain が揺れにくい🎨➡️🛡️
  • DBを変えても Domain が揺れにくい💾➡️🛡️ ってなるよ😊✨

ちなみに .NET は「LTS/STS」が明確で、長く安定運用したいときは LTS を選ぶのが定番だよ📌(.NET 10 は LTS で、サポート期間も明示されてる)(Microsoft)


4) “小さく始める”なら、まずフォルダ分けでOK📁🌱

最初から複数プロジェクトに分けなくて大丈夫🙆‍♀️ まずは 1プロジェクトの中をこう分けるのが超おすすめ👇

MyApp/
Presentation/ ← UI
Application/ ← ユースケース
Domain/ ← ルール本体
Infrastructure/ ← DB/外部I/O

置き場所チートシート🧾✨

  • 画面イベント、入力、表示整形 → Presentation 🖥️
  • 「◯◯する(登録/更新/検索/通知)」の流れ → Application 🎬
  • 金額計算、期限ルール、状態遷移、バリデーション → Domain 💎
  • DBアクセス、ファイル、HTTP、時間取得、ログ出力 → Infrastructure 🔌

5) 公開面(public)を絞ると、壊れにくくなる🔒✨

プロジェクトが壊れやすくなる原因の1つが、 なんでも public にしちゃう問題😇💦

ポイントはこれ👇

  • 基本は internal(プロジェクト内限定) にする🔒
  • “境界として外に出すもの”だけ public にする✨

C# は「トップレベル(名前空間直下)の型」は public / internal にできて、指定しないと既定は internal だよ📌(Microsoft Learn) さらに最近は「file 修飾子」で“そのファイル限定”にもできる(小さく隠せる)って流れもあるよ🧷✨(Microsoft Learn)


6) ハンズオン🛠️:既存コードを4箱に仕分けして“迷子ゼロ”にする📦🧭

ここから実戦だよ〜💪🎀 題材は「締切つき ToDo を登録して、期限が来たら通知する」📅🔔(ありがちで練習しやすい!)

Step 1:まず“登場人物”を決める🧩

最低限これだけでOK👇

  • Domain:ToDo のルール(期限、完了、検証)💎
  • Application:登録する・完了にする・通知する流れ🎬
  • Infrastructure:時間取得、通知の実装(外の世界)🔌
  • UI:画面/入力/表示🖥️

Step 2:Domain(本体)に “ルール” を閉じ込める💎🔒

例:ToDo は「締切が過去なら登録できない」とか「完了したら完了日時を持つ」とかね😊

// Domain/TodoItem.cs
namespace MyApp.Domain;

public sealed class TodoItem
{
public string Title { get; }
public DateTime DueAt { get; }
public bool IsCompleted { get; private set; }

public TodoItem(string title, DateTime dueAt, DateTime now)
{
if (string.IsNullOrWhiteSpace(title)) throw new ArgumentException("タイトル必須だよ🥺");
if (dueAt < now) throw new ArgumentException("締切が過去はダメだよ⏳💦");

Title = title;
DueAt = dueAt;
}

public void Complete()
{
IsCompleted = true;
}
}

✅ ここがポイント

  • Domain は「時間の取り方」を知らない(外の世界を知らない)🛡️
  • だから「now」は外から渡してるよ(第11〜13章の考え方が効く!)🎁✨

Step 3:Application に “やりたいことの流れ” を置く🎬✨

Application は「手順の司令塔」👑 ここで “外の世界” は インターフェイス(契約) にするよ🔌✨

// Application/Ports.cs
namespace MyApp.Application;

public interface IClock
{
DateTime Now { get; }
}

public interface ITodoRepository
{
void Add(MyApp.Domain.TodoItem item);
}
// Application/AddTodoUseCase.cs
namespace MyApp.Application;

public sealed class AddTodoUseCase
{
private readonly IClock _clock;
private readonly ITodoRepository _repo;

public AddTodoUseCase(IClock clock, ITodoRepository repo)
{
_clock = clock;
_repo = repo;
}

public void Execute(string title, DateTime dueAt)
{
var item = new MyApp.Domain.TodoItem(title, dueAt, _clock.Now);
_repo.Add(item);
}
}

✅ ここがポイント

  • Application は Domain を使う🎬➡️💎
  • でも DB の具体実装は知らない(Repository は契約だけ)🔌✨

Step 4:Infrastructure に “外の世界の実装” を置く🔌🌍

ここで初めて「実際の時計」「実際の保存先」を書くよ〜🧱✨

// Infrastructure/SystemClock.cs
namespace MyApp.Infrastructure;

public sealed class SystemClock : MyApp.Application.IClock
{
public DateTime Now => DateTime.Now;
}
// Infrastructure/InMemoryTodoRepository.cs
namespace MyApp.Infrastructure;

public sealed class InMemoryTodoRepository : MyApp.Application.ITodoRepository
{
private readonly List<MyApp.Domain.TodoItem> _items = new();

public void Add(MyApp.Domain.TodoItem item) => _items.Add(item);
}

✅ ここがポイント

  • Infrastructure は Application の契約を実装する(外側が合わせに行く)🤝✨
  • Domain 側は「保存の仕方」を一切知らない🛡️

Step 5:UI は “組み立て役(Composition Root)” を持つ🖥️🏗️

最後に UI 側で「パーツを組み立てて」動かすよ〜😊✨

// Presentation/Program.cs
using MyApp.Application;
using MyApp.Infrastructure;

var clock = new SystemClock();
var repo = new InMemoryTodoRepository();

var useCase = new AddTodoUseCase(clock, repo);

Console.Write("タイトル:");
var title = Console.ReadLine() ?? "";

Console.Write("締切(例 2026-01-20):");
var dueText = Console.ReadLine() ?? "";
var dueAt = DateTime.Parse(dueText);

useCase.Execute(title, dueAt);

Console.WriteLine("登録できたよ〜🎉✅");

🎉 これで「4箱+依存の向き」が完成!


7) “分けただけ”で終わらない!境界が壊れない工夫🧱🔒

✅ ルール1:Domain から外側参照が出たら即ストップ🛑

例:Domain に DB クラス名、HTTP、ファイル、Console が出たら赤信号🚨

✅ ルール2:public は最小限に✨

「外に出すのは契約(インターフェイス)と本当に必要な型だけ」🔒 (C# の access modifiers の基本もここが肝だよ)(Microsoft Learn)

✅ ルール3:テストで境界を守る(発展)🧪🛡️

「うっかり参照しちゃった😇」を自動で検出できると強い!

例:アーキテクチャテスト用に NetArchTest みたいなライブラリを使うと、依存ルールをユニットテストでチェックできるよ✅(GitHub) (ここは発展なので、“できたらラッキー”でOK🎀)


8) AI活用(この章は2つだけ)🤖✨

AIプロンプト1(仕分け)📦

「この機能を4箱(UI/App/Domain/Infra)にどう仕分ける?理由も」

✅ チェックすること🧠

  • Domain に「外の世界」が混ざってない?🛡️
  • Application が “手順” に寄ってる?(ルールを抱えすぎてない?)🎬

AIプロンプト2(依存の向き監査)🧭

「依存の向きが変になりそうな箇所を指摘して」

✅ チェックすること🧠

  • “Domain → Infra” の矢印が生まれてない?🚫
  • UI が Infrastructure を直呼びしてない?(Application 経由が基本)🖥️➡️🎬

C# 14 / .NET 10 まわりの最新情報も押さえつつ進めると安心だよ(最新SDKやIDEで試せるって公式にも明記されてる)(Microsoft Learn)


9) まとめ🎀✨(この章でいちばん大事な3行)

  • 4箱で置き場所を固定📦📦📦📦
  • **依存は内側へ(Domain を守る)**🛡️➡️
  • public を絞って“外に出す顔”を小さく🔒✨

次の第17章では、この4箱ルールを使って ミニプロジェクトを完成させるよ〜💪🎉