第24章:Portって何?(内側が欲しい“能力”)🎯
この章でやることはシンプルだよ😊
「UseCase(内側)が外側にお願いしたいこと」を、Port(=インターフェース)として言語化できるようになること!💪💕
1) Portはなに?ひとことで言うと…🧠✨

**Port = 内側(UseCase)が「外側にこうしてほしい!」ってお願いするときの“窓口”**だよ🪟🔌
- 内側は「保存して」「取り出して」「通知して」みたいに**能力(やってほしいこと)**を言う
- 外側はその能力を満たす**実装(Adapter)**を差し込む
この「Port(窓口)」の考え方は、Ports & Adapters(ヘキサゴナル)でも中心アイデアで、Portは“目的のある会話(purposeful conversation)”を表すって説明されるよ📞✨ (Alistair Cockburn)
2) Portがないと、何がつらいの?😵💫🧨
たとえば、UseCaseの中でいきなりDBライブラリを触り始めたら…👇
- UseCaseがDB都合(SQL/テーブル/接続/例外)に引っ張られる🗄️💥
- テストのたびにDBが必要になって、遅い&面倒😇🧪
- DBをSQLite→別のDBに変えたいだけなのに、UseCaseまで修正地獄😱
クリーンアーキはこういう関心の分離を狙う設計で、中心(ビジネスルール)を外側の詳細から守るのが主目的だよ🛡️✨ (Clean Coder Blog) (ヘキサゴナルでも「UIやデータストアに依存せずテストできる」などが強調されるよ🧪🎉) (AWS ドキュメント)
3) PortとAdapterの関係を、ミニ図でつかむ🧩🔁
- Port:内側が欲しい能力(インターフェース)
- Adapter:外側の具体実装(SQLite版、InMemory版…)
イメージはこれ👇
- UseCase(内側) → Port(お願い口) → Adapter(外側の実装)
- 内側は「どうやるか」を知らない、ただ「これして」と言うだけ😊
4) このミニTaskアプリで、Portは何になる?🗒️🔌✨
題材のユースケースは Create / Complete / List だったよね?😊 この3つをやるとき、UseCaseが外側にお願いしたいことを抜き出すと…👇
CreateTaskが外側にお願いしそうなこと📝
- 新しいTaskを保存してほしい💾
- 新しいIDを発行してほしい(ID生成)🆔✨
- (必要なら)今の時刻を教えてほしい⏰
CompleteTaskが外側にお願いしそうなこと✅
- 対象TaskをIDで取ってきてほしい🔎
- 更新後のTaskを保存してほしい💾
ListTasksが外側にお願いしそうなこと👀
- Task一覧を取ってきてほしい📚
つまり現時点のPort候補は、だいたいこんな感じになるよ👇
TaskRepository(保存・取得・一覧)🗄️IdGenerator(ID発行)🆔Clock(時刻)⏰
注意⚠️:この章は「Portって何?」がテーマだから、APIを完璧に確定するのは次章(第25章)でOKだよ😉✨
5) Port設計のコツ:外側都合じゃなく「内側都合」💘
Portで超大事なのはこれ👇
✅ Portは「技術名」じゃなく「能力名」
- ❌
SqliteTaskRepository(技術名が入ってる) - ✅
TaskRepository/TaskStore(能力の名前)
技術が変わっても、内側が欲しいのは「保存」「取得」みたいな能力だよね😊✨
✅ Portは「どうやるか」を語らない
Portは “依頼書” だから、こういうのは書かない🙅♀️
- SQL文
- HTTPのヘッダ
- DB接続
- ライブラリ固有の型
内側は外側の詳細を知らないのが正義🛡️💕
6) TypeScriptでのPortの書き方(最小例)✍️🧩
ここでは「Port=interface」って形で書くのが一番わかりやすいよ😊
たとえば TaskRepository Port(超ミニ版)👇
// src/usecases/ports/TaskRepository.ts
import { Task } from "../../entities/task/Task";
export interface TaskRepository {
save(task: Task): Promise<void>;
findById(id: string): Promise<Task | null>;
list(): Promise<Task[]>;
}
ポイント🌟
- PortはUseCase側の言葉で書く(
Taskを中心に)💗 Promiseにしておくと、DBでもメモリでも同じ顔で扱えるよ😊(I/Oっぽさを吸収)
次に、UseCase側は 具体実装を知らずに こうやって使うだけ👇
// src/usecases/createTask/CreateTaskInteractor.ts
import { TaskRepository } from "../ports/TaskRepository";
import { IdGenerator } from "../ports/IdGenerator";
import { Task } from "../../entities/task/Task";
export class CreateTaskInteractor {
constructor(
private readonly repo: TaskRepository,
private readonly idGen: IdGenerator,
) {}
async execute(title: string): Promise<{ id: string }> {
const id = this.idGen.newId();
const task = Task.create({ id, title });
await this.repo.save(task);
return { id };
}
}
ここが気持ちいいところ😍✨ UseCaseは “repoがSQLiteか” を1ミリも知らない。だから差し替えがラクになる🎉🔁
7) Portを作る手順(迷子にならない3ステップ)🧭✨
ステップ①:UseCaseを「手順」に分解する🧩
例:CompleteTask
- Taskを取る
- 完了にする(Entityルール)
- 保存する
ステップ②:「外側が必要な行」に印をつける🖊️
- 「Taskを取る」「保存する」→ 外側っぽい✅
- 「完了にする」→ 内側(Entityの仕事)✅
ステップ③:印がついた行を“能力”にまとめてPortにする🔌
- 取る+保存 →
TaskRepository - ID発行 →
IdGenerator - 時刻 →
Clock
この流れ、めっちゃ大事だよ〜😊✨
8) よくある失敗あるある(先に潰そっ)💣😇
❌ 失敗1:Portがデカすぎる(巨大Repository)🐘
最初から「なんでもできるRepository」にしがち… → 次章(第25章)で 最小メソッド主義 をやるから、ここでは「膨らませない意識」を持っておけばOK😉✨
❌ 失敗2:Port名が技術寄り⚙️
DatabaseServiceとかSqlClientとか… → それ、内側の言葉じゃない😭 内側は「Taskを保存したい」なの!
❌ 失敗3:Portが外側の型を返す📦💥
- DBのRow型とか、HTTPレスポンス型とか → 内側が外側に汚染されちゃう😵💫
9) ちょい最新メモ(2026のいま)🆕✨
- TypeScriptは 5.9 のリリースノートが更新されてるよ(2026年1月更新)🧠✨ (TypeScript)
- Node.jsは v24 がLTS(Active LTS)で更新されている(2026-01-13のリリースもあるよ)🟢 (Node.js)
このへんはビルド設定やモジュール周りに影響しやすいけど、Port自体の考え方は不変だよ😊🔌
10) 理解チェック(1問)✅📝✨
Q:次のうち「Portとして適切な名前」はどれ?(理由も言えたら満点💯)
A. SqliteTaskRepository
B. TaskRepository
👉 正解:B 🎉 理由:Portは「能力」を表すから、技術名(Sqlite)を入れない😊✨
11) 今章の提出物(成果物)📦🎁
- ✅ 「Create / Complete / List」から抽出した Port候補のリスト(3〜5個)
- ✅ 各Portに 1行説明(“内側が欲しい能力”として)
- ✅
TaskRepositoryのinterface(最小でOK)
12) AI相棒🤖✨(コピペで使えるプロンプト)
Port候補出し🔌
CreateTask / CompleteTask / ListTasks の手順を箇条書きにして、外側に依頼すべき“能力”だけを抽出してPort名候補を10個出して。技術名(DB名/HTTP名)は禁止。
Portがデカすぎる診断🐘➡️🫧
このPort interfaceのメソッド一覧を見て、責務が大きすぎるところを指摘して。分割案も2パターン提案して(例:Reader/Writer分割など)。
命名チェック🧼
このPort名は内側(業務)寄り?外側(技術)寄り?理由を添えて、より良い名前案を5つ出して。
次の第25章では、この章で出した TaskRepository を “最小メソッド主義”でキレイに確定していくよ🗄️✨
「巨大Repository化」しないコツ、そこで一気に掴もうね😊💪💕