“設計がわからない”あなたへ!!ドメイン駆動設計・DDDの第一歩【設計・アーキテクチャ】

  • LINEで送る
“設計がわからない”あなたへ!!ドメイン駆動設計・DDDの第一歩【設計・アーキテクチャ】

—— 設計に悩んでいるあなたへ ——

 「動くから、とりあえずOK」
 「サービスクラスが巨大になってきたけど、どう整理すればいいか分からない…」

 そんなモヤモヤを感じたことはありませんか?
 あなたは決して一人ではありません。多くのエンジニアが、**“なんとなくの設計”**で日々コードを書きながら、どこかで引っかかりを感じています。

 この記事で紹介するのは、その“引っかかり”に名前を与え、解きほぐしてくれる考え方――
それが、**ドメイン駆動設計(DDD: Domain-Driven Design)**です。

 難解な理論ではありません。むしろDDDは、コードに“業務の意味”を込めるという、とてもシンプルな発想です。設計が分からなくても大丈夫。この一歩から、一緒に始めてみましょう。

なぜDDDが注目されているのか?

現場で起きがちな“よくある問題”

  • 業務の変更に弱く、少しの仕様変更でコード全体が壊れる
  • 技術主導で設計されており、業務の意図やルールが読み取れない
  • 「どこに何を書くべきか」が曖昧で、修正のたびに迷子になる

「設計と業務が噛み合ってない」の根本原因

 これは、業務とコードが“同じ言語”で語られていないことにあります。現場で使われている用語やルールがコードに現れず、開発者と業務担当者の間に認識のズレが起きてしまうのです。

複雑さに立ち向かうためのアプローチ

 DDDは、単に整理整頓のための設計手法ではありません。ビジネスの複雑さを正面から受け止め、モデリングの力で管理・表現していくためのアプローチです。

ドメイン駆動設計(DDD:Domain-Driven Design)とは何か?

 DDD (Domain-Driven Design) とは、ソフトウェアを業務観点から設計しようとする考え方です。ここでいう「ドメイン」とは、アプリケーションが対象とする業務領域や問題領域のことです。たとえばECサイトであれば、「商品」「カート」「注文」「支払い」「配送」などが該当します。

 DDDでは、こうしたドメインの構造やルールを深く理解し、それをそのままコードに反映させることで、業務にフィットした設計を目指します。

 DDDの本質は、「ユーザーが本当にやりたいことを理解し、それに合わせたモデルを作り、事業者と開発者が共通の言葉で共有しながら開発すること」にあります。デルを作り、それを事業者と開発者が共通の言葉で分かち合いながら組み立てる」ことにあります。

 上の図は、DDDを使わない設計と使った設計の違いを示したものです。 DDDを使わない場合、すべての処理を1つの巨大なクラスで管理してしまい、保守が難しくなります。

 一方DDDでは、業務の流れに沿って「商品」「カート」「注文」「支払い」「配送」などのドメインごとにクラスを分けることで、構造が明確になり、責務の分離と柔軟な変更が可能になります。

ECサイトの注文を例に考えてみよう!

上の図にもあるように、ECサイトでの注文処理は次のような流れで構成されています:

商品を選ぶ → カートに入れる → 注文を確定する → 支払う → 配送する

 DDDを使わない場合、この一連の処理をすべて OrderService のような1つのクラスで扱ってしまいがちです。

class OrderService {
    public void processOrder(Request request) {
        // 商品情報取得
        // カートに追加
        // 在庫チェック
        // 合計金額計算
        // 注文確定
        // 支払い処理
        // 配送手配
        // DB保存
    }
}

このように1つの巨大なクラスにすべて詰め込むと、責務が曖昧になり修正や拡張が困難になります。

一方、DDDではこの処理を次のように「業務単位」に分けて設計します:

  • 商品を選ぶ → Product エンティティと ProductRepository
  • カートに入れる → Cart エンティティと CartService
  • 注文を確定する → Order エンティティ
  • 支払う → Payment エンティティと PaymentService
  • 配送する → Shipment エンティティと ShippingService

 そして、それらをまとめる役割として OrderService(アプリケーションサービス)が流れを制御します。

