本記事について
当サイトを閲覧いただきありがとうございます。 本記事はシリーズ『生成AI時代のアーキテクチャ超入門』の「アプリケーションアーキテクチャ」カテゴリ第2弾として、ドメインロジックについて解説する記事です。
ドメインロジックは「業務固有のルール・判断・計算」を担う層で、ここの設計品質がアプリの長期競争力を決めます。本記事では Transaction Script vs Domain Model(DDD)の2大スタイル、DDD戦術/戦略パターン、貧血ドメインモデルというアンチパターン、AI時代における「業務概念を型に昇格させる」価値まで解説します。
このカテゴリの他の記事
「アプリの価値」が宿る層
| 層 | 例 |
|---|---|
| UI層 | 画面表示・入力検証 |
| ドメイン層 | 「割引は5000円以上で10%」「退会後30日で匿名化」 |
| インフラ層 | DB・外部API |
アプリの価値はドメインロジックに宿ります。ここが混沌としていると、業務変更のたびにコードが歪んでいく。業務ルールがUIやDBに漏れていると、同じルールが複数箇所に散らばり、変更時に片方だけ直して矛盾が生まれる、という事故がよく起きます。
3大スタイル
Martin Fowler が整理したドメインロジックの表現方式は、大きく3つに分類できます。プロジェクトの複雑さとチームの成熟度によって、どれを採用するかが変わります。
flowchart LR
Q{業務の複雑度}
TS["Transaction Script<br/>手続き型<br/>業務=1関数"]
TM["Table Module<br/>(あまり使われない)<br/>DBテーブル=ロジック単位"]
DM["Domain Model (DDD)<br/>業務概念をオブジェクトで表現"]
Q -->|低・CRUD中心| TS
Q -->|レガシー| TM
Q -->|高・複雑な業務ルール| DM
TS -.- L1[書き始めが速い<br/>育つと貧血モデルに陥り<br/>サービス層が肥大化]
DM -.- L2[業務概念を型に昇格<br/>初期コストは高いが<br/>長期保守に強い]
classDef q fill:#fef3c7,stroke:#d97706;
classDef ts fill:#dbeafe,stroke:#2563eb;
classDef tm fill:#f1f5f9,stroke:#64748b;
classDef dm fill:#fae8ff,stroke:#a21caf;
class Q q;
class TS ts;
class TM tm;
class DM dm;
| スタイル | 特徴 |
|---|---|
| Transaction Script | 手続き型。業務1つ=1関数で書く |
| Table Module | DBテーブルごとにロジックを集約する |
| Domain Model(DDD) | 業務概念をオブジェクトで表現する |
このうちTable Moduleはあまり使われず、実務では「Transaction Script vs Domain Model」の選択になることがほとんどです。
Transaction Script
Transaction Script は、リクエストごとに処理を手続き型で書いていくスタイルです。MVCフレームワークの Service 層によく見られる形で、最もシンプルで書き始めが速いのが特徴です。
function registerOrder(req) {
const user = getUser(req.userId)
if (!user.isActive) throw new Error()
const stock = getStock(req.productId)
if (stock < 1) throw new Error()
const price = calcPrice(...)
saveOrder(...)
sendMail(...)
}
| 強み | 弱み |
|---|---|
| シンプルで書き始めが速い | ロジックがあちこちに散乱しやすい |
| 学習コストが低い | 似た処理が重複する |
| 小規模プロジェクトに向く | 業務ルールが成長するとカオス化 |
CRUD中心のシンプルな業務なら、Transaction Scriptで十分。最初からDDDにする必要はありません。
Domain Model(DDD)
Domain Model は、業務概念をクラスとして表現し、そこにロジックを集約するスタイルです。DDD(ドメイン駆動設計)はこの方式を体系化した手法で、複雑な業務ドメインを扱う時に威力を発揮します。
class Order {
place() {
if (!this.user.isActive) throw new InactiveUserError()
if (this.items.isEmpty()) throw new EmptyOrderError()
this.status = OrderStatus.Placed
return new OrderPlacedEvent(this.id)
}
}
ロジックが Order クラス内に集約されるため、「注文を出すとはどういうことか」がコード1箇所を読むだけで分かります。テストも書きやすく、業務ルールが増えても秩序を保てます。
| 強み | 弱み |
|---|---|
| ロジックが1箇所に集約・テスト容易 | 初期設計コストが高い |
| 業務の変化にコードが追従しやすい | 学習コスト大(チーム全体) |
| 大規模で複雑なドメインに強い | 小規模には過剰 |
DDDの戦術的パターン
DDDでは、ドメインモデルを構築するための戦術的パターンが整理されています。これらを知っておくと、複雑なビジネスロジックを整理された構造で表現できます。
| パターン | 役割 |
|---|---|
| Entity | IDで識別される・内部状態が可変 |
| Value Object | 値で識別される・不変(例:Money, Email) |
| Aggregate | 整合性境界を持つEntityの塊 |
| Repository | 集約を永続化する窓口 |
| Domain Service | 複数の集約にまたがるロジック |
| Domain Event | 業務上の出来事を表す |
パターンは「必要になってから導入すればよい」最初から全部使う必要はありません。
Value Objectの効用
Value Object は、プリミティブ型ではなく「意味のある型でドメインを表現」するパターンです。「金額」を number ではなく Money クラスで、「メール」を string ではなく Email クラスで表現することで、業務概念を型システムに昇格させます。
❌ sendMoney(amount: number, currency: string)
→ 引数の順序を間違えても検知できない
✅ sendMoney(amount: Money)
→ Money = amount + currency をまとめて持つ
❌ if (email.includes('@'))
→ 毎回バリデーションが必要
✅ Email.parse(str)
→ 不正値は生成時に弾くので、以降は安全
プリミティブ型を使い回す設計は「プリミティブ強迫症」(Primitive Obsession)と呼ばれるアンチパターンです。
Money や Email のようなValue Objectは規模によらず有効。プリミティブ強迫症は初期から意識する価値があります。
集約(Aggregate)
集約は、複数のEntity・Value Objectを「整合性を保つ単位」としてひとまとめにしたものです。集約の内部では強整合を保ち、集約間は結果整合にするのが設計の基本です。
[Order集約]
├─ Order(集約ルート)
├─ OrderItem[]
└─ ShippingAddress
集約を跨ぐ更新は原則禁止
→ 他集約に影響を与えるならDomainEvent経由で通知
設計原則は以下の通りです。
- 集約は小さく保つ(大きな集約は競合とロックの温床)
- 集約外への参照はIDのみ(オブジェクト直接参照を避ける)
- 集約の更新は集約ルート経由
集約境界を適切に引けるかがDDDの最大の難所。ここが失敗すると全体が破綻します。
戦略的DDD
DDDには戦術的パターン以上に重要な戦略的DDDの概念があります。これは「コードを書く前に業務の境界を発見する」ことに焦点を当てた思想で、多くのプロジェクトで軽視されがちですが、実は本当に価値があるのはこちらです。
| 概念 | 内容 |
|---|---|
| Ubiquitous Language | 業務と開発で同じ言葉を使う |
| Bounded Context | 言葉の意味が通用する範囲 |
| Context Map | 複数コンテキスト間の関係図 |
| Event Storming | 業務を付箋で可視化する発見手法 |
同じ「顧客」という言葉でも、営業部門では「見込み客」を含み、経理部門では「請求先」を意味するといった違いがあります。この違いを無理に統一せず、「コンテキストごとに別モデルとして扱う」のが戦略的DDDの核心です。
貧血ドメインモデル(アンチパターン)
DDDの形だけ真似て、「データだけ持つクラス + ロジックが全部Service層」という状態になる失敗パターンを「貧血ドメインモデル」(Anemic Domain Model)と呼びます。一見DDDっぽく見えますが、中身はTransaction Scriptと変わりません。
❌ class Order {
id, status, items // データのみ
}
class OrderService {
static place(order) {
// ロジックが全部ここにある
if (!order.user.isActive) throw ...
if (order.items.length === 0) throw ...
order.status = 'placed'
...
}
}
これはDDDの「業務ロジックは業務オブジェクトに集約する」という本質を失っており、「Transaction Scriptの複雑版」にすぎません。DDDの学習曲線が高い最大の理由でもあります。
「DDDを採用した」の95%は貧血モデル。形ではなく「ロジックの置き場所」を問うのが本質です。
ケース別の選び方
CRUD中心・シンプル業務
Transaction Script。無理にDDDを導入してもクラスが増えるだけで、業務価値は上がりません。
ロジックが複雑・業務が頻繁に変化する領域
Domain Model(DDD)。保険・金融・医療・EC・物流など、業務ルールが多い領域では投資が回収できます。
スタートアップMVP
Transaction Script → 成長後にDDD。最初から完璧な設計を目指すと力尽きます。必要になってから段階的に育てる。
既存レガシーシステムの改善
段階的にDDD化。全てを一度に書き直さず、ボトルネックの業務領域から少しずつドメインモデルに移行します。
ロジックの配置原則
業務ルールは常にドメイン層に置く。これはどのスタイルを採用していても共通の原則です。業務ルールがUI・インフラに漏れると、変更時に複数箇所を修正することになり、矛盾が生じやすくなります。
❌ コントローラで税金計算
❌ フロントエンドで割引計算(バックエンドでも再計算が必要になる)
❌ DBストアドプロシージャに業務ルール
✅ ドメイン層のValue Object / Entityに業務ルールを集約
UI側で同じ計算を「表示目的で再現する」のはOKです。ただしルールの保有者は常にドメイン層です。
業務複雑度×スタイルの実務段階表
※ 2026年4月時点の業界相場値です。テクノロジー・人材市場の変化で陳腐化するため、定期的にアップデートが必要です。
「最初からDDD」は過剰、「ずっとTransaction Script」は破綻の元。業務の複雑さに合わせて段階的に育てるのが現実解です。
| 業務複雑度 | 業務ルール数 | 推奨スタイル | 採用すべき戦術的パターン |
|---|---|---|---|
| シンプルCRUD | 〜10個 | Transaction Script | なし(素のサービス層) |
| 中程度 | 10〜50個 | Transaction Script + Value Object | Value Objectのみ |
| 複雑 | 50〜200個 | Domain Model(DDD軽量版) | Entity / Value Object / Repository |
| 極めて複雑 | 200個〜 | 本格DDD | Entity / VO / Aggregate / Domain Service / Domain Event |
判断の目安は「業務ルール変更の頻度」です。週1回以上ルールが変わる領域(保険・金融・物流・EC)はDDD投資が回収できます。一方、CRUD中心の管理画面は5年以上運用してもTransaction Scriptで十分。Martin Fowlerが2003年に整理した3スタイルの使い分けが今も有効です。
DDDは業務複雑度に見合った時だけ。早すぎる導入はクラス爆発しか生みません。
ドメイン設計の鬼門・禁じ手
DDDを採用したプロジェクトで事故る典型を整理します。「形だけパターンを並べても貧血モデルになるだけ」、が共通の失敗です。
| 禁じ手 | なぜダメか |
|---|---|
| 貧血ドメインモデル(Entityはデータのみ・ロジックはService層) | DDDの形だけ。Transaction Scriptの複雑版で、恩恵ゼロ |
プリミティブ強迫症(金額を number、メールを string で持つ) | 型で業務を守れない。Money / Email Value Objectにする |
| 集約(Aggregate)を大きく設計 | ロックの競合・性能劣化・テスト困難。小さく保つのが原則 |
| 集約間をオブジェクト直接参照 | 境界が破れる。集約間はID参照のみが鉄則 |
| 戦略的DDD(Ubiquitous Language / Bounded Context)を無視 | 同じ「顧客」が複数部署で別概念。文脈を切らないと破綻 |
| 業務専門家と会話せずコードだけで業務モデリング | 業務用語とコード名が乖離、「User / Member / Account / Customer」問題 |
| 最初から全ドメインにDDDを適用 | CRUD画面まで4層構造になりクラスが爆発 |
| Domain Service を乱用してロジックを置く | Entityに置くべきロジックが出ていく。まずEntity、次にDomain Service |
| Domain Event をイベントバスに直接発行 | トランザクション境界と整合しない。Outboxパターンで整合化 |
2003年のEric Evans “Domain-Driven Design” から20年以上経っても、DDDが難しく感じるのは「技術より業務を学ぶこと」が本質だからです。パターンを覚えるより、業務専門家と会話する時間のほうが重要です。
DDDの本質は業務の言葉でモデルを育てる思想で、戦略的DDDを飛ばしてパターンだけ真似ると失敗します。
AI時代の視点
AI駆動開発が前提になると、ドメインロジックは「AIが理解できる業務表現」が決定的な価値を持ちます。Value Objectで業務概念を型にし、Aggregateで境界を明示する設計は、AIが業務ルールを読み取って正しいコードを生成するための必須条件です。
| AI時代に有利 | AI時代に不利 |
|---|---|
| Value Object・Entityで業務概念を型表現 | プリミティブ型(string・number)を使い回し |
| Aggregateで整合性境界を明示 | トランザクション境界が暗黙 |
| Ubiquitous Languageをコードと対応 | 業務用語とコード名がバラバラ |
| ドメインロジックがドメイン層に集約 | ロジックがUI・Service・DBに散在 |
AIは「型と名前が業務を表すコード」を読むと、追加の業務ロジックを業務整合性を保ったまま生成できます。逆に number や string ばかりの貧血モデルだと、AIは「何を表しているか」を推測で書くしかなく、ハルシネーションの温床になります。これからは「ドメインモデル = AIへの仕様書」という位置づけで設計する時代です。
よくある勘違い
- 「DDD=クラス設計のパターン集」 → 本質は「業務の言葉でモデルを育てる思想」戦略的DDD(Ubiquitous Language・Bounded Context)を無視してパターンだけ真似ても貧血モデルになる
- 「最初からDDDにすれば安心」 → CRUD中心の小規模プロジェクトでは過剰設計。業務複雑度に見合わないDDDは、ただクラスを増やすだけ
- 「DDDは宗教」 → DDDは道具です。業務ルールが複雑で長期運用される領域では、投資が確実に回収できる。適用場面を見誤らないこと
- 「Value Objectは小規模には不要」 →
MoneyやEmailのようなValue Objectは規模によらず有効です。プリミティブ強迫症を避けるのは初期から意識する価値がある
「Transaction Scriptを2ファイルに分割しただけ」(業界事例)
参画したECサイトの案件で、Order クラスは id と status と items だけを持ち、「注文を確定する」「キャンセルする」「返金する」といった処理は全て OrderService の静的メソッドに並んでいた、という事例があります。レビュー会で「これはDDDではなく、Transaction Scriptを2ファイルに分割しただけです」と指摘された、という話が聞かれます。
過去のプロジェクトで、order.place() と書けない Order クラスを書いてしまい、レビューで「このOrderはただのデータクラスで、業務は語れていない」と指摘される経験はDDDを学び始めた多くのエンジニアが通る道です。「DDDを採用した」と言いながら貧血モデルになっているプロジェクトは極めて多い。
形だけパターンを並べても、ロジックがドメインオブジェクトに宿っていなければDDDではない。order.place() が書けないコードは、まだオブジェクトではなくデータの入れ物です。
決めるべきこと — あなたのプロジェクトでの答えは?
以下の項目について、あなたのプロジェクトの答えを1〜2文で言語化してみてください。曖昧なまま着手すると、必ず後から「なぜそう決めたんだっけ」が問われます。
- ドメインロジックの記述スタイル(Transaction Script / DDD / 折衷)
- Value Object をどの範囲で採用するか
- 集約の境界定義(Bounded Contextの設計)
- Domain Event / イベント駆動アーキテクチャの採用有無
- DDD戦術的パターンのうちどれを採用するか
- Ubiquitous Languageの文書化と更新ルール
よくある失敗
- 最初からDDDを全適用 ― CRUDしかない画面にも集約を定義し、コードだけ肥大化する
- 貧血ドメインモデルになる ― DDDの形だけ真似て、実態はTransaction Scriptのまま
- 戦略的DDDを無視 ― 戦術的パターンだけ使い、Bounded Contextを意識しないので業務概念が混線する
- 業務専門家と会話しない ― Ubiquitous Languageが育たず、コードの言葉と業務の言葉が乖離する
最終的な判断の仕方
ドメインロジック設計の核心は「業務ルールをどこに置くか」ではなく、「業務の複雑さに見合ったスタイルを選ぶか」です。CRUD中心のシンプルなシステムにDDDを全適用すると、クラスが増えるだけで業務価値は上がらず、チームの学習コストだけが積み上がる。一方、保険・金融・物流のように業務ルールが絶え間なく変化する領域でTransaction Scriptを貫くと、数年でコードが崩壊します。
小さく始めて、必要になってから段階的にDomain Modelに育てるのが健全で、「最初からDDD」は典型的な過剰設計。
現代の決定的な軸は「業務概念が型として表現されているか」Value Objectで Money や Email を型化し、Aggregateで整合性境界を明示し、Ubiquitous Languageでコードと業務の言葉を一致させる。これらは人間のためだけでなく、AIへの仕様書として決定的な価値を持ちます。
number や string ばかりの貧血モデルでは、AIは業務を推測で書くしかなくハルシネーションを起こす。逆に型と名前が業務を語るコードなら、AIは整合性を保ったまま機能追加を生成できる。DDDの本質は「宗派」ではなく「業務を型で語ること」にあり、その価値はAI時代にさらに大きくなります。
選定の優先順位をまとめると次の通りです。
- 業務複雑度で選ぶ(CRUDはTransaction Script、複雑業務はDDD)
- 小さく始めて段階的に育てる(最初からDDD全適用は過剰設計)
- 業務概念を型に昇格(Value Object / Aggregate / Ubiquitous Language)
- 貧血モデルを避ける(ロジックはドメイン層に集約)
まとめ
本記事はドメインロジックについて、Transaction Script vs DDD・Value Object・集約・戦略的DDDまで含めて解説しました。如何だったでしょうか。
業務複雑度に見合ったスタイルを選び、業務概念を型に昇格させる。これがAI時代も含めた2026年のドメインロジック設計の現実解です。
次回は命名とコード規約(命名原則・Linter/Formatter・PRレビュー・CODEOWNERS)について解説します。
シリーズ目次に戻る → 『生成AI時代のアーキテクチャ超入門』の歩き方
それでは次の記事も閲覧いただけると幸いです。
📚 シリーズ:生成AI時代のアーキテクチャ超入門(27/89)