第11章:Entityのライフサイクル(作る→変える)🔁
〜「完了(complete)」のルールを “どこに置く?” をスッキリ決める回だよ〜😊🧠
0. この章でできるようになること 🎯💖
- 「Taskを完了にする」処理の責務の置き場を迷わなくなる✨
- **Entityが守るべき更新ルール(不変条件)**を、コードに閉じ込められる🔒
- 「UseCaseがやること/Entityがやること」を、線引きできるようになる✍️
- “禁止条件” の漏れをテストで止められる🧪✅
1. まず超大事:Entityのライフサイクルってなに?🧬
Entityって「ただのデータ箱」じゃなくて、 作られて → 状態が変わって → ずっとルールを守り続ける存在だよ💡✨
今回のTaskなら、ざっくりこんな流れ👇
- 作る(Create)🆕
- 完了にする(Complete)✅
- 一覧で見る(List)👀
この章では特に 「完了にする(Complete)」の状態変化を扱うよ🔁
2. いちばん迷うポイント:completeは誰の責務?🤔⚖️
ここがクリーンアーキの気持ちいい所〜!✨ 結論から言うと👇
✅ Entityが担当すること(ルール・状態遷移)🛡️
- 「完了にしていい条件」
- 「完了したらこういう状態になる」
- 「二重完了は禁止」みたいなドメインルール
✅ UseCaseが担当すること(手続き・段取り)🧭
- どのTaskを完了するか探す(Repositoryで取得)
- Entityに「完了して!」ってお願いする(メソッド呼ぶ)
- 保存する(Repositoryで保存)
- 返す(Response作る)
つまり✨ UseCaseは段取り係、Entityはルール番人👮♀️💖
3. 状態遷移を “絵” にする(めっちゃ効く)🧠🖊️
まずは脳内を整理するために、こういう矢印を作ろ〜👇
Active(未完了)➡️Completed(完了済み)

禁止も書いちゃう👇
Completed➡️Completedは ❌(二重完了禁止)
文章でルールを書くなら例えば👇
- 未完了のTaskだけが完了できる
- 完了したら
completedAtが入る - すでに完了してたらドメインエラー
4. 実装方針:Entityの「更新」はメソッドに閉じ込める🔒✨
やりたいのはこれ👇
- 外側から
task.completed = trueとか直接いじれないようにする😤 - 必ず
task.complete()を通るようにする✅ - そこでルールチェックして、ダメならエラーを返す⚠️
5. Entity実装例(Taskのライフサイクル)🧱✅
5-1. ドメインエラー(例)⚠️
(前章で作った流れを引き継ぐイメージだよ〜)
// src/entities/errors.ts
export type DomainError =
| { type: "TaskAlreadyCompleted" }
| { type: "InvalidTitle"; reason: string };
5-2. Task Entity(completeのルールをEntityに集約)🛡️
// src/entities/task.ts
import type { DomainError } from "./errors";
export type TaskId = string;
export type Result<T> =
| { ok: true; value: T }
| { ok: false; error: DomainError };
export class Task {
// 外から直接書き換えさせない✨
private constructor(
private readonly _id: TaskId,
private _title: string,
private _completedAt: Date | null,
) {}
// 読み取り専用の出口だけ用意する😊
get id(): TaskId {
return this._id;
}
get title(): string {
return this._title;
}
get completed(): boolean {
return this._completedAt !== null;
}
get completedAt(): Date | null {
return this._completedAt;
}
// 作るときのルール(例:タイトル必須)
static create(params: { id: TaskId; title: string }): Result<Task> {
const title = params.title.trim();
if (title.length === 0) {
return { ok: false, error: { type: "InvalidTitle", reason: "タイトルが空だよ🥲" } };
}
return { ok: true, value: new Task(params.id, title, null) };
}
// 状態遷移:Active -> Completed ✅
complete(now: Date): Result<void> {
if (this._completedAt !== null) {
return { ok: false, error: { type: "TaskAlreadyCompleted" } };
}
this._completedAt = now;
return { ok: true, value: undefined };
}
}
ポイントはこれだよ👇✨
completedAtがnullかどうかで状態が決まる(シンプル!)complete(now)を通らないと完了できない- 二重完了はドメインエラーで止める🚫
6. 「UseCaseはどう呼ぶの?」の絵(責務の分離)🧩✨

