第56章:レイヤードアーキテクチャ 🧁✨

**〜古典的だけど、1人開発なら「これで十分」になりやすい〜**😊
今日のゴール 🎯
この章が終わったら、次ができるようになります👇✨
- 「このコード、どこに置くの?」で迷いにくくなる 🧭
- 変更に強い“置き場所ルール”を持てる 🧱
- AIに頼んでも構造がグチャらない指示ができる 🤖💡
1. レイヤードアーキテクチャってなに?🍰
一言でいうと…
役割が違うものを、層(レイヤー)で分ける設計だよ😊
ミルフィーユみたいに「層」を作って、役割が混ざらないようにします🧁✨ 混ざるとどうなるかというと… UIの都合がビジネスルールを壊したり、DBの事情で仕様が歪んだりします😱💦
2. 1人開発でレイヤードが効く理由 🏃♀️💨
1人開発って「スピード」が命だけど、未来の自分が敵になりがち…👻💦 レイヤードにすると、未来の自分がこう思えるようになるよ👇😊
- 「画面の変更?じゃあ Presentation だな」📱
- 「ルール変更? Domain だな」⚖️
- 「DB差し替え? Infrastructure だな」🗄️
つまり、変更の着地地点がすぐ分かるのが最強ポイントです✨
3. レイヤードの定番4層 🧩
DDD寄りに“今どきの分け方”でいくと、だいたいこの4つが安定です👇😊
① Presentation(表示・入口)🚪📱
- 画面、API、Controller、Endpoint、UI入力など
- “受け取って渡す”が仕事
- ルールは基本ここに置かない🙅♀️
② Application(ユースケース)🧭
- 「ユーザーがやりたいこと」を実行する係
- 例:会員登録する、注文する、予約する
- ドメインを呼び出して流れを組み立てる✨
③ Domain(ビジネスルールの中心)👑📚
- Entity / Value Object / Domain Service / Domain Event
- “このアプリの正しさ”がここ
- DBもWebも知らないのが理想😊
④ Infrastructure(外部と接続)🔌🗄️
- DB、外部API、メール送信、ファイル保存など
- Repository の実装もここに置きがち✨
(ちなみに、2025の最新C#は C# 14、対応は .NET 10 だよ🧡) (Microsoft Learn) (IDEも最新版ラインとして Visual Studio 2026 が案内されてます) (Visual Studio)
4. 「どこに置く?」迷わないルール 🧭✨
迷ったら、この質問を自分に投げてね😊
- Q1:画面やAPIの都合? → Presentation 📱
- Q2:操作の手順(ユースケース)? → Application 🧭
- Q3:業務ルール(正しさ)? → Domain 👑
- Q4:DB/外部サービス都合? → Infrastructure 🔌
そして超重要ルール👇🔥 ✅ Domain は外側(DBやWeb)を知らない これだけで世界がキレイになります✨
5. 最小サンプル:本を借りる(超ミニ)📚😊
「会員は最大5冊まで借りられる」ってルールがあるとします!
フォルダ(プロジェクト)例 🧱
MyLibrary
├─ MyLibrary.Web (Presentation)
├─ MyLibrary.Application (Application)
├─ MyLibrary.Domain (Domain)
└─ MyLibrary.Infrastructure (Infrastructure)
Domain:ルールの中心 👑
namespace MyLibrary.Domain;
public readonly record struct MemberId(Guid Value);
public readonly record struct BookId(Guid Value);
public sealed class Member
{
private readonly List<BookId> _borrowed = new();
public MemberId Id { get; }
public IReadOnlyList<BookId> Borrowed => _borrowed;
public Member(MemberId id) => Id = id;
public Result Borrow(BookId bookId)
{
if (_borrowed.Count >= 5)
return Result.Fail("これ以上借りられません(最大5冊)😢");
if (_borrowed.Contains(bookId))
return Result.Fail("同じ本を二重に借りるのはNGだよ😵💫");
_borrowed.Add(bookId);
return Result.Ok();
}
}
public sealed record Result(bool IsSuccess, string? Error)
{
public static Result Ok() => new(true, null);
public static Result Fail(string message) => new(false, message);
}
public interface IMemberRepository
{
Task<Member?> FindAsync(MemberId id, CancellationToken ct);
Task SaveAsync(Member member, CancellationToken ct);
}
👉ポイント😊
- ルール(最大5冊)はDomainに置いた👑
- DomainはDBを知らない(IMemberRepositoryだけ)✨
Application:ユースケース 🧭
using MyLibrary.Domain;
namespace MyLibrary.Application;
public sealed class BorrowBookUseCase
{
private readonly IMemberRepository _repo;
public BorrowBookUseCase(IMemberRepository repo) => _repo = repo;
public async Task<Result> HandleAsync(MemberId memberId, BookId bookId, CancellationToken ct)
{
var member = await _repo.FindAsync(memberId, ct);
if (member is null) return Result.Fail("会員が見つからないよ🥲");
var result = member.Borrow(bookId);
if (!result.IsSuccess) return result;
await _repo.SaveAsync(member, ct);
return Result.Ok();
}
}
👉ポイント😊
- Applicationは「手順」を書く場所
- ルールそのものはDomainへ委譲✨
Infrastructure:保存の実装(今回はインメモリで)🗄️
using System.Collections.Concurrent;
using MyLibrary.Domain;
namespace MyLibrary.Infrastructure;
public sealed class InMemoryMemberRepository : IMemberRepository
{
private readonly ConcurrentDictionary<MemberId, Member> _store = new();
public Task<Member?> FindAsync(MemberId id, CancellationToken ct)
=> Task.FromResult(_store.TryGetValue(id, out var m) ? m : null);
public Task SaveAsync(Member member, CancellationToken ct)
{
_store[member.Id] = member;
return Task.CompletedTask;
}
// デモ用:会員を事前に入れておく
public void Seed(Member member) => _store[member.Id] = member;
}
Presentation:入口(最小APIっぽく)🚪✨
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using MyLibrary.Application;
using MyLibrary.Domain;
using MyLibrary.Infrastructure;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<InMemoryMemberRepository>();
builder.Services.AddSingleton<IMemberRepository>(sp => sp.GetRequiredService<InMemoryMemberRepository>());
builder.Services.AddScoped<BorrowBookUseCase>();
var app = builder.Build();
// デモ用 seed
var repo = app.Services.GetRequiredService<InMemoryMemberRepository>();
var memberId = new MemberId(Guid.NewGuid());
repo.Seed(new Member(memberId));
app.MapPost("/members/{memberId:guid}/borrow/{bookId:guid}", async (Guid memberId, Guid bookId, BorrowBookUseCase useCase) =>
{
var result = await useCase.HandleAsync(new MemberId(memberId), new BookId(bookId), CancellationToken.None);
return result.IsSuccess ? Results.Ok("借りたよ〜📚✨") : Results.BadRequest(result.Error);
});
app.Run();
👉ポイント😊
- Presentationは「受け取って返す」係
- ルールをここに書き始めたら黄色信号🚥💦
6. よくある事故あるある 😇💥
😵💫 事故1:Controllerにルールが増殖
「最大5冊」判定をControllerに書き始める → 画面が増えるほど崩壊💣
😵💫 事故2:DomainがDBを知ってしまう
Domainで DbContext を使い始める → ルールがDBの都合に支配される🫠
😵💫 事故3:Applicationが巨大な神クラス
UseCaseが何百行→「結局どこ直すの?」状態に😱 👉 小さく分けるのが正義✨
7. AIに頼むときの“崩れない”指示テンプレ 🤖🧡
コピペして使ってOKだよ😊✨
- 「この機能を Presentation / Application / Domain / Infrastructure に分けて、配置理由も書いて」🧠
- 「Domainには ビジネスルールだけ、DBやWeb依存は入れないで」🚫
- 「UseCaseは手順だけ。ルールはEntityのメソッドに寄せて」🧭
- 「Controllerを薄くして。入力→UseCase→結果返すだけに」📱
8. ミニ演習 📝✨(15〜30分)
演習A:置き場所クイズ🎮
次のコードはどこに置く?(理由も!)
- 「メールアドレス形式チェック」📧
- 「注文合計金額を計算」🧾
- 「DBから注文を取得するSQL」🗄️
- 「画面の入力をDTOに詰める」📦
演習B:あなたの過去コードを3層に分ける🧹
昔の小さめプロジェクトから1画面(1機能)選んで、
- Presentation:入口
- Application:手順
- Domain:ルール に分けてみてね😊✨ AIには「どこに置くべきかレビューして」って頼むと爆速です⚡🤖
まとめ 🍀
レイヤードは派手じゃないけど、1人開発の“迷い”を消す武器だよ😊🧡 まずはこれで「置き場所の筋」を作るのが、AI時代の最短ルートです✨
次の章(オニオン)では、さらに “依存の向き” を美しくしていくよ〜🧅✨