public class OrderService {
    public Order placeOrder(Cart cart, Address address, PaymentMethod paymentMethod) {
        Order order = new Order(cart.getItems(), address);
        order.confirm();
        orderRepository.save(order);

        paymentService.pay(order, paymentMethod);
        shippingService.ship(order);

        return order;
    }
}

 このように、DDDに基づいて小さな業務ごとにクラスを分けることで、 コードが業務の構造と一致し、理解しやすく・保守しやすい設計になります。

ドメイン駆動設計の構成要素

 DDDを理解する上では、まずソフトウェア設計におけるレイヤ構造――プレゼンテーション層(UI)・アプリケーション層・ドメイン層・インフラ層――を把握しておくことが大切です。

 この中で ドメイン層が“中心” にあり、アプリケーションの業務的な意味を担います。以下に、各層の役割を表にまとめました:

各層の名前 主な役割・特徴
プレゼンテーション層(UI)ユーザーの操作や画面表示・APIの受付など、外部とのインターフェースを担当Web画面、REST API、フロントエンド
アプリケーション層ユースケース単位の処理を制御。ドメイン層を呼び出し業務の流れを調整OrderService.placeOrder()
ドメイン層(中心)業務ロジックやルールの中心。エンティティやドメインサービスを含むOrder, Product, Payment
インフラ層DBや外部システムとのやりとりなど、技術的な実装を担うMySQL, SMTP, 外部API

 DDDでは、業務ロジックを整理し、モデルとして表現するために、いくつかの重要な構成要素が用意されています。以下に、それぞれの役割を詳しく解説します。

🧱エンティティ(Entity)

  • 一意な識別子(ID)を持ち、時間とともに状態が変化する業務オブジェクト
  • 「どれか」が重要であり、同じ属性であっても異なる個体として扱われる
  • 業務上の履歴や変更の追跡が必要な場面でよく使われる
  • 永続化(データベースなど)において、IDで一意に識別されることが前提
  • 例:Order(注文ごとのIDで識別)、Customer(顧客IDで管理)、Product(商品IDによって個別の商品を追跡)

🔖 値オブジェクト(Value Object)

  • 識別子を持たず、属性だけで意味が決まり、不変であるデータ
  • 「何であるか」ではなく「どんな値か」が重要で、同じ属性であれば同一とみなされる
  • イミュータブル(不変)であり、変更がある場合は新たに作り直す
  • 計算や比較などのロジックを持つこともある(例:合計金額の計算など)
  • モデルの中で使い捨てるような小さな値を表すのに最適
  • 例:Address(住所の集合としての値)、Money(通貨+金額の組)、Quantity(数量)

🧩 集約(Aggregate)

  • 関連するエンティティや値オブジェクトを一つのまとまりとして扱う概念
  • 集約の中では整合性ルールが保証されるようにする(例:在庫数がマイナスにならないなど)
  • 集約には必ず「集約ルート」と呼ばれる中心のエンティティが存在し、外部とのやりとりは必ずルートを介して行う
    • これにより、集約の内部構造を隠蔽し、保守性や拡張性が高まる
  • 例:CartItem を複数持つ構造 → 外部は cart.addItem(product, quantity) のように操作

🧷 集約ルート(Aggregate Root)

  • 集約の中心となるエンティティで、集約内のすべての操作の入口となる存在
  • 外部からのアクセスや変更は、必ずこのルートエンティティを介して行われる
    • これにより、集約内部の整合性ルールを破られないように保護する役割を担う
  • 集約ルートが責任を持って内部のエンティティや値オブジェクトに命令を伝えることで、**外部との接点を制御し、内部構造の隠蔽(カプセル化)**を実現する
  • IDによって永続化・取得される単位でもあり、リポジトリは基本的に集約ルート単位で設計される
  • 例:CartItem を複数持ちますが、Item は外部から直接操作されず、すべて Cart を介して追加・変更される

📦 リポジトリ(Repository)

  • エンティティや集約を保存・取得する役割を担うインターフェース
  • データベースやファイルなどのインフラ層の技術的詳細を隠蔽し、ドメイン層がインフラ層の実装に依存しないようにする
  • 集約ルート単位で設計されるため、リポジトリは「何を保存・取得するか」が明確になる
  • 通常、リポジトリはインターフェース(OrderRepository)としてドメイン層に定義し、実装(OrderRepositoryImpl)はインフラ層に配置される
    • これにより、テストや移植が容易になり、ドメインロジックを純粋に保つことができる
  • 例:OrderRepository.findById(orderId)ProductRepository.save(product)

