本ページはプロモーションが含まれています

IT全般

ドメイン駆動設計(DDD)実践記|3原則で複雑なコードを整理した話

CATEGORY
REV.
DRAWN BY
TOM
SHEETS
9枚
DDD(ドメイン駆動設計の実践記)

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

ドメイン駆動設計(DDD)とは、業務のルール(ドメイン知識)をコードの構造そのものに表現し、ビジネスの変化に強いソフトウェアを作るための設計手法である。

本記事では、肥大化した注文処理クラスを「ドメインモデル中心」「ユビキタス言語」「依存関係の向き」という3つの原則だけに絞ってリファクタリングした、筆者自身の実践記録を紹介する。

「修正したはずのバグが、別の機能で再発する」「コードを読み解くだけで午前中が終わる」。この消耗は、コードが汚いからではない。業務のルールが、コードのどこにも表現されていないからだ。

私が担当していたOrderServiceも、まさにこの状態だった。5000行を超える注文処理クラスに、在庫チェックから決済、メール送信までが詰め込まれていた。そこで使ったのがドメイン駆動設計(DDD)である。すべてを導入したわけではない。意識したのは「ドメインモデル中心」「ユビキタス言語」「依存関係の向き」の3原則だけだ。

この記事でわかること

  • ドメイン駆動設計(DDD)を「ドメインモデル中心」「ユビキタス言語」「依存関係の向き」の3原則だけに絞って導入する考え方
  • 5000行を超えて肥大化したServiceクラスを、Entity・Value Object・Domain Service・Repositoryに切り分けた具体的な手順
  • DDD導入で実際にかかったコスト(コード量の増加・チームへの説明コスト)と、それでも得られたメリット
  • DDDが向いているプロジェクトと向いていないプロジェクトの判断基準

PARTS LIST /目次

なぜドメイン駆動設計(DDD)を選んだのか

私が担当していたシステムは、いわゆる「継ぎ足しの秘伝のタレ」状態でした。

長年運用されてきたシステムは、初期の設計思想を誰も知らないまま、機能追加だけが繰り返されていたのです。毎日がお化け屋敷を探検するような気分でした。

どこに罠があるかわからない恐怖と戦いながら、私は「根本的に変えなければ、いつか破綻する」と確信していました。

なぜなら、コードがビジネスの変化についていけなくなっていたからです。

リファクタリング前のコードが抱えていた問題

当時のコードは、まさにカオスそのものでした。特に深刻だったのが、責務が曖昧なServiceクラスの存在です。

OrderServiceというクラスには、注文に関するありとあらゆる処理が詰め込まれていました。在庫チェック、決済処理、メール送信、ポイント計算。

これら全てがひとつのクラスに書かれており、行数は5000行を超えていました。どこでデータを変更しているのか、誰も把握できません。

さらにDTOとEntityの境界崩壊も起きていました。データベースのテーブル構造を表すクラスが、そのまま画面表示や計算ロジックに使われていたのです。

データベースのカラム変更が、画面の表示ロジックにまで波及してエラーを引き起こす。そんな事態が日常茶飯事でした。

あわせて読む

結果として、仕様変更のたびに影響範囲が読めない状態に陥っていました。「一部を変えるだけ」の修正に大量のコードリーディングと工数が必要。

なぜなら、あちこちに散らばったロジックを全て見つけ出し、テストしなければならないからです。

小手先のリファクタリングでは限界を感じた理由

最初は、メソッドの抽出や変数名の変更といった、一般的なリファクタリングを試みました。しかし、すぐに限界が訪れます。

命名を直しても本質は変わらないからです。「スパゲッティコード」の見た目を綺麗に整えたところで、絡まり合った中身はそのまま。

変数の名前がint aからint priceになっても、そのpriceがあらゆる場所で書き換えられていれば、バグはなくなりません。

また、設計思想が共有されていなかった点も致命的でした。私がコードを綺麗に整理しても、翌週には別のメンバーがまた元の書き方で機能を追加してしまいます。

