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

第10章:SoC(関心の分離)とBCのつながり🔍🧼

この章でできるようになること🎯✨

  • SoC(関心の分離)って何かを、ふわっとじゃなく説明できる🙂🫶
  • BCがSoCの強力なやり方の1つだと腑に落ちる🧠💡
  • UI都合・DB都合・外部サービス都合が、ドメインを汚す瞬間を見分けられる👀🧽
  • 「混ぜると困るものリスト」を作って、設計の守り方が分かる🧾🛡️

10.1 SoCってなに?🧁✨

SoCは Separation of Concerns の略で、ひとことで言うと…

「気にすること(関心)を、混ぜずに分けて置こうね」🍱✨

たとえば、学内フリマ🛍️で「出品」って機能を作るとき…

  • 🖥️ 画面の関心:ボタンの位置、入力フォームの見た目、エラーメッセージの文章
  • 📜 ルールの関心:出品タイトルは必須、価格は0円NG、説明は2000文字まで
  • 🗄️ 保存の関心:DBにどう保存する?どのテーブル?どのカラム?
  • 🌐 外部の関心:画像アップロード、通知、決済などの外部サービス連携

これらをごちゃ混ぜにすると、あとでこうなるよ〜😇➡️😱

  • 画面ちょい変更しただけで、ルールが壊れる💥
  • DBの都合で、ドメインの言葉が歪む🌀
  • 外部サービスのクセで、コードがベタベタに汚れる🧽

1. 関心の分離(SoC)と BC の関係 🧩✨

イメージ

「関心の分離」って、プログラミングでよく聞くよね😊 BC(Bounded Context)は、その考え方を “ドメインの言葉” と “責務のまとまり” に対して、もっと強く適用したもの だよ🧠🧱

✅ SoCが守りたいもの(ざっくり)🍀

  • UI・DB・外部連携と、業務ルールを混ぜない🧼
  • 変更が起きても、影響範囲を小さくする✂️
  • テストしやすくする🧪✨

✅ BCが守りたいもの(超ざっくり)🌸

  • 同じ言葉が、同じ意味で通る世界を作る🗣️🔒
  • 責任の範囲(何を扱い、何を扱わない)を固定する🏷️📌
  • 境界を越えるときは、ちゃんと“翻訳”する🌍➡️🏠

つまり…

  • SoC:混ざると困る関心(UI/DB/外部)を分ける🍱
  • BC:混ざると困る意味(言葉/ルール/責務)を分ける🧠🧱

同じ「分ける」でも、BCはより「意味」と「責任」に効くよ💘


10.3 「混ぜるとヤバい」代表3パターン😇➡️😱

ここ、超大事なので、事故例を3つで覚えちゃおう🧠⚡


パターンA:UI都合がドメインに侵入🖥️➡️📜

よくある混入物🧨

  • 画面文言(「価格を入力してください」みたいな文章)
  • 表示形式(「¥1,200」みたいなフォーマット)
  • 画面の状態(ローディング中、モーダル開閉など)

なにが困る?😵

  • 画面を変えたら、ドメインロジックまで変更が波及💥
  • APIやバッチなど「画面がない入口」でもロジックが使いにくい🙅‍♀️

✅ 目指す感覚: ドメインは “ルールの結果” を返すだけ。 「どう表示するか」はUIが決める🎀


パターンB:DB都合がドメインをねじ曲げる🗄️➡️🧠

よくある混入物🧨

  • DBのカラム名をそのままドメイン名にする
  • NULLの都合で「本当は必須」なのに optional が増える
  • 正規化/非正規化の都合が、業務ルールに混ざる

なにが困る?😵

  • DB変更=ドメイン変更になって、変更が怖い😱
  • “意味のある型” が消えて、ただのデータ袋になる🌀

✅ 目指す感覚: ドメインは「意味のある形(型)」で考える💎 DBは「保存の形」🗄️ 形が違ってOK🙆‍♀️(だから変換が必要になる✨)


パターンC:外部サービス都合が中心モデルを汚す🌐➡️🧽

よくある混入物🧨

  • 外部APIのフィールド名がそのまま内部に入る
  • 外部の状態(status文字列)が、そのまま内部の状態になる
  • 外部の欠損値・単位・時刻表現が、そのまま染み込む

なにが困る?😵

  • 外部が変わるたびに内部が壊れる🧨
  • “自分のアプリの言葉” が消えて、他人の言葉で生きることに…😇

✅ 目指す感覚: 外部は外部。内部は内部。 境界に 翻訳係(ACLみたいな役)を置く🛡️🧾

※AI拡張やIDE連携も、ここに近い事故が起きやすいよ⚠️(後で対策やるね🙂)


