第24章 Inbound Adapter①:Controllerは薄く!🌐🙂
この章はひとことで言うと、**「Controllerを“ただの受付”にする」**練習だよ〜!🧾➡️🧠➡️✅ 受付が薄いほど、変更に強くなるし、テストもしやすくなるの😊💕
1) 今日のゴール🎯✨

✅ Controllerがやること(受付の仕事)💁♀️
- HTTPリクエストを受け取る📩
- 入力DTOを受ける(形のチェック)🧩
- Port(UseCase)を呼ぶ📞
- 結果をHTTPレスポンスにする📤
❌ Controllerがやっちゃダメなこと(中身の仕事)🙅♀️
- 業務ルールの判断(例:割引条件、在庫ルール)🧠💥
- DBアクセス(Repository呼び出し)🗄️💥
- ドメインの生成ロジック盛り盛り(複雑な組み立て)🧱💥
- 例外の握りつぶし・謎の変換(とりあえず200返す等)😇💥
2) “薄いController”の合言葉🪄✨
Controllerは、これだけでOK👇(テンプレ6手)😆
- DTOを受け取る📦
- 必要なら軽い形式チェック✅
- UseCase入力に変換🔁
- Port(UseCase)を呼ぶ📞
- 出力をレスポンスDTOに変換🔁
- HTTP結果を返す📤
それ以上は 外へ追い出す!🏃♀️💨
3) バリデーションの置き場所ルール✅🧠
ここ、超大事だよ〜!📌✨
① 形式(入力の形)バリデーション:Inbound側🧾✅
例:
- 必須項目が空(null/空文字)
- 数字の範囲(1〜99)
- 文字数(1〜50)
ASP.NET Coreの [ApiController] を付けると、モデル検証エラーは 自動で400 になるから、Controllerで ModelState.IsValid を毎回書かなくていいよ〜!楽ちん!😆✨ (Microsoft Learn)
② 業務(意味)バリデーション:Core(Domain/UseCase)🛡️❤️
例:
- 「注文は営業時間内だけ」
- 「同一注文に同じ商品を二重追加禁止」
- 「金額の合計が負にならない」
これはUI都合じゃなくてルールだから、Coreに置くのが強い💪✨
4) 変換(DTO ↔ Core)の置き場所🔁🧼
変換はAdapterの大事なお仕事だよ〜!🧹✨ 外の都合(HTTP/JSON)を、Coreに持ち込まないための“フィルター”😤💕
- Request DTO → UseCase Input(Coreが欲しい形)
- UseCase Output → Response DTO(外へ出す形)
「変換が散らばる」とControllerが太りがちだから、Mapper(変換クラス)を用意すると超気持ちいいよ😊🌿
5) 実装例:OrdersController(薄い版)☕🧾✨
前提:すでに Core 側に Inbound Port(UseCase interface) と UseCase用DTO がある想定だよ!(第22〜23章の続き)🙂
5-1. Core側(Port)イメージ🧠🔌
public interface ICreateOrderUseCase
{
Task<CreateOrderResult> HandleAsync(CreateOrderCommand command, CancellationToken ct);
}
public sealed record CreateOrderCommand(
string CustomerName,
IReadOnlyList<CreateOrderItem> Items
);
public sealed record CreateOrderItem(string MenuId, int Quantity);
public sealed record CreateOrderResult(string OrderId);
ここは Core。HTTPを一切知らないのがポイント🛡️✨
5-2. Inbound側:Request/Response DTO📦✨
using System.ComponentModel.DataAnnotations;
public sealed record CreateOrderRequest(
[property: Required, StringLength(50, MinimumLength = 1)]
string CustomerName,
[property: Required, MinLength(1)]
List<CreateOrderItemRequest> Items
);
public sealed record CreateOrderItemRequest(
[property: Required, StringLength(50, MinimumLength = 1)]
string MenuId,
[property: Range(1, 99)]
int Quantity
);
public sealed record CreateOrderResponse(string OrderId);
[ApiController] なら、この DataAnnotations の検証エラーは 自動400 にしてくれるよ〜!😆✨ (Microsoft Learn)
(エラーフォーマットは ValidationProblemDetails が使われる流れだよ🧩) (Microsoft Learn)
5-3. 変換Mapper(Controllerを痩せさせる)🔁🧼
public static class OrderDtoMapper
{
public static CreateOrderCommand ToCommand(this CreateOrderRequest req)
=> new(
CustomerName: req.CustomerName,
Items: req.Items.Select(x => new CreateOrderItem(x.MenuId, x.Quantity)).ToList()
);
public static CreateOrderResponse ToResponse(this CreateOrderResult result)
=> new(result.OrderId);
}
5-4. Controller本体(薄い!)🌿✨
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/orders")]
public sealed class OrdersController : ControllerBase
{
private readonly ICreateOrderUseCase _createOrder;
public OrdersController(ICreateOrderUseCase createOrder)
{
_createOrder = createOrder;
}
[HttpPost]
public async Task<ActionResult<CreateOrderResponse>> Create(
[FromBody] CreateOrderRequest request,
CancellationToken ct)
{
// ① DTOを受ける([ApiController] が形式チェックを助けてくれる)
// ② UseCase入力に変換
var command = request.ToCommand();
// ③ Port呼び出し
var result = await _createOrder.HandleAsync(command, ct);
// ④ 返す形に変換して返却
var response = result.ToResponse();
return CreatedAtAction(nameof(GetById), new { orderId = response.OrderId }, response);
}
[HttpGet("{orderId}")]
public ActionResult GetById(string orderId)
=> Ok(new { OrderId = orderId }); // ← ここは後の章でちゃんと実装しよう😊
}
見て見て!👀✨ Controllerの中に「業務ロジック」「DB」「謎の変換」が無い!最高!🎉😆
6) よくある“太る原因”と治し方🍔➡️🥗
🍔 太る原因①:Controllerで「ビジネス判断」しちゃう
✅ 治し方:UseCaseへ移動(Coreへ)🛡️
🍔 太る原因②:ControllerがRepositoryを呼ぶ
✅ 治し方:UseCaseがRepository Portを使う構造に戻す🔌🗄️
🍔 太る原因③:変換がControllerにベタ書き
✅ 治し方:Mapperへ隔離(Adapterの仕事)🧼🔁
🍔 太る原因④:ModelStateチェック祭り
✅ 治し方:[ApiController] の自動400を活かす(毎回書かない)😆✨ (Microsoft Learn)
7) AI活用(Copilot/Codex)で爆速にするコツ🤖💨✨
✅ おすすめプロンプト例(そのまま貼れる)📋✨
- 「ASP.NET Core WebAPIのControllerを作って。
ICreateOrderUseCaseをDIで受け取り、Controllerは変換→UseCase呼び出し→返却だけにして。業務ロジックは入れないで。」 - 「Request/Response DTOをDataAnnotationsで作って。
[ApiController]前提で、検証エラーは自動400に任せたい。」
Visual Studio 2026 は Copilot まわりの統合も強化され続けてるので、雛形生成→人間が境界ルールを守るがやりやすいよ〜!🤖✨ (Microsoft Learn)
8) ミニ演習🧪✨(めちゃ効く!)
お題☕🧾
「太ったController」を想像して、次を全部外へ追い出してみてね👇😆
- if文だらけの業務判断 → UseCase
- DB保存 → Outbound Port/Adapter
- 変換ぐちゃぐちゃ → Mapper
- 形式チェック → DTO + DataAnnotations
9) 仕上げチェックリスト✅✨
- Controllerが Port以外(RepositoryやDB)を触ってない?🗄️🚫
- Controllerに「業務判断」が書いてない?🧠🚫
- 変換がMapperに寄ってる?🔁✅
[ApiController]の自動400を活かしてる?😆✅ (Microsoft Learn)- CoreはHTTPを知らない?🛡️✅
おまけ:今どきの基準のひとことメモ📝✨
いまの .NET は .NET 10 が最新LTSとして提供されていて、2026-01-13時点で 10.0.2 が出てるよ〜!🪟✨ (Microsoft) (C# 14 は .NET 10 以降でサポート、って整理になってるよ📌) (Microsoft Learn)
次の章(第25章)は、いよいよ Outbound Port(Repositoryの約束) を作って「Coreが外を知らない」をさらに固めるよ〜!🔌🗄️✨