第22章 Outbound Port:Repositoryを決める 💾🔌
![hex_ts_study_022[(./picture/hex_ts_study_022_mocking_stubbing_for_tests.png)
この章はひとことで言うと、**「アプリの中心(ユースケース)が、保存先の都合に振り回されないための“約束”を決める回」**だよ〜😊🌸 Repository は “保存のお願い窓口”(Outbound Port)って感じ!🔌💖
1. 今日のゴール 🎯✨
この章が終わったら、次ができるようになるよ😊
- ✅ Repository(Outbound Port)が なぜ必要か 説明できる
- ✅ ToDoアプリに必要な 最小のRepositoryインターフェース を自分で決められる
- ✅ 「Repositoryが何でも屋になる事故」を 未然に防げる 🚫🧹
2. Repositoryってなに?(超やさしく)🧺💡
Repository は、ざっくり言うと
「ドメインのオブジェクトを“メモリ上のコレクションみたいに扱える”ようにする仕組み」
ってイメージでOK😊🧺
「配列っぽく 追加/取得/検索 できるように見せる」みたいな発想だね✨
(この“コレクションっぽい”説明は Martin Fowler の定義が有名だよ)(martinfowler.com)
そして重要なのがここ👇
- Repository は DBのためのクラス じゃない
- Repository は 中心(ドメイン/ユースケース)のための“契約(interface)” 🔌✨
- 実装(DB/ファイル/メモリ)は外側(Adapter)が担当 🧩💪
「中心に interface(抽象)を置いて、外側が実装する」って考え方は、RepositoryをDDDの文脈で説明する資料でもよくこう書かれるよ📌(Microsoft Learn)
3. なんで“Outbound Port”として切るの?🧭🔥
ヘキサゴナルの気持ちよさはここ😍
- 中心は 「保存が必要」 だけ知ってればいい
- 「どこに」「どうやって」保存するか は外側が吸収する
つまり中心はこう言うだけ:
「Todoを保存して」🔌 「idでTodoちょうだい」🔌
この “お願いの形”が Port(interface) だよ〜😊✨ 中心が 自分に都合のいい形でPortを定義できる のが強い💪 (Hexagonal の説明でも “coreは自分が定義したportだけに依存する” みたいに語られるよ)(Architectural Metapatterns)
4. Repository Port の決め方(手順)🪜✨
Repository って、最初にDBを見て決めるとだいたい失敗する😵💫💥 うまくいく順番はこれ👇
手順A:ユースケースから必要な操作だけ拾う 🧠➡️🔌
今回のToDoユースケース(例):
- AddTodo ✅
- CompleteTodo ✅
- ListTodos ✅
ここから “保存に必要なこと” を抜き出すよ📝✨
- AddTodo:保存したい →
save(todo) - CompleteTodo:idで探す →
findById(id)、更新して保存 →save(todo) - ListTodos:一覧ほしい →
list()
はい、もうこれでほぼ完成😆🎉 Portは「ユースケースが必要な分だけ」でOK!
5. よくある事故(先に潰す)🚨😇
❌ 事故①:なんでもRepository 🧹💥
「RepositoryってCRUD全部あるよね?」でこうなる👇
create/update/delete/find/search/rawQuery/executeSql...😵💫- さらに「便利メソッド」増殖で地獄🐘
症状:
- どのユースケースもRepositoryに依存しまくり
- 仕様変更のたびにRepositoryが太る🍔
- テストもつらい😇
✅ 対策:Repositoryは“最小の約束”✂️🔌
- 今のユースケースに必要な分だけ
- “将来使うかも”は一旦いらない🙅♀️✨
6. Repository Port の形(TSでのおすすめ)🧩✨
ポイント1:Repositoryは“ドメイン型”を扱ってOK 🧠💞
Repositoryは 中心側の契約 だから、返すのが Todo でも全然OK😊
(DBの行とかORMモデルは外側の話!)
ポイント2:非同期(Promise)に寄せるのがおすすめ ⏳✨
InMemoryでも Promise にしておくと、後でDB/ファイルに差し替えてもユースケースが無傷😍
(I/Oは将来ほぼ非同期になるからね)
7. 実装してみよう:TodoRepositoryPort 💾🔌
フォルダ例:src/app/ports/TodoRepositoryPort.ts みたいな感じ📁✨
(置き場所はプロジェクト流儀でOKだよ😊)
// src/app/ports/TodoRepositoryPort.ts
import { Todo } from "../../domain/todo/Todo";
import { TodoId } from "../../domain/todo/TodoId";
export interface TodoRepositoryPort {
/** Todoを保存する(新規でも更新でもOKにするのが簡単✨) */
save(todo: Todo): Promise<void>;
/** idで1件取得(なければnull) */
findById(id: TodoId): Promise<Todo | null>;
/** 一覧取得(まずはシンプルに全件でOK😊) */
list(): Promise<Todo[]>;
}
findByIdの “見つからない” をnullにするのは、最初は分かりやすいからおすすめ😊 (エラー設計をしっかりやるのは後の章で育てよう🌱✨)
8. ユースケース側はこうなる(中心がスッキリ😍)🧠✨
例:CompleteTodo の雰囲気👇
// src/app/usecases/CompleteTodo.ts
import { TodoId } from "../../domain/todo/TodoId";
import { TodoRepositoryPort } from "../ports/TodoRepositoryPort";
export class CompleteTodo {
constructor(private readonly repo: TodoRepositoryPort) {}
async execute(input: { id: string }): Promise<void> {
const id = TodoId.fromString(input.id);
const todo = await this.repo.findById(id);
if (!todo) {
// ここは後の章で「仕様エラー」にしていくと超キレイ✨
throw new Error("Todo not found");
}
todo.complete(); // ← ドメインのルール(例:二重完了禁止)✨
await this.repo.save(todo);
}
}
見て見て!👀✨ ユースケースは DBもファイルも一切知らない 🙅♀️💕 「依存の向き」守れてる感が気持ちいい〜〜〜😆🛡️
9. “良いRepository Port”チェックリスト ✅🔎✨
Repositoryを決めたら、これでセルフチェックしてね😊
- ✅ メソッド名が ユースケースの言葉 になってる?(技術用語まみれじゃない?)🗣️
- ✅ 最小限?(今のユースケースが使ってない操作が混ざってない?)✂️
- ✅ 引数/戻り値に DB都合の型 が混ざってない?(Row/ORM/SQLなど)🚫
- ✅ “便利そう”で
query(sql: string)とか生やしてない?(だいたい破滅😇)🧨 - ✅ 例外や失敗の表現が 一貫 してる?(null / throw / Resultが混在してない?)🧯
10. AI(Copilot/Codex)に頼むなら、ここだけ頼ろう🤖✨
AIに手伝ってもらうのはめっちゃOK!😍 でも Port設計の芯 は人間が握ろう🛡️✨(ここ崩れると全部崩れる😇)
✅ 使ってOK:候補出し・命名
- 「このユースケースに必要なRepositoryメソッド候補を3案出して」
- 「命名をユビキタス言語寄りに直して」
⚠️ 注意:AIがやりがち事故
- いきなり
GenericRepository<T>を提案してくる(だいたい要らない😂) - CRUD全部盛りを提案してくる(太る🍔)
コピペ用プロンプト(おすすめ)📝🤖
次のユースケースに必要な「最小のRepository interface」を提案して。
- AddTodo: 追加
- CompleteTodo: 完了(id指定)
- ListTodos: 一覧
注意:
- CRUD全部は不要
- DB都合の型(Row/ORM/SQL)を出さない
- メソッド数は最小(3〜4個まで)
11. ミニ課題(手を動かすやつ🖐️🎀)
課題A:Repository Port を “今の仕様だけ” で完成させる 💾✨
save / findById / listの3つだけで一旦完成にする😊
課題B:Listに “完了/未完了フィルタ” を追加してみる 🔍✅
やるならこういう方向がオススメ👇(増やしすぎ注意!)
list(filter?: { completed?: boolean }): Promise<Todo[]>;
「ユースケースが必要になったら足す」🌱 これで勝ち✌️✨
12. おまけ:2026/01/23 時点の“周辺メモ”🗓️✨
- Node.js は v24 系が Active LTS、v25 系が Current という位置づけだよ📌(Node.js)
- 2026年1月中旬に 24.13.0 (LTS) のセキュリティリリース も出てるよ🔐(Node.js)
- TypeScript は 2025年末の公式ブログで 6.0が“JS実装として最後の系統”、7系へ進む話が出てるよ🧠✨(Microsoft for Developers)
(この章の本題はRepository設計だから、ここは軽くでOKだよ😊🌸)
まとめ 🎁💖
Repository(Outbound Port)はこう覚えよう😊🔌
- Repositoryは中心の“お願い窓口” 💾🔌
- ユースケースから逆算して最小にする ✂️✨
- DB都合を中心に入れない 🙅♀️🛡️
- 太ったら負け 🍔⚠️
次の章では、Repository以外の小さな外部依存(Clock/UUIDなど)も同じノリで切って、テストが急にラクになる魔法をかけるよ〜〜🪄🧪✨