第30章:移行&卒業制作:既存モノリスを育てて完成🏁🎉
今日のゴール🎯
- 既存モノリスを止めずにモジュール化していく「段階移行」のやり方を身につける🩹✨
- 「最初に切る境界」を自分で選べるようになる🧠🧱
- 最終課題(卒業制作)で、あなたの題材をモジュール化して提出できる状態にする📦🎓
まずダメ例😇 → 良い例😎
ダメ例😇(ビッグバン移行)💥
- いきなり全部リライトする
- 途中で仕様変更が来て詰む😵
- 「完成まで価値ゼロ」になりがち🫠
- だいたい終わる前に炎上🔥
良い例😎(段階移行)🩹✨
- 小さく区切って、出せるところから出す
- 既存を“絞め殺す木”みたいに少しずつ置き換える(Strangler Fig)🌿 置き換えを小さなリリースで積み上げるのがコツだよ📦 (martinfowler.com)
- モノリスの中身を差し替えるときは「Branch by Abstraction」も超便利🧩 大きな変更を進めながらも、継続リリースできるのが強い💪 (martinfowler.com)
今回の“最新スタメン”だけ押さえよ📌✨
- **.NET 10(LTS)**が稼働中で、2026-01-13時点のパッチは 10.0.2(LTSは長く使えるのが強み)🛡️ (Microsoft)
- C# 14は「最新のC#」で、.NET 10でサポートされてるよ🧠✨ (Microsoft Learn)
- Visual Studio 2026 18.2.0(2026-01-13リリース)が最新アップデートの一つだよ🪟🛠️ (Microsoft Learn)
段階移行の“3大テク”🍣✨(これだけで戦える)