「なぜここにロジックを書いてはいけないのか」という基準(ドメイン知識の置き場所)がチーム内で統一されていない限り、コードは再び腐敗していくのです。

今回のリファクタリングで採用したDDDの考え方

DDDには膨大な概念があります。すべてを一度に導入しようとすれば、チームは混乱し、開発はストップしてしまうでしょう。

そこで私は「現状の課題解決に必要な部分だけ」をつまみ食いすることにしました。教科書通りの完璧さよりも、現場での実用性を優先させたのです。

DDDの全てはやっていない理由

まず、戦略的DDDは最小限に留めました。「境界づけられたコンテキスト」(モデルの前提が通用する範囲を区切る考え方)という考え方があります。「コンテキストマップ」(複数のコンテキストの関係を図にしたもの)も同様です。

しかし、既存のモノリシックな(一枚岩の)システムをいきなりマイクロサービスのように分割するのはリスクが高すぎます。

まずは目の前のコードを整理する「戦術的DDD」に集中しました。

戦術的DDDに絞った判断は、現実的な導入ラインを探った結果です。チームメンバー全員がDDDの専門家ではありません。

学習コストを抑えつつ、効果を実感してもらうには、コードレベルでの改善から始めるのが最善だと判断しました。

特に意識した3つのポイント

リファクタリングの指針として、以下の3つだけは徹底しました。これさえ守れば、コードの品質は劇的に向上します。

  1. ドメインモデル中心であることです。これまでは「データ」と「処理」がバラバラでした。データを持つクラス(Entity)に、そのデータを使うルール(メソッド)も持たせる。オブジェクト指向の基本に立ち返り、データと振る舞いをセットにしました。
  2. ユビキタス言語です。コード内のクラス名やメソッド名を、現場の業務担当者が使う言葉と一致させました。「ユーザー」ではなく「会員」、「削除」ではなく「退会」。言葉のズレがバグを生む温床だからです。
  3. 依存関係の向きです。ドメイン(業務ロジック)をシステムの中心に置き、データベースやUI(ユーザーインターフェース)がドメインに依存するようにしました。ドメインがデータベースの都合に振り回されるのを防ぐためです。
あわせて読む

実際にどうリファクタリングを進めたか

ここからは具体的な手順をお話しします。いきなりエディタを開いてコードを書き換えることはしませんでした。急がば回れ。まずは現状の理解から始めました。

まず業務ロジックをコードから言語化した

最初に行ったのは、コードリーディングからの日本語化です。

複雑怪奇なOrderServiceのコードを読み解き、「ここで何をしているのか」を日本語で書き出しました。

「もし在庫がなかったらエラー」「合計金額が5000円以上なら送料を0にする」といった具合です。

これにより、クラス設計の前に「業務の流れ」を可視化しました。コードに埋もれていた仕様を掘り起こす作業です。

驚いたことに、誰も知らない謎のロジックが大量に見つかりました。見つかった謎のロジックを整理するだけでも、システムは随分と身軽になります。

Entity / Value Object / Domain Serviceの切り分け

次に、書き出した業務ロジックを適切な置き場所に振り分けていきます。ここでDDDの戦術的パターンが登場します。

この振り分けの発想は、Repositoryパターン(データの出し入れをドメインから切り離す。Martin Fowlerの『エンタープライズアプリケーションアーキテクチャパターン』やDDDで扱われるパターン)と重なる部分があります。また、GoFのデザインパターンのStrategyパターン(アルゴリズムを差し替え可能にする)とも近い考え方です。設計パターンに馴染みがあるエンジニアほど、飲み込みが早かった印象です。

あわせて読む

まず何をEntityにしないかを慎重に決めました。全てのデータをEntityにすると管理が大変です。

IDを持ち、ライフサイクル(生成から消滅まで)を管理すべき対象(注文、会員など)だけをEntityとして定義しました。

