第61章:アプリケーションサービス(ユースケースを実現する層)🧩✨

DDDって聞くと「値オブジェクト!集約!リポジトリ!」ってパーツに目が行きがちなんだけど… 実はそれらを “ちゃんと動くアプリ” にまとめ上げる司令塔 が必要なんだよね🙂↕️🚦
それが アプリケーションサービス(Application Service)だよ〜!🎉
1. アプリケーションサービスって何する人?👩💼🧠
一言でいうと、
「ユーザーがやりたいこと(ユースケース)」を、ドメインの部品を使って実現する係 ✅
たとえばユースケースってこういうやつ👇
- 会員登録する 📝
- 商品を注文する 🛒
- タスクを追加する ✅
- 予約をキャンセルする 📅❌
アプリケーションサービスは “手順を組み立てる人” で、 ビジネスルールそのものは、できるだけドメイン(Entity/VO/Aggregate)に持たせる のがコツだよ🙂✨
2. どこまでが担当?(超重要)🧭
アプリケーションサービスの担当はだいたいこれ👇
- 入力(Command/DTO)を受け取る 📩
- 必要な集約をリポジトリから取ってくる 🔍
- 集約に「命令」する(メソッド呼ぶ)📣
- 保存する 💾
- 必要なら通知する(ドメインイベント発火後の処理など)📢
- 結果(DTO/Result)を返す 🎁
逆に、やりすぎ注意なのはこれ👇⚠️
ifだらけでビジネスルールを書き始める(ドメインが空っぽになる)😵💫- DBのSQLやEFの細かい都合が漏れる(アプリ層がインフラに汚染される)🧪
- “神クラス” 化して巨大になる(何でも屋になる)🧟♂️
3. ありがちな混乱ポイント(ここで整理)🧹✨
✅ Controller(画面・APIの入り口)
- HTTPとか画面の都合を扱う係 🌐
- できれば薄くする(受付→サービス呼ぶだけ)🍃
✅ Application Service(今回の主役)
- ユースケースを実現する係 🎮
- “手順” を書く(オーケストレーション)🎻
✅ Domain(Entity/VO/Aggregate/Domain Service)
- ルールを守らせる係 🧠
- “正しさ” の中心 ❤️
✅ Infrastructure(DBや外部API)
- 現実の道具を扱う係 🛠️
- 交換可能にしておきたい 🔁
4. ミニ例で体感しよう:タスクを追加する ✅📝
「タスク管理アプリ」で、ユーザーがやりたいことは
「タスクを追加したい!」
だよね🙂 これをDDDっぽく分けるとこうなるよ👇
- Domain:
TodoList集約が「タスク追加」を正しく処理する ✅ - Application:
AddTaskの手順(取得→命令→保存)を書く 🧭 - Infrastructure:保存先(DBとか)💾
- Web:APIや画面 🌐
5. コード例:薄いアプリケーションサービスの正解形🌸
Domain(集約)側:ルールはこっちに寄せる🧠
public sealed class TodoList
{
private readonly List<TodoItem> _items = new();
public Guid Id { get; }
public IReadOnlyList<TodoItem> Items => _items;
public TodoList(Guid id) => Id = id;
public Result AddTask(string title)
{
if (string.IsNullOrWhiteSpace(title))
return Result.Fail("タイトルは必須だよ🥺");
if (title.Length > 50)
return Result.Fail("タイトルは50文字までだよ✂️");
_items.Add(new TodoItem(Guid.NewGuid(), title.Trim(), isDone: false));
return Result.Ok();
}
}
public sealed record TodoItem(Guid Id, string Title, bool IsDone);
注目ポイント👀✨ 「タイトル必須」とか「50文字まで」は ドメインの責任 にしてるよ!
Application(アプリケーションサービス)側:手順を書く🚦
public sealed class TodoApplicationService
{
private readonly ITodoListRepository _repo;
public TodoApplicationService(ITodoListRepository repo)
=> _repo = repo;
public async Task<Result> AddTaskAsync(AddTaskCommand command, CancellationToken ct)
{
// 1) 対象の集約を取得する
var list = await _repo.GetByIdAsync(command.TodoListId, ct);
if (list is null)
return Result.Fail("対象のリストが見つからないよ😢");
// 2) 集約に命令する(ルール判断はドメイン側)
var result = list.AddTask(command.Title);
if (!result.IsSuccess)
return result;
// 3) 保存する
await _repo.SaveAsync(list, ct);
// 4) 結果を返す
return Result.Ok();
}
}
public sealed record AddTaskCommand(Guid TodoListId, string Title);
ここが超大事💡 アプリケーションサービスは 薄い!
list.AddTask(...)に命令して、結果見て、保存してるだけ🙂↕️✨
Repository(インターフェイスだけ)🧱
public interface ITodoListRepository
{
Task<TodoList?> GetByIdAsync(Guid id, CancellationToken ct);
Task SaveAsync(TodoList list, CancellationToken ct);
}
Result(超ミニ版)🎁
public sealed class Result
{
public bool IsSuccess { get; }
public string? Error { get; }
private Result(bool isSuccess, string? error)
=> (IsSuccess, Error) = (isSuccess, error);
public static Result Ok() => new(true, null);
public static Result Fail(string error) => new(false, error);
}
6. 「薄さ」を守るためのチェックリスト✅✨
アプリケーションサービスを書いたら、これ見てね🙂
- ルールの
ifが大量にない?(ドメインに寄せられるかも)🧠 - “取得→命令→保存” になってる?🚦
- DB都合(EFのEntity直触り等)が漏れてない?🧪
- メソッド名がユースケースになってる?(例:
AddTask)📝 - 1メソッドが長編小説になってない?📚💥
7. AIに手伝ってもらうコツ(めちゃ相性いい)🤖💕
アプリケーションサービスは「手順」が中心だから、AIがかなり得意だよ!
こんな感じで頼むと気持ちよく出る🙂✨
- 「アプリケーションサービスは薄く、取得→命令→保存で」
- 「ビジネスルールはドメイン側に寄せて」
- 「失敗は
Resultで返して、例外乱発しないで」 - 「Command/DTOを作って」
プロンプト例👇
TodoList集約に AddTask(title) がある前提で、
アプリケーションサービス AddTaskAsync を作って。
責務は「取得→命令→保存」で薄く。
失敗は Result で返して。
8. ミニ演習🎯✅(すぐできる!)
次のユースケースを、同じ形で作ってみてね😊💕
お題A:タスク完了にする ✅
TodoListにCompleteTask(taskId)を追加- Application Service に
CompleteTaskAsync(command)を追加
お題B:タスク名を変更する ✏️
RenameTask(taskId, newTitle)- 文字数チェックなどのルールはドメイン側へ🧠
まとめ🌟
- アプリケーションサービスは ユースケースの司令塔 🚦
- 仕事はだいたい 取得→命令→保存 🧭
- ルールはドメインに寄せる(アプリ層は薄く!)🧠✨
- AIに頼むなら「薄く!ルールはドメイン!」って言うのが勝ち🤖🏆
次の第62章(DTO)に行くと、 「画面に返す形」と「ドメインの形」をもっとキレイに分けられて、気持ちよさが倍増するよ〜🥰📦