1) Strangler Fig(外側から置き換え)🌿
- 入口(UI/API/ルーティング)に“分岐”を置いて 新しい実装に少しずつ流すイメージだよ🚦 (Microsoft Learn)
2) Branch by Abstraction(中身を差し替え)🧩
- 既存の呼び出しをいったんインターフェースに寄せて、 旧実装と新実装を並走させて切り替えるやつ🔁 (martinfowler.com)
3) ACL(アンチコラプションレイヤ)🧼🧱
- レガシー側の言葉・型・都合が、新モジュールに侵入して“汚染”するのを防ぐ💦
- 翻訳はACLでやって、モジュールの中は自分の言葉を守る✨ (Microsoft Learn)
“最初に切る境界”の選び方🧠🪓(迷ったらこの採点でOK)
次の5項目を、各0〜2点で採点して合計が高いものから✨
- 痛みが強い(変更が怖い・バグが多い・改修頻度が高い)😵💫
- 責務がわかりやすい(チーム内で説明が揃う)📣
- 依存が少ない(周りを巻き込みにくい)🧵
- データの所有者が決めやすい(テーブル/集約の責任が見える)🗃️
- 価値が早く出る(1〜2週間で“何か出せる”)📦✨
いきなり「一番デカい塊」を切らないでね😇 まずは“学びが取れて、爆発しにくい”ところからでOK🧯
段階移行のロードマップ🏁(この順でやると事故りにくい)
Step 0:安全装置を付ける🛡️
- まずはテストの最低ライン(主要ユースケースのE2E or APIテスト)を用意🧪
- ログ・メトリクスも最低限(失敗の検知が早いと強い)🔭
Step 1:境界を“紙に描く”🖍️
- 依存の矢印をざっくり描く(誰が誰を呼んでる?)👀
- “言葉が衝突する場所”を特定(Orderが意味違う…みたいな)🧱
Step 2:モジュールの「窓口」だけ決める🪟
- まず実装じゃなくて、**公開API(窓口)**を決める📣
- 内部のクラスは見せない(DTOやコマンドで話す)📦
Step 3:ACLで“翻訳壁”を作る🧼🧱
- レガシー↔新モジュール間は、いきなり直結しない
- 変換・吸収はACLでまとめておく(あとで剥がせる)🧽 (Microsoft Learn)
Step 4:Branch by Abstractionで“差し替えポイント”を作る🔁
- 旧処理を呼んでる所をインターフェース経由に寄せる
- 新実装を入れて、段階的に切り替える🧩 (martinfowler.com)
Step 5:データ境界を“守れる形”にする🗃️🔒
- テーブル共有が残るなら、まずは「書き込み責務」を1つに固定
- 直接UPDATEし合うのは最初に潰す🧯
Step 6:参照ルールを“自動で怒る”ようにする😠➡️✅
- 人の注意だけに頼らず、アーキテクチャテストで破りを検出🔍
- 例:NetArchTest系で「HostはModuleのInternalに触るな」みたいなルールにする📏 (GitHub)
Step 7:旧コードを消して、境界を固める🧱✨
- 置き換え終わったら旧実装を削除(残すと復活する😇)
- 「次に切る候補」を同じ手順で繰り返す🔁
手を動かす(C#)⌨️✨:移行の最小“型”を作ろう
ここでは「1モジュール目」を切るための“型”だけ作るよ🧩 (あなたの題材に合わせて名前は変えてOK😊)
1) 公開API(窓口)を先に作る🪟
// Modules/Ordering/Public/IOrderingApi.cs
namespace MyApp.Modules.Ordering.Public;
public interface IOrderingApi
{
Task PlaceOrderAsync(PlaceOrderCommand command, CancellationToken ct);
}
public sealed record PlaceOrderCommand(
Guid CustomerId,
IReadOnlyList<LineItemDto> Items
);
public sealed record LineItemDto(Guid ProductId, int Quantity);
ポイント📌
- “内部のEntityやDbContext”を返さない🙅♀️
- まずは コマンド/DTOで会話させると事故が減るよ📦✨
2) Branch by Abstractionの差し替え口を作る🧩🔁
// Legacy/Ordering/OrderingFacade.cs (Host側でもOK)
public interface IOrderingFacade
{
Task PlaceOrderAsync(PlaceOrderRequest req, CancellationToken ct);
}
// 旧実装(当面は既存コードを呼ぶだけ)
public sealed class LegacyOrderingFacade : IOrderingFacade
{
public Task PlaceOrderAsync(PlaceOrderRequest req, CancellationToken ct)
=> LegacyOrderingService.Place(req, ct); // 既存を呼ぶ
}
// 新実装(新モジュールに寄せていく)
public sealed class ModularOrderingFacade : IOrderingFacade
{
private readonly MyApp.Modules.Ordering.Public.IOrderingApi _api;
public ModularOrderingFacade(MyApp.Modules.Ordering.Public.IOrderingApi api) => _api = api;
public Task PlaceOrderAsync(PlaceOrderRequest req, CancellationToken ct)
=> _api.PlaceOrderAsync(
new(req.CustomerId, req.Items.Select(x => new LineItemDto(x.ProductId, x.Quantity)).ToList()),
ct
);
}
- “Facade”の向こう側を、旧→新に入れ替えていくイメージだよ✨
- この考え方がBranch by Abstraction🧩 (martinfowler.com)
3) 参照ルールをテストで固定🔍✅(例:NetArchTest)
// Tests/Architecture/ModuleRulesTests.cs
using NetArchTest.Rules;
using Xunit;
public class ModuleRulesTests
{
[Fact]
public void Host_must_not_reference_module_internal()
{
var result = Types.InAssembly(typeof(MyApp.Host.Program).Assembly)
.That()
.ResideInNamespace("MyApp.Host")
.ShouldNot()
.HaveDependencyOn("MyApp.Modules.Ordering.Internal")
.GetResult();
Assert.True(result.IsSuccessful, string.Join(Environment.NewLine, result.FailingTypeNames));
}
}
- 「人が気をつける」より「テストが怒る」が強い😆🛡️ (GitHub)
ミニ演習📝✨(15〜30分でOK)
- あなたの既存モノリスで「一番痛い機能」を1つ選ぶ😵💫➡️🎯
- その機能の“窓口”を I〜Api で1つ定義する🪟
- 旧実装を Facade で包む(今は中身そのままでOK)📦
- アーキテクチャテストを1本だけ追加する🔍✅
- 旧→新に差し替える「順番」を箇条書きで書く🗒️✨
卒業制作(最終課題)✨🎓:あなたの題材でモジュール化して提出!
必須要件✅
-
モジュール 2つ以上(例:Catalog / Ordering でもOK)📦📦
-
各モジュールに「公開API(窓口)」がある🪟
-
モジュール境界のルールを自動チェック(アーキテクチャテスト等)🔍
-
“既存コード(またはレガシー相当)”から、段階移行した形跡がある🩹🔁
- Strangler Fig か Branch by Abstraction のどちらか(両方なら最高)🌿🧩 (martinfowler.com)
できたら加点✨(おすすめ)
- ACLで外部/レガシー連携の翻訳を隔離🧼🧱 (Microsoft Learn)
- 「最初に切った境界」を選んだ理由が説明できる(採点表を添付)🧠
- 境界の“破り”がPRで検知できる(CIで落ちる等)🚦
提出物📦
-
リポジトリ(コード)🗂️
-
README(モジュール図・境界ルール・起動方法)📝
-
1ページの「移行メモ」
- 何を最初に切った?なぜ?次は?(3行でOK)✍️
-
テスト結果(スクショでもOK)🧪📸
合格ライン(超シンプル)🎉
- 境界が言える(口頭で説明できる)
- 窓口しか通らない(直接参照が減ってる)
- ルールが自動で守られてる(テスト/CI)
AI活用プロンプト例🤖✨(コピペOK)
- 「この既存プロジェクトから、最初に切るべき境界候補を3つ出して。依存の強さ/価値/リスクで理由も。」
- 「Ordering周りの呼び出しを“Branch by Abstraction”にしたい。差し替え点の候補と、段階移行の手順を書いて。」 (martinfowler.com)
- 「レガシーDTOを新モジュールに持ち込みたくない。ACLの変換層(Mapper/Adapter)を設計して、配置場所も提案して。」 (Microsoft Learn)
- 「アーキテクチャテストで ‘HostはModules.*.Internalに依存禁止’ を追加したい。NetArchTestで例を書いて。」 (GitHub)
まとめ(覚える1行)📌✨
“いきなり全部”じゃなくて、境界を決めて、窓口を作って、少しずつ置き換える🩹🏁