🛠 ドメインサービス(Domain Service)

  • 特定のエンティティに属さないが、業務的に意味を持つ処理を提供するドメイン層のコンポーネント
  • 業務ロジックが複数のエンティティにまたがる場合や、値オブジェクトの組み合わせだけでは表現できない処理に使われる
  • あくまでドメインロジックの一部であり、UIやDB処理などの技術的な処理は行わない
  • 状態は持たず、関数的(ステートレス)に扱われることが一般的
  • 責務が曖昧な処理が Service に集中しすぎないよう注意し、命名には業務用語を使うことで意味を明確にする
  • 例:決済処理ルールをまとめた PaymentService、配送可否を判定する ShippingService など

⚙ アプリケーションサービス(Application Service)

  • ユースケース単位で業務処理の流れを制御する責任を持つ層で、プレゼンテーション層とドメイン層の仲介役
  • 状態は基本的に持たず、ドメイン層の機能を組み合わせて呼び出し、全体の流れを組み立てる役割を担う
  • トランザクションの開始・終了やログ出力、リトライなどの周辺処理もここで担当する
  • ドメイン知識(ルールや判断)はドメイン層に委ね、アプリケーションサービスは“業務の順序やタイミング”に集中する
  • コントローラやバッチの呼び出し元から最初に呼ばれる処理であり、ドメイン層との橋渡しになる
  • 例:OrderService.placeOrder()(カートから注文を作成 → 支払い → 配送の一連の流れを制御)

💬 ユビキタス言語(Ubiquitous Language)

  • ドメイン駆動設計において特に重要な考え方で、開発者・業務担当者・ドメインエキスパートが共通で使う言葉や表現を指す
  • 会話や設計、コードの中で使われる用語を統一することで、認識のズレや設計ミスを防止する
  • この言語は単なる命名ルールではなく、業務の意味やルールを正確に反映した表現であることが重要
  • クラス名・メソッド名・変数名などにもユビキタス言語をそのまま使うことで、コード自体が仕様書として扱うことができる
  • 言葉が業務とコードを結ぶ橋渡しとなり、非エンジニアとのコミュニケーションも円滑になる
  • 例:「注文を確定する」という業務 → order.confirm() / 「配送を手配する」→ shipping.prepare()

🧠 ドメインモデル(Domain Model)

  • 業務ルールや構造をソフトウェア上で再現したドメイン層の中核的な設計モデルの集合
  • 単なるデータ構造ではなく、業務の意味や振る舞いを表現する責任を持つ
  • エンティティ・値オブジェクト・ドメインサービス・集約などが相互に連携し、業務全体のルールをモデルとして実装する
  • ドメインモデルは「コードとして動作する業務知識の表現」であり、技術ではなく業務の視点で設計されることが重要
  • ユビキタス言語に沿って設計され、読みやすく、変更しやすいモデルとして保守性にも寄与する
  • 例:注文処理に関する Order, Payment, Shipping などのクラスや、それらのやりとりを支えるロジックの集合業務ルールや構造をソフトウェア上で再現したモデル群。

 このような構成要素を正しく設計し、業務とコードの整合性を保つことで、DDDは「複雑な業務を扱える、柔軟で保守性の高い設計」を実現します。

DDDが活きるシチュエーション/向かない場面

○ DDDが力を発揮するケース ○

DDDは、以下のような状況で特に力を発揮します:

  • 業務ルールが複雑で、例外や条件分岐が多い
    • 例:金融、保険、EC、在庫管理など
  • 仕様変更が頻繁に発生する業務
    • ビジネスの成長とともにルールが変わる
  • 複数人でのチーム開発で役割と責任を明確にしたい場合
  • 非エンジニア(業務担当者)と協力して開発する必要がある
    • 共通言語(ユビキタス言語)の重要性が増す
  • リファクタリングしながら育てる設計が求められる長期運用プロジェクト

❌ あえて使わないほうがいいシステム ❌

一方で、以下のようなケースではDDDのメリットが薄く、オーバーエンジニアリングになりがちです:

  • CRUD中心のシンプルなアプリ
    • 例:社内向けの業務帳票ツール、設定画面、マスタ管理など
  • 業務がほぼ決まっており変更が少ない(ロジックの再利用性も低い)
  • とにかく早く作ることが最優先なPoC・プロトタイピング
  • 開発チームがDDDの前提知識を持っていない場合(かえって混乱を招く)