10.4 学内フリマで、SoC×BCをコードの形にする📁🔒

ここでは「出品BC(Listing)」を例に、分け方の型を作るよ🧸✨

ざっくり構造イメージ📦

src/
contexts/
listing/
domain/ # ルールと意味の中心🧠
app/ # ユースケース(入口の流れ)🎮
infra/ # DB/外部連携🗄️🌐
ui/ # 画面・APIハンドラ🖥️📡

```mermaid
graph TD
UI[UI層<br/>入力・表示] --> App[App層<br/>流れの制御]
App --> Domain[Domain層<br/>中心・知識]
App --> RepoInterface[Repository Interface]
Infra[Infra層<br/>具体的保存] -- "Implements" --> RepoInterface

ポイントはこれ👇✨

* domainは「意味」と「ルール」だけ🧠📜
* appは「やる順番」だけ(ドメインを呼ぶ係)🎮
* infraは「保存や外部の具体」だけ🗄️🌐
* uiは「入力→呼び出し→表示」だけ🖥️

---

## ミニ例:価格のルールは domain に置く🪙🔒

```ts
// src/contexts/listing/domain/money.ts
export class Money {
private constructor(private readonly yen: number) {}

static create(yen: number): Money {
if (!Number.isInteger(yen)) throw new Error("価格は整数でね🙏");
if (yen <= 0) throw new Error("価格は1円以上だよ💦");
return new Money(yen);
}

value(): number {
return this.yen;
}
}

✅ ここが大事💡

  • 「¥1,200」みたいな表示は domainに入れない🧼
  • エラーメッセージの文章は最小限でOK(UIで差し替えてもOK)🎀

ミニ例:保存は interface で切る🧾✂️

// src/contexts/listing/domain/listingRepository.ts
import { Listing } from "./listing";

export interface ListingRepository {
save(listing: Listing): Promise<void>;
findById(id: string): Promise<Listing | null>;
}

domainは「保存したい気持ち」だけ持つ☺️ 「どのDB?どう保存?」は知らない🙅‍♀️✨


ミニ例:ユースケースは app に置く🎮✨

// src/contexts/listing/app/createListing.ts
import { Money } from "../domain/money";
import { ListingRepository } from "../domain/listingRepository";
import { Listing } from "../domain/listing";

export type CreateListingInput = {
title: string;
priceYen: number;
};

export class CreateListing {
constructor(private readonly repo: ListingRepository) {}

async execute(input: CreateListingInput): Promise<{ listingId: string }> {
const price = Money.create(input.priceYen);
const listing = Listing.create({ title: input.title, price });

await this.repo.save(listing);

return { listingId: listing.id() };
}
}

✅ app層は「処理の流れ」担当🧭

  • 入力を受け取る
  • domainの型に変換
  • domainのルールに通す
  • repoで保存

ミニ例:DBは infra に閉じ込める🗄️🔒

// src/contexts/listing/infra/inMemoryListingRepository.ts
import { ListingRepository } from "../domain/listingRepository";
import { Listing } from "../domain/listing";

export class InMemoryListingRepository implements ListingRepository {
private store = new Map<string, Listing>();

async save(listing: Listing): Promise<void> {
this.store.set(listing.id(), listing);
}

async findById(id: string): Promise<Listing | null> {
return this.store.get(id) ?? null;
}
}

まずはインメモリでOK🙆‍♀️ SoCができてると、あとでDBに差し替えても「domain/app」が揺れにくいよ✨


10.5 今日の成果物「混ぜると困るものリスト」🧾⚠️

ここからが本題のアウトプットだよ📝✨ 下のテンプレを埋めていこう🙂💕

テンプレ📄

  • 🖥️ UI都合(例:画面文言、表示形式、画面状態)

    • 例:
    • 例:
    • 例:
  • 🗄️ DB都合(例:カラム名、NULL事情、保存形式)

    • 例:
    • 例:
    • 例:
  • 🌐 外部都合(例:外部APIの命名、単位、欠損、状態)

    • 例:
    • 例:
    • 例:
  • 🧠 意味の衝突(例:同じ単語が別の意味)

    • 例:
    • 例:
    • 例:

💡 コツ: 「それ、domainに入った瞬間に “意味” が歪む?」って自問すると見つけやすいよ🧠🔍


10.6 ミニ演習3つ🧪✨

演習1:これはどの関心?仕分けゲーム🍱🎮