一方で、Value Object(値オブジェクト)を増やした理由は、ロジックの散逸を防ぐためです。例えば「住所」や「金額」といった概念です。

これまでは単なるStringやintとして扱われていましたが、専用のクラスを作りました。

「金額は負の値にならない」「住所には郵便番号が必須」といったルールをクラスの中に閉じ込めることで、不正な値がシステムに入り込むのを防げます。

集約(Aggregate)とRepositoryの境界をどう引いたか

Entity・Value Objectを整理した後、もう一つ決める必要があったのが、集約(Aggregate)の境界です。集約とは「一緒に整合性を保ちたいデータのまとまり」のことで、外部からは集約の入り口となるオブジェクト(集約ルート)を経由してしか更新できないようにします。

注文(Order)とその明細(OrderItem)がまさにこの関係でした。これまでは注文明細だけを直接書き換えるコードがあちこちに散らばっていて、合計金額と明細の中身が食い違うバグの温床になっていたのです。注文を集約ルートとし、明細の変更も必ず注文経由で行うようにしたことで、この手のバグは起きなくなりました。

この集約単位の読み書きを担当するのがRepositoryです。Entityを直接DBから引っ張ってくるのではなく、Repositoryという窓口を経由するようにしました。これにより、ドメイン層は「データがどこに保存されているか」を知らなくて済みます。

Serviceの役割を整理した

最後に、肥大化していたServiceクラスのダイエットです。

ここでやらせないことを決めたのが最大のポイントです。アプリケーション層のServiceにはビジネスロジックを一切書きません。EntityにもValue Objectにも収まらない業務ルールは、ドメイン層のDomain Serviceに置きます。

彼らの仕事は「ドメインオブジェクトをリポジトリから取り出し、メソッドを呼び出し、保存する」という進行役のみ。

トランザクション境界の置き方もここで整理しました。ひとつの業務処理(ユースケース)が完了する単位でトランザクションを管理し、データの整合性を保ちます。

ロジックがドメインモデルに移動したおかげで、Serviceクラスは驚くほど薄く、読みやすくなりました。

業務ルールの置き場所:OrderServiceからドメイン層へ
中身
BEFORE
AFTER
合計5000円以上なら送料0業務ルール
OrderService
ShippingCostドメイン層
金額は負の値にならない業務ルール
OrderService
金額(Value Object)ドメイン層
住所には郵便番号が必須業務ルール
OrderService
住所(Value Object)ドメイン層
取り出す → 呼び出す → 保存処理の進行
OrderService
OrderService進行役だけ(アプリケーション層)
NOTEルールは本文に出てくる例。EntityにもValue Objectにも収まらない業務ルールは、ドメイン層のDomain Serviceに置く。

リファクタリング後、コードはどう変わったか

苦労の末、リファクタリングを終えたコードは別物のように生まれ変わりました。見た目の美しさだけでなく、開発のスピードや安全性に直結する変化です。

クラス構成と依存関係の変化

before / after の考え方で言うと、以前は「手続き型」のスクリプトのようなコードでした。上から下へダラダラと処理が書かれていたのです。

afterでは、意味のある小さな部品(オブジェクト)が連携して動く形になりました。

これによりレイヤー構造の明確化が達成されました。

プレゼンテーション層(画面)、アプリケーション層(進行役)、ドメイン層(業務ルール)、インフラ層(DBなど)がきれいに分かれました。

DBを変更してもドメインロジックには影響しませんし、画面のデザインを変えても計算ロジックは壊れません。

各層が独立しているため、テストも書きやすくなりました。

あわせて読む

仕様変更への耐性はどうなったか

最も効果を感じたのは、影響範囲の予測が容易になった点です。

「送料の計算ルールを変えたい」と言われたら、「ShippingCostクラスを見ればいい」と即答できます。

以前のように、コード全体をgrep検索する必要はありません。

影響範囲をすぐ特定できることは、修正時の心理的負担を劇的に下げてくれました。

