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

第14章:Use Case Interactorの型を決める📐

ここでやるのは超シンプルで、でも超効くやつです💪💖 **「Use Caseは毎回この形で書く!」**を決めて、迷いとブレを消します🧹✨ テンプレが決まると、あとが爆速になるよ〜🚀😊


1) まず結論:この講座のInteractor“標準形”✅🧩

この先ずっと使う「型」はこれ👇✨

  • Use Caseは 1目的 = 1Interactor 🎯
  • 入口は Request(外側の言葉を入れない)📥
  • 出口は Response(表示都合を入れない)📤
  • 実行メソッド名は execute に統一🧠
  • 戻り値は Promise に統一(同期でも)⏳
  • 成功/失敗は Result型 で返す(例外ドカーンを減らす)🧯

UseCase Interactorの標準テンプレート図


2) 最新事情もちょい確認👀🆕(型設計に効くところだけ)

  • TypeScript は 5.9系のリリースノートで Node 向け設定(--module node20 など)を安定オプションとして用意していて、TSの“Node運用”まわりがより整理されてきてるよ🧠✨ (typescriptlang.org)
  • Node は v24 が Active LTSv25 が Current といった枝の動き(更新日含む)が公式でまとまってるよ📦 (nodejs.org)
  • テストは Vitest 側のドキュメントが 2026年1月更新になってて、普通に“今の前提”でOK🧪✨ (vitest.dev) (※さらに Vitest 4 系で Browser Mode が安定化、みたいな流れもあるよ〜🌐🧪 (InfoQ))

で、何が言いたいかというと… **「Use Case を Promise 前提にしておくと、IOやテスト戦略と噛み合ってめっちゃ楽」**ってこと🥳


3) Result型を1つ決めよう🧯✨(失敗の戻し方を統一)

第21章で「失敗の扱い」を本格的にやるけど、ここでは“器”だけ作るよ🍱✨

// usecases/_shared/Result.ts
export type Ok<T> = { ok: true; value: T };
export type Err<E> = { ok: false; error: E };
export type Result<T, E> = Ok<T> | Err<E>;

export const ok = <T>(value: T): Ok<T> => ({ ok: true, value });
export const err = <E>(error: E): Err<E> => ({ ok: false, error });

これで、Use Caseはいつもこう返せる👇😊

  • 成功:return ok(response)
  • 失敗:return err(error)

4) Use Case の“型”テンプレ(これが本体)🧱✨

4-1. 「UseCaseインターフェース」を統一する🎛️

// usecases/_shared/UseCase.ts
import type { Result } from "./Result";

export interface UseCase<Request, Response, Failure> {
execute(request: Request): Promise<Result<Response, Failure>>;
}

この1枚があるだけで、プロジェクト全体がスッキリします😌✨ 「どのUseCaseも execute で呼べる」って強い💪


5) Chapter 14の実作業:CreateTask のInteractor骨格を作る🗒️✨

ここでは「形だけ」をちゃんと作るよ🎯 (中身の実装は第18章でガッツリやる想定🧩)

5-1. Request / Response / Failure を分けて置く📦📌

// usecases/create-task/CreateTaskModels.ts
export type CreateTaskRequest = {
title: string;
};

export type CreateTaskResponse = {
taskId: string;
};

Failure(失敗)は“内側の言葉”でね⚠️💭

// usecases/create-task/CreateTaskErrors.ts
export type CreateTaskFailure =
| { type: "InvalidTitle"; message: string }
| { type: "Unexpected"; message: string };

ここでのポイント💡

  • HTTP 400 とか 500 とかは まだ絶対入れない🙅‍♀️
  • “内側の失敗”として言語化する🗣️✨

5-2. Port(必要な能力)だけ差し込む🔌✨

Interactorは Portにだけ依存するんだったね😊

// usecases/ports/TaskRepository.ts
export interface TaskRepository {
save(task: { id: string; title: string; completed: boolean }): Promise<void>;
}

(※ここは第24〜29章で洗練するけど、今は最小でOK🙆‍♀️)


5-3. Interactor(実行役)の型を完成させる🎬✅

// usecases/create-task/CreateTaskInteractor.ts
import type { UseCase } from "../_shared/UseCase";
import { err, ok, type Result } from "../_shared/Result";
import type { TaskRepository } from "../ports/TaskRepository";
import type { CreateTaskRequest, CreateTaskResponse } from "./CreateTaskModels";
import type { CreateTaskFailure } from "./CreateTaskErrors";

export class CreateTaskInteractor
implements UseCase<CreateTaskRequest, CreateTaskResponse, CreateTaskFailure>
{
constructor(private readonly repo: TaskRepository) {}

async execute(
request: CreateTaskRequest
): Promise<Result<CreateTaskResponse, CreateTaskFailure>> {
// ここでは「形」だけ。中身は第18章で完成させるよ🧩✨

const title = request.title?.trim();
if (!title) {
return err({ type: "InvalidTitle", message: "タイトルが空だよ😢" });
}

const taskId = crypto.randomUUID(); // 外側に逃がす場合は後でPort化🆔✨
await this.repo.save({ id: taskId, title, completed: false });

return ok({ taskId });
}
}

crypto.randomUUID() は「便利だけど差し替えたい」気持ちが出てくるやつ😆 それを **第26章(Id/ClockのPort化)**で“綺麗に”する流れになるよ🆔⏰✨


6) クラス?関数?どっちがいいの?🤔✨

この講座のおすすめは👇

  • 基本:クラスInteractor(依存をconstructorで見せられて分かりやすい)🏗️✨
  • ただし、依存が少ない・状態がないなら 関数Interactor でもOK🙆‍♀️

初心者のうちは 「依存が見える」=理解が早いので、まずクラスで統一が安全だよ😊💕


7) 命名と配置ルール(迷子ゼロのため)🗺️🧭

おすすめルール📌✨

  • フォルダ名:create-task / complete-task / list-tasks みたいに 動詞+目的📝

  • ファイル分割:

    • XxxModels.ts(Request/Response)📦
    • XxxErrors.ts(Failure)⚠️
    • XxxInteractor.ts(実行役)🎬
  • execute() 以外の名前を増やさない(迷いが増えるから)🧠💥


8) AI相棒🤖✨(コピペ用プロンプト)

テンプレ生成🧱

CreateTask の UseCase テンプレを TypeScript で作って。
条件:
- execute(request): Promise<Result<response, failure>> 形式
- Request/Response/Failure を別ファイルに分ける
- 外側の言葉(HTTP/SQL/Express等)を一切出さない
- repo は Port(interface) として注入する

“混ざり”チェック🧼

この UseCase 実装に「外側の都合(HTTP/DB/フレームワーク)」が混ざってないか監査して。
混ざってる行を指摘して、分離案も出して。

9) 理解チェック✅💡(1問だけ)

Q. Use Case の execute() の戻り値を Promise に統一しておくと、何が嬉しい?🧠✨ (ヒント:テスト、IO、差し替え、のどれか🎯)


10) 提出物(成果物)📦🎁

  • Result.ts / UseCase.ts_shared に作る✅

  • CreateTask

    • Models / Errors / Interactor を作って コンパイル通る状態にする✅
  • ついでに(余裕あれば)CompleteTask / ListTasks も “型だけ” 作ると最高🥳🎉


次の第15章では、この型にピッタリはまる **Requestモデル(Input Boundary)**を作っていくよ📥✨ 「外側の入力を内側に入れる“整形口”」ってやつね😊🧼💕