次の要素を、4カテゴリに振り分けてみよう👇 (UI / ドメイン / 保存 / 外部)

  • 「価格は1円以上」
  • 「¥1,200の表示」
  • 「フォームのプレースホルダー文言」
  • 「Listingsテーブルのprice_yenカラム」
  • 「外部決済のstatus = authorized」
  • 「出品できるのは学内ユーザーだけ」
  • 「画像アップロードの署名付きURL」
  • 「DBのインデックス設計」

✅ 目安: “ルール” はドメイン寄り、 “見せ方” はUI寄り、 “保存の形” は保存寄り、 “他人の世界” は外部寄り🌍✨


演習2:混ぜ混ぜ関数を分けよう🧽✂️

下みたいな関数があったら、どこが混ざってる?を指摘して、3つに分割してみよう🙂

function createListingFromForm(form: { title: string; priceText: string }) {
if (form.title.trim() === "") alert("タイトル必須です"); // UI文言
const yen = Number(form.priceText.replace("¥", "")); // 表示形式に依存
if (yen <= 0) throw new Error("invalid"); // ルール

// DB都合が混入(例)
return { listing_title: form.title, price_yen: yen, created_at: new Date().toISOString() };
}

✅ 目標:

  • UI:入力チェックやメッセージ(ただしルールの本体は持たない)
  • domain:価格やタイトルのルール
  • infra:保存用の形(カラム名など)

演習3:自分のBCに「立入禁止」看板を立てる🚧🏷️

「出品BC」のdomainに、これ禁止!って3つ書いてみよう🙂✨

例👇

  • 「画面文言を入れない」🖥️🚫
  • 「DBのカラム名を型名にしない」🗄️🚫
  • 「外部status文字列をそのまま状態にしない」🌐🚫

10.7 AI相棒の使い方🤖💬

AIはめちゃ便利だけど、SoCの観点で使うと爆伸びするよ📈✨ (依頼の仕方=観点が命🔥)

仕分け依頼🧺

  • 「この関数の中にある関心を “UI / domain / infra” に分類して、混入点を指摘して」🧠🔍

分割依頼✂️

  • 「domainに残すべきルールだけ抽出して、TypeScriptのクラス/関数にして」🧱✨

入口依頼🚪

  • 「app層のユースケースとして、入力→変換→domain呼び出し→repo保存の流れを書いて」🎮🧭

10.8 AI拡張を使うときの安全メモ🔐⚠️

AI拡張は便利だけど、「拡張機能マーケット」には危ないものも混ざることがあるよ🥲 最近、VS Codeの“AIっぽい”拡張がデータを抜くケースが報告されてる⚠️ (TechRadar) さらに、AI入りIDE全般で「プロンプト注入」などを入口に、情報漏えいや危険な動作につながる研究報告も出てるよ🧯 (Tom's Hardware)

だから、最低限これだけは守ろう👇🧸🛡️

  • 拡張は「提供元・実績・レビュー」を確認してから入れる👀
  • 怪しい“AIアシスタント拡張”を増やしすぎない(特に用途かぶり)🧨
  • リポジトリ内の指示ファイルやREADMEの“見えない指示”に注意(コピペ誘導など)🕵️‍♀️
  • 機密(APIキー等)はワークスペースに平文で置かない🔑🚫
  • AIに「秘密情報を貼らない」ルールを自分に課す🙅‍♀️💦

10.9 2026/02時点の小ネタ🍀

  • TypeScript 5.9系では import defer など、モジュール実行タイミングをコントロールする提案への対応が入ってるよ(挙動を理解して使うと事故りにくい)🧠✨ (typescriptlang.org)
  • TypeScriptはネイティブ移行に向けたプレビューも配布されていて、@typescript/native-preview で試せるよ⚡ (Microsoft for Developers)
  • さらに、将来のネイティブ移行(TypeScript 7系)に向けた進捗とロードマップも公式が継続的に出してるよ🗺️✨ (Microsoft for Developers)

この流れがあるからこそ、UI/DB/外部の都合をdomainから分離しておくのが、将来の変化にめちゃ強い💪😊


この章のまとめ🧁✨

  • SoCは「関心を混ぜない」🍱
  • BCは「意味と責任を混ぜない」🧠🧱
  • 混入しやすいのは UI都合・DB都合・外部都合 の3つ😇➡️😱
  • 成果物は「混ぜると困るものリスト」🧾⚠️
  • domainを守れると、変更が怖くなくなる🛡️💕

自己チェック✅✨

  • domainに画面文言や表示形式が入ってない🖥️🚫
  • domainがDBカラム名やNULL事情に引っ張られてない🗄️🚫
  • 外部の命名やstatusが、そのまま内部モデルに入ってない🌐🚫
  • 境界を越えるところで「変換/翻訳」ポイントが言える🧾✨
  • 「混ぜると困るものリスト」が埋まってる📝💮