「ここを直せば、他には影響しない」という安心感があるだけで、エンジニアは自信を持ってコードを変更できます。

お化け屋敷だったプロジェクトは、整理整頓された図書館のように、どこに何があるかすぐに分かる場所に変わりました。

DDDリファクタリングで正直つらかったところ

もちろん、全てが順調だったわけではありません。DDDにはそれなりの「痛み」も伴います。これから導入を考える方のために、正直な体験談をお伝えします。

最初はコード量が増えた

リファクタリング直後、「綺麗になったのに長い」問題に直面しました。クラスの数が倍以上に増えたのです。

String emailで済んでいたものをEmailAddressクラスにするわけですから、ファイル数は確実に増えます。

単純なCRUD(登録・参照・更新・削除)だけの機能なら、DDDはオーバーエンジニアリングになりやすいです。

「これだけの処理にこれだけファイルがいるの?」と、自分でも疑問に思う瞬間がありました。

しかし、長期的に見ればこの「冗長に見える構造」が複雑さを抑え込む防波堤になります。

チームに説明するコストが高かった

もうひとつの壁は、設計思想はコードだけでは伝わらないことでした。

なぜわざわざValue Objectを作るのか。なぜServiceにロジックを書いてはいけないのか。

こうした設計判断の理由をチームメンバーに理解してもらうには、根気強い対話が必要でした。

コードレビューで何度も「ここはドメイン層に移しましょう」と指摘し、勉強会を開いてDDDのメリットを共有しました。

共通認識ができるまでは、開発スピードが一時的に落ちる覚悟が必要です。

それでもDDDでリファクタリングして良かった理由

初期コストはかかりましたが、そのコストを補って余りあるメリットがありました。一度軌道に乗れば、開発効率は上がりやすくなります。

コードレビューの観点が揃った

DDDの導入により、良い / 悪いの基準が明確になりました。

以前は「好みの問題」で揉めていたコードレビューが、「ドメインモデルとして正しいか」という建設的な議論に変わりました。

「このロジックはこのEntityが持つべき責務ではないか?」「この名前はユビキタス言語とずれている」といった指摘が飛び交うようになり、レビューの質が格段に向上しました。

基準があれば、迷いがなくなります。

ドメイン知識がコードに残るようになった

何より嬉しかったのは、人が変わっても理解できる設計になったことです。

新しく入ったメンバーがコードを読んだとき、「コードを見れば業務仕様がわかる」と言ってくれました。

仕様書はすぐに古くなりますが、動いているコードは嘘をつきません。

ドメイン知識がコードそのものに表現されているため、コードがそのまま生きたドキュメントとしての役割を果たしてくれるのです。

ドメイン知識が生きたドキュメントとして残ることは、長期的に運用するシステムにとって、計り知れない資産となります。

DDDはどんな人・プロジェクトに向いているか

DDDは素晴らしい手法ですが、万能ではありません。向き不向きを見極めて導入することが成功の鍵です。

向いているケース

業務が複雑なシステムには最適です。ECサイト、金融システム、物流管理など、ルールが多く、例外処理が複雑なドメインではDDDの真価が発揮されます。

また、仕様変更が多い場合も向いています。ビジネスの変化に合わせてコードを安全に変更し続ける必要があるなら、DDDのコストを払う価値は十分にあります。

中長期で育てるシステムなら、迷わず導入を検討すべきです。

向いていないケース

逆に、CRUD中心の単純な掲示板や管理ツールには不向きです。データを右から左へ流すだけの処理にDDDを適用すると、無駄にコードが複雑になるだけです。

また、短命なプロジェクトや、使い捨てのキャンペーンサイトなども推奨しません。学習コストや設計コストを回収する前にプロジェクトが終わってしまうからです。

よくある質問