🔍「DDDを使うべき?」を判断する視点

導入の判断には、以下のような問いを立ててみるとよいです:

  • ビジネスルールは複雑か?ルールが増える可能性があるか?
  • エンジニアと業務担当者で言葉のすれ違いが起きていないか?
  • ドメインごとに責務を分けることで恩恵がありそうか?
  • 今後も機能追加・仕様変更が定期的に発生しそうか?

よくある誤解と注意点

DDDはフレームワークではない

 DDDという言葉を聞くと、特定のライブラリや設計テンプレートを思い浮かべる方もいますが、DDDは「思想」や「設計の進め方」を示すものであり、実装に決まった型はありません。SpringやLaravelなどのフレームワークとはまったく異なる概念です。

すべてのプロジェクトに必要とは限らない

 DDDは強力なアプローチですが、すべてのプロジェクトで採用すべきとは限りません。複雑な業務ロジックやルールを扱う場面では効果を発揮しますが、CRUD中心のシンプルなアプリケーションに対しては、かえって冗長になることもあります。

ドメインエキスパートとの協業がカギ

 DDDは、業務知識を正確にコードへ落とし込むことが求められます。そのためには、現場の業務を熟知した「ドメインエキスパート」との密な協業が欠かせません。技術者だけで設計を進めるのではなく、業務の言葉とルールをベースにしたコミュニケーションが成功のカギです。

まとめ

 DDDは、「業務の意味をちゃんとコードに落とし込もう」という設計の考え方です。クラスをきれいに分けるとか、設計パターンを使うことが目的ではなく、業務の複雑さを正しく表現することがゴール。

 難しそうに見えるかもしれませんが、「これって業務的にどういう意味?」という視点を持つだけで、設計がぐっと良くなります。

 業務と開発のズレを減らしたい、変更に強い設計にしたいなら、まずは小さくDDDを取り入れてみるのがおすすめです!

備考

ECサイトの例のファイル構成例

src/
├── application/                    # ユースケースや業務処理の流れを定義
│   ├── service/                    # 各業務単位のアプリケーションサービス
│   │   ├── OrderService.java       # 注文処理の流れ制御
│   │   ├── CartService.java        # カート操作の制御
│   │   └── PaymentService.java     # 支払い処理の制御
│   └── usecase/                    # 具体的なユースケース単位の操作
│       └── PlaceOrderUseCase.java  # 「注文を確定する」操作をまとめたユースケース
├── domain/                         # 業務ルールやビジネスモデル(中心)
│   ├── model/
│   │   ├── product/                # 商品に関するエンティティと値オブジェクト
│   │   │   ├── Product.java
│   │   │   └── ProductId.java
│   │   ├── cart/                   # カートとそのアイテム
│   │   │   ├── Cart.java
│   │   │   ├── CartItem.java
│   │   │   └── CartId.java
│   │   ├── order/                  # 注文とその明細
│   │   │   ├── Order.java
│   │   │   ├── OrderItem.java
│   │   │   └── OrderId.java
│   │   ├── payment/                # 支払い関連
│   │   │   ├── Payment.java
│   │   │   └── PaymentMethod.java
│   │   └── shipping/               # 配送情報
│   │       ├── Shipment.java
│   │       └── Address.java
│   └── repository/                 # 永続化インターフェース
│       ├── ProductRepository.java
│       ├── OrderRepository.java
│       └── CartRepository.java
├── infrastructure/                 # DBや外部サービスの具体的な実装
│   ├── repository/                 # リポジトリの実装クラス
│   │   ├── ProductRepositoryImpl.java
│   │   ├── OrderRepositoryImpl.java
│   │   └── CartRepositoryImpl.java
│   └── external/                   # 外部サービスとの連携
│       ├── PaymentGatewayAdapter.java
│       └── ShippingAdapter.java
├── presentation/                   # ユーザーとの接点(画面/API)
│   ├── controller/                 # Webコントローラー層
│   │   ├── OrderController.java
│   │   ├── ProductController.java
│   │   └── CartController.java
│   └── dto/                        # リクエスト・レスポンスDTO
│       └── OrderRequest.java
└── shared/                         # 共通処理
    ├── exception/                  # 業務例外定義
    │   └── BusinessException.java
    └── utils/                      # 日付や文字列などの共通ユーティリティ
        └── DateUtils.java

最新の投稿

SNSでもご購読できます。

コメントを残す