UseCase側はこんな気持ち👇
- RepositoryからTaskを取ってくる📦
task.complete(now)を呼ぶ✅- 保存する💾
- Response作って返す📤
ここで “completeできる条件” をUseCaseに書き始めると だんだん「中心のルールが散らばって」事故りやすくなるよ😵💫💦 (例:A画面ではチェックしたのに、B画面では忘れた…とか)
7. ルールは増える。だから “禁止条件リスト” を作る📝🚫
今はミニアプリでも、ルールって増えるのが普通だよ〜😆
✅ completeの禁止条件(例)
- すでに完了している(
completedAt !== null) - (将来)削除済みは完了できない
- (将来)期限切れは完了できない
- (将来)権限がない人は完了できない(これはUseCase寄り)
この章のゴールは、“Entityが守る禁止条件” を漏れなくメソッド内に置くことだよ🛡️✨
8. テストで「禁止条件の漏れ」を潰す🧪💖
Vitestは最近も更新が続いてて、移行ガイドも整備されてるよ〜(Vitest 4系)🧪✨ (vitest.dev) なので Entityテストは Vitest でサクッといこう😊
// src/entities/task.test.ts
import { describe, it, expect } from "vitest";
import { Task } from "./task";
describe("Task Entity lifecycle", () => {
it("未完了のTaskは完了できる✅", () => {
const created = Task.create({ id: "t1", title: "レポート出す" });
if (!created.ok) throw new Error("create failed");
const task = created.value;
const r = task.complete(new Date("2026-01-22T10:00:00Z"));
expect(r.ok).toBe(true);
expect(task.completed).toBe(true);
expect(task.completedAt).not.toBeNull();
});
it("完了済みは二重完了できない🚫", () => {
const created = Task.create({ id: "t1", title: "洗濯する" });
if (!created.ok) throw new Error("create failed");
const task = created.value;
const r1 = task.complete(new Date());
expect(r1.ok).toBe(true);
const r2 = task.complete(new Date());
expect(r2.ok).toBe(false);
if (!r2.ok) {
expect(r2.error.type).toBe("TaskAlreadyCompleted");
}
});
it("空タイトルは作れない🥲", () => {
const created = Task.create({ id: "t1", title: " " });
expect(created.ok).toBe(false);
if (!created.ok) {
expect(created.error.type).toBe("InvalidTitle");
}
});
});
テストの気持ちはこれ👇
- 作れること
- 変えられること
- 変えちゃダメな時に止まること ←超大事💥
9. ありがちな落とし穴(ここでハマりやすい)🕳️😵
❌ UseCase側で if (task.completed) return ... を毎回書く
→ 書き忘れが起きる(画面が増えるほど事故る)💥
❌ Entityを interface Task { ... } のただの型にしちゃう
→ ルールが散らばりやすい💦 (Entityは “ふるまい” が主役だよ〜)
❌ completed: boolean だけで済ませる
→ 後から「いつ完了した?」が欲しくなって壊しがち😇
completedAt: Date | null は地味に便利✨
10. この章の提出物(成果物)📦🎁
TaskEntityにcomplete(now)がある✅- 二重完了をDomainErrorで止めている🚫
Task.create()で最低限の生成ルールが入ってる🆕- Entityのテストが3本以上🧪✨
11. 理解チェック(1問)📝💡
Q. 「完了済みTaskは完了できない」チェックは、UseCaseとEntityのどっちに書く?理由も! (答え:Entity。理由:ルールが中心に集約され、呼び出し元が増えても漏れないから💖)
12. AI相棒プロンプト(コピペ用)🤖✨
禁止条件の洗い出し🔍
Taskのcomplete処理について、「Entity側で守るべき禁止条件」を漏れなく列挙して。
将来増えそうなルールも候補として出して、Entity向き/UseCase向きに分類して。
テスト観点づくり🧪
Task Entityのライフサイクル(create -> complete)に対して、
最低限必要なテストケースを5つ、理由付きで提案して。
実装レビュー💅
このTask Entity実装をレビューして、外側から状態が壊れないか、責務が散ってないか指摘して。
改善案があれば具体的なコード方針も出して。
13. ちょい最新メモ(2026年1月時点)📌✨
- TypeScriptの公式リリースノートは 5.9 が公開されていて、ページ更新も2026年1月に入ってるよ🧷 (typescriptlang.org)
- Node.jsは v24系がActive LTS として扱われていて、2026-01-13に 24.13.0 のセキュリティリリースも出てる🛡️ (nodejs.org)
(この章の結論はツールが変わっても揺れないけど、テストや実行環境の更新は定期的に追うと安心だよ〜😊✨)
次の章(第12章)では、今作ったEntityが 外側に依存してないか監査するよ👀🛡️ 「Entities層のimportがゼロで綺麗✨」って状態を作っていこ〜!🎉