ドメイン駆動設計(DDD)を使った今回のリファクタリングについて、読者からよく聞かれる質問をまとめました。戦術的DDDの範囲、Entity・Value Objectの使い分け、向き不向きの判断基準など、本文で扱った論点を中心にしています。細かい手順や根拠は各見出しの本文もあわせて確認してください。

Q1. ドメイン駆動設計(DDD)とは何ですか?

A1. 業務のルール(ドメイン知識)をコードの構造そのものに表現し、ビジネスの変化に強いソフトウェアを作るための設計手法です。本記事では「ドメインモデル中心」「ユビキタス言語」「依存関係の向き」の3原則に絞って適用しています。

Q2. DDDはすべての概念を導入する必要がありますか?

A2. いいえ、必要ありません。「境界づけられたコンテキスト」のような戦略的DDDは最小限にとどめ、目の前のコードを整理する戦術的DDDに集中するだけでも効果があります。学習コストを抑えつつ効果を実感できる範囲から始めるのが現実的です。

Q3. ユビキタス言語とは何ですか?

A3. コード内のクラス名やメソッド名を、現場の業務担当者が使う言葉と一致させる考え方です。例えば「ユーザー」ではなく「会員」、「削除」ではなく「退会」のように言葉のズレをなくし、バグの温床を減らします。

Q4. EntityとValue Objectはどう使い分けますか?

A4. IDを持ち、ライフサイクル(生成から消滅まで)を管理すべき対象(注文、会員など)はEntityにします。金額や住所のように値そのものに意味があり、ルールを閉じ込めたい概念はValue Objectとして扱います。

Q5. DDDを導入するとコード量は増えますか?

A5. はい、増える傾向があります。String emailで済んでいたものをEmailAddressクラスにするなど、クラスの数が増えるため、短期的には「綺麗になったのに長い」と感じる場面があります。

Q6. DDDはどんなプロジェクト・システムに向いていますか?

A6. ECサイト・金融システム・物流管理のように業務が複雑で仕様変更が多く、中長期で育てるシステムに向いています。逆に、単純なCRUD中心の掲示板や短命なプロジェクトには、学習コストや設計コストが見合わずオーバーエンジニアリングになりがちです。

Q7. Service層にビジネスロジックを書いてはいけないのはなぜですか?

A7. アプリケーション層のServiceは「ドメインオブジェクトをリポジトリから取り出し、メソッドを呼び出し、保存する」という進行役に限定するためです。EntityやValue Objectに収まらない業務ルールは、ドメイン層のDomain Serviceに置きます。

Q8. 集約(Aggregate)とは何ですか?

A8. 一緒に整合性を保ちたいデータのまとまりのことです。外部からは集約の入り口となるオブジェクト(集約ルート)を経由してしか更新できないようにすることで、データの不整合を防ぎます。

まとめ:DDDは「整理術」だった

今回のリファクタリングを通じて私が得た最大の収穫は、DDDが決して難解な理論ではなく、実用的な「整理術」だという気づきです。

設計の正解が分かったわけではありません。今でも迷うことはあります。しかし、考える軸が手に入ったことは大きな進歩です。

「このロジックはドメインの知識か?」「このロジックはアプリケーションの都合か?」。この問いかけができるだけで、コードは驚くほど整理されます。

リファクタリングの目的がブレなくなったのも、今回の収穫のひとつです。

単にコードを綺麗にするのではなく、「ビジネスの変化に強いコードにする」という明確なゴールに向かって進めるようになりました。

REVISIONS / 改訂履歴
REV.DATE内容
52026-10-16図解を描き直し(図面のデザインにそろえた)
42026-10-14雰囲気だけの挿絵を外した
32026-10-092026年10月時点の情報で全体を見直し
22026-10-06記載内容の事実確認と修正
12025-12-20初版
NEXT READ本棚から1本

AIで開発を組み直すなら、まずこの5冊から

入門・実践・設計まで、Claude Codeの本をレベル別に5冊選んで比べました。

→ おすすめ5冊を見る

  • この記事を書いた人
  • 最新記事

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

IT全般