第29章:まずフォルダで分ける(物理境界)📁✂️
この章でできるようになること🎯✨
- 「境界(Bounded Context)」を フォルダ構成で“見える形” にできる📦👀
- 他コンテキストの中身に勝手に触れない import の癖をつけられる🛡️🔌
- 境界が崩れにくいプロジェクト骨格を作れる🧱💪
0. 2026/02/02 時点のミニメモ🗞️🧸
- TypeScript の安定版ラインは 5.9.3(npm の “Latest” 表示)だよ🧩✨ (NPM)
- 5.9 の公式リリースノートも公開されてる(※この章は“フォルダ境界”が主役なので、機能詳細は後回しでOK)📝🌱 (TypeScript)
1. なんで「フォルダで分ける」が最初に効くの?💡📁

設計に慣れてない時ほど、頭の中の境界ってすぐ溶けちゃうのね🫠 だから最初にやるべきはこれ👇
- 物理的に分ける(=混ざりにくくする)🧱
- 入口を決める(=触っていい場所だけ触る)🚪✨
フォルダは、境界を守る ガードレール みたいなもの🏎️🛡️ 「ここから先は別の世界〜!」って視覚で分かるだけで、事故が激減するよ🚑💦
2. “学内フリマ” のBCをフォルダに落とす🛍️🏫
例として、こんな3つに分けるよ(章の後半で増やしてもOK)🌸
- Listing(出品)🧾📦
- Trading(取引)💳🤝
- Shipping(配送)🚚📮
ポイント:BC名はフォルダ名にもなるから、短くて意味がズレにくい単語が◎🏷️✨
3. まずは最小の“境界フォルダ構成”を作る🧱📁
最初はこれで十分!「深くしすぎない」のがコツだよ🧸🌱
src/
contexts/
listing/
index.ts ← 外に見せる入口(Public API)🚪✨
app/ ← ユースケース(アプリ層)🎮
domain/ ← ルールや型(ドメイン層)🧠
trading/
index.ts
app/
domain/
shipping/
index.ts
app/
domain/
shared/ ← 共有は最小!増やしすぎ注意⚠️
utils/
```mermaid
graph TD
Root[src/] --> C[contexts/]
Root --> S[shared/]
C --> BC1[listing/]
C --> BC2[trading/]
subgraph BC_Detail ["BC内部の基本形"]
BC1 --- I["index.ts (入口)"]
BC1 --- A["app/ (操作手順)"]
BC1 --- D["domain/ (世界の法則)"]
end
## “domain” と “app” の雑な理解でOK🙆♀️✨
* **domain**:ルール・型・不変条件・状態…(気持ち「世界の法則」)🧠📜
* **app**:やりたいこと(ユースケース)を並べる場所(気持ち「操作手順」)🎮🧾
---
## 4. 入口ファイル `index.ts` を “窓口” にする🚪📮
BCの外からは **必ずここ経由** にするよ✅
すると「外から触っていいもの」が自然に揃う✨
例:`src/contexts/listing/index.ts`
```ts
export { createListing } from "./app/createListing";
export type { ListingId, ListingItem } from "./domain/types";
✅ 良いimport(外の人は入口だけ見る)
import { createListing } from "@contexts/listing";
❌ ダメimport(中身を覗きに行く=境界破壊)
import { ListingItem } from "../contexts/listing/domain/types"; // だめ〜😭
5. “入口import” を楽にする(tsconfig の paths)🧭✨
相対パス地獄(../../../)は、境界を壊す温床だよ😵💫
だから 入口専用のimport名 を作ってあげるのが最強💪
TypeScript は baseUrl と paths で import の解決ルールを作れるよ📌 (TypeScript)
tsconfig.json の一例👇
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@contexts/listing": ["src/contexts/listing/index.ts"],
"@contexts/trading": ["src/contexts/trading/index.ts"],
"@contexts/shipping": ["src/contexts/shipping/index.ts"]
}
}
}
これで 「入口しかimportできない空気」 が作れるよ🌿✨
6. “共有(shared)” は最小にしようね⚠️🧬
共有フォルダって便利なんだけど…増え始めると一気にこうなる👇
- なんでも shared に置く
- 全コンテキストが shared に依存する
- 結局「大きな一枚岩」に逆戻り😇➡️😱
shared に入れていいのは、だいたいこの3種類だけに絞るのがおすすめ🍙✨
- 技術的ユーティリティ(日付フォーマット、文字列整形など)🔧
- ドメインじゃない純粋関数(副作用なし)🧼
- 超安定な型(増えるなら“別のBCにすべき”のサインかも)🚨
7. 章のゴール:フォルダ境界を“崩れにくくするルール”🧾✅
この章では、まず文章でOK!コードより先にルールを置くよ📌😊
境界ルール(最小セット)🛡️
- 他BCの
domain/やapp/を直接importしない 🙅♀️ - importは
@contexts/<name>(入口)からだけ 🚪✨ - shared に “業務の言葉” を置かない(置きたくなったらBC再検討)🧠⚠️
8. ミニ演習:境界フォルダを実際に作る🧪📁
演習A:フォルダを掘る🕳️✨
src/contexts/を作るlisting / trading / shippingを作る- それぞれに
domain/ app/ index.tsを作る
演習B:まず1個だけ通す(Listing)🛍️✅
src/contexts/listing/domain/types.ts
export type ListingId = string;
export type ListingItem = {
id: ListingId;
title: string;
priceYen: number;
};
src/contexts/listing/app/createListing.ts
import type { ListingItem } from "../domain/types";
export function createListing(input: Omit<ListingItem, "id">): ListingItem {
return { id: crypto.randomUUID(), ...input };
}
src/contexts/listing/index.ts
export { createListing } from "./app/createListing";
export type { ListingId, ListingItem } from "./domain/types";
最後に、どこかの呼び出し側(例:src/main.ts)で入口importして動かす🧸✨
import { createListing } from "@contexts/listing";
const item = createListing({ title: "教科書セット📚", priceYen: 1200 });
console.log(item);
9. AI相棒に頼むときの質問テンプレ🤖💬
テンプレ1:フォルダ移動の手順を出してもらう📦
- 「いま
src/直下に散らばってる○○ファイルを、contexts/listingに移したい。移動先とimport修正方針を手順で出して」🗺️✨
テンプレ2:入口(index.ts)の公開物を絞る✂️
- 「
contexts/listingの外に公開すべき関数・型だけをindex.tsに集めて。公開しない候補も理由つきで出して」🚪✅
テンプレ3:sharedに入れていいか判定させる⚖️
- 「この
Money型を shared に置くべき?それとも特定BCに置くべき?判断基準と、分けるなら代案も出して」🧠💡
10. 仕上げチェックリスト✅👀
src/contexts/<name>/index.tsがある?🚪- 他BCからのimportが 入口経由 になってる?🧭
../../..の相対パスが増えてない?😵💫- shared に業務語彙(例:Order, User, Trade)が増えてない?⚠️
次章の予告📮✨
次は「各BCの公開API(入口)をもっと綺麗に決める」よ🚪✨(第30章)