DUICUO

Spring アプリケーションの欠陥に基づいた DDD についてお話しましょう。

Springフレームワークは、エンタープライズレベルのJavaアプリケーション構築における事実上の標準となっています。多くのエンタープライズプロジェクトは、Springプロジェクトとそのサブプロジェクト、特にJava Webプロジェクトを基盤として構築されています。その多くはSpringを採用し、Web、Service、Daoという階層化原則に従っており、下位層が上位層にサービスを提供しています。しかし、Petri Kainulainen氏は自身のブログで、多くのSpring Webアプリケーションに潜む最大の欠陥を指摘しました。

[[319091]]

経験豊富なソフトウェア開発者の多くは、DDDという言葉を耳にし、自分のプロジェクトに適用しようと試みたことがあるでしょう。リポジトリを作成したのに、しばらくすると従来のDAOに似てきてしまい、実装が正しいのか疑問に思う、といった状況に遭遇したことはありませんか?あるいは、集約型リポジトリを作成したら、膨大な量になり、参照するオブジェクトが多すぎて、またミスを犯してしまった、といった経験はありませんか?

この記事では、ドメイン駆動設計と、ドメイン駆動設計における貧血、失血、充血モデルの使用について説明します。

Springアプリケーションの欠陥

現在、Springフレームワークを使用するJava Webアプリケーションのほとんどは、単一責任原則と関心の分離原則を厳格に遵守しています。しかし、これらの原則に加えて、望ましくないアンチパターンや設計原則もいくつか生まれています。例えば、次のようなものがあります。

  • ドメイン モデル オブジェクトは、アプリケーション データを格納するためにのみ使用されます (ドメイン モデルは、ドメイン モデル貧血症のアンチパターンを使用します)。
  • ビジネス ロジックは、ドメイン オブジェクトのデータを管理するためのサービス層に存在します。
  • サービス層では、アプリケーション内の各エンティティはサービス クラスに対応します。

Springフレームワークを使ってアプリケーションを構築する開発者は、依存性注入のメリットを熱心に語ります。しかし残念ながら、多くの開発者は、単一責任原則や関心の分離といったSpringの利点をアプリケーションで適切に活用できていません。SpringベースのWebアプリケーションを詳しく見てみると、多くのアプリケーションが、以下のような一般的だが欠陥のある設計原則に基づいて実装されていることがわかります。

これらの設計原則は広く用いられています。現在私が取り組んでいるJava Webプロジェクトでは、アーキテクチャ設計においてこれらの原則を採用しており、主に一般的な3層または多層アーキテクチャを採用しています。これらは一般的にどのようなものなのでしょうか?

  • Web 層 (一般にプレゼンテーション層と呼ばれる): ユーザー入力を受け取り、データをサービス層に送信します。
  • サービス層 (ビジネス ロジック層とも呼ばれます): この層は、トランザクションの境界を定義し、ビジネス ロジックを処理し、アクセス許可を管理および承認し、ストレージ層と通信します。
  • ストレージ層 (データ アクセス層): データベースと通信し、データを保存します。

しかし、何か気づきましたか?問題はサービス層にあります。トランザクション管理、ビジネスロジック、権限チェックなど、サービス層はあまりにも多くの責任を負っています。これは単一責任の原則と関心の分離の原則に違反し、多くの依存関係と循環依存関係を生み出しています。ビジネスの複雑さが増すにつれて、サービス層のコードは非常に大きく複雑になり、テストコストの増加に直接つながります。サービス層には主に2つの問題があります。

  • アプリケーションのビジネス ロジックはサービス層から提供されます。

ビジネスロジックはサービス層全体に散在しています。特定のビジネスルールがどのように実装されているかを確認する必要がある場合、まずそのルールを見つける必要があります。さらに、複数のサービスクラスが同じビジネスルールを必要とする場合、そのルールはサービス間でコピーされるため、大量のコード重複が発生します。

  • 各ドメイン モデル クラスには、サービス層にサービス クラスがあります。

これは単一責任原則に違反しています。単一責任原則とは、各クラスは1つの責任のみを持ち、その責任はクラスによって完全にカプセル化されるべきであると規定しています。クラス内のすべてのサービスは、この責任と一貫性を保つ必要があります。

現状を改善するために、ドメイン駆動設計の関連する概念と実装戦略を以下で詳しく紹介します。

ドメイン駆動設計(DDD)

DDDの全体構造は、インフラストラクチャ、ドメイン、アプリケーション、インターフェース(ユーザーインターフェースまたはインターフェース層とも呼ばれます)の4つの層に分かれています。各層の機能は以下で説明します。


  • ユーザー インターフェイス (プレゼンテーション層): ユーザーに情報を表示し、ユーザー コマンドを解釈する役割を担います。
  • アプリケーション層:この層はアプリケーションのアクティビティを調整します。ビジネスロジックは含まれず、ビジネスオブジェクトの状態も保存しませんが、アプリケーションタスクプロセスの状態を保存できます。
  • ドメイン層:この層にはビジネスドメインに関する情報が含まれます。ビジネスオブジェクトの状態はここに保存されます。ビジネスオブジェクトの永続性とその状態は、インフラストラクチャ層に委譲される場合があります。
  • インフラストラクチャ層:この層は、他の層をサポートするライブラリとして機能します。層間の情報交換を容易にし、ビジネスオブジェクトの永続性を実現し、ユーザーインターフェース層のサポートライブラリも備えています。

基本概念

実在物

オブジェクトは、属性ではなく識別子によって区別される場合、エンティティと呼ばれます。例えば、2つのオブジェクトが異なる識別子を持っている場合、他のすべての属性が同じであっても、それらは全く異なるエンティティであるとみなされます。

値オブジェクト

一意の識別子を持たないオブジェクトを値オブジェクトと呼びます。これは、ドメインにおいて、必ずしも一意の識別子が必要なわけではないためです。つまり、それが特定の「もの」ではなく、「何であるか」だけが重要です。例えば、注文プロセスにおいて、配送先住所の場合、住所情報が同じであれば、同じ配送先住所とみなします。一意の識別子がないため、「これ」や「あれ」という値オブジェクトを区別することはできません。

ドメインサービス

重要なドメインの振る舞いや操作の中には、エンティティオブジェクトや値オブジェクトとしてモデリングするのに適さないものがあります。これらは本質的に単なる操作であり、具体的なオブジェクトではありません。さらに、これらの操作は多くの場合複数のドメインオブジェクトに関係しますが、これらのサービスはこれらのオブジェクトを調整して操作を完了するだけです。したがって、これらはドメインサービスに分類できます。ドメインサービスはすべてのビジネスロジックを実装し、様々な検証方法を通じてその正確性を保証します。同時に、ドメインロジックがアプリケーション層に現れるのを防ぎます。本質的に、ドメインサービスはファサードのような感覚を持っています。

集約と集約ルート

集約は、ドメインオブジェクト間の明確な関係と境界を定義することでドメインモデルの凝集性を実現します。これにより、複雑で維持が困難なオブジェクト関係のネットワークの形成を回避できます。集約は、凝集度の高い関係を持つ関連するドメインオブジェクトの集合を定義します。集約は、データ変更の単位と考えることができます。

集約ルートはエンティティオブジェクトに属し、ドメインオブジェクト内で非常に凝集度の高いコアオブジェクトです。(集約ルートはグローバルに一意の識別子を持ちますが、エンティティは集約内で一意のローカル識別子のみを持ちます。値オブジェクトには一意の識別子がないため、「この値オブジェクト」や「あの値オブジェクト」といったものは存在しません。)

集約にエンティティが 1 つしかない場合は、そのエンティティが集約ルートになります。ただし、エンティティが複数ある場合は、集約内のどのオブジェクトが独立した意味を持ち、外部ドメインと直接対話できるかを考慮する必要があります。

工場

DDDにおけるファクトリーの概念は、カプセル化の考え方も体現しています。ファクトリーを導入する理由は、ドメインオブジェクトの作成が単純な「new」操作ではなく、比較的複雑なプロセスになる場合があるためです。ファクトリーの役割は、オブジェクト作成の詳細を隠蔽することです。実際、ほとんどの場合、ドメインオブジェクトの作成はそれほど複雑ではないため、オブジェクトを作成するには単純なコンストラクタを使用するだけで済みます。オブジェクト作成の詳細を隠蔽することの利点は明らかです。ドメイン層のビジネスロジックがアプリケーション層に漏れるのを防ぎ、アプリケーション層の負担も軽減します。アプリケーション層は、ドメインファクトリーを呼び出すだけで目的のオブジェクトを作成できるからです。

リポジトリ

リポジトリはインフラストラクチャをカプセル化し、クエリと永続的な集計操作を提供します。これにより、モデルレベルに集中し、オブジェクトの保存とリポジトリへのアクセスを委譲できます。リポジトリはデータベースラッパーではなく、ドメイン層とインフラストラクチャをつなぐ橋渡しの役割を果たします。DDDはドメインモデルに関係するものであり、データベース操作には関係しません。

DDD設計

DDDの概念は、理解するのが少し抽象的かもしれません。デザインパターンに似た概念で、非常に便利に思えますが、実際にコードを開発する際には、どのように適用すればよいか分からなかったり、直接適用しようとしてぎこちない結果になったりするかもしれません。DDDの戦略的設計には、主にドメイン/サブドメイン、共通言語、境界付けられたコンテキスト、アーキテクチャスタイルといった概念が含まれます。

ドメインとサブドメイン

現実世界では、ドメインは問題領域とソリューションシステムで構成されます。ソフトウェアは一般的に、現実世界の部分的なシミュレーションと考えられています。データモデリング(DDD)では、ソリューションシステムは境界付けられたコンテキストにマッピングされます。境界付けられたコンテキストとは、ソフトウェアが問題領域に対して提供する具体的かつ有限なソリューションです。

日常の開発では、大規模なソフトウェアシステムを複数のサブシステムに分割することが一般的です。この分割は、アーキテクチャ上の考慮事項やインフラストラクチャに基づいて行われる場合があります。しかし、DDDでは、システムをドメイン、つまりビジネスロジックに基づいて分割します。

境界付けられたコンテキスト

特定の責任は明示的な境界によって定義されます。ドメインモデルはこれらの境界内に存在します。これらの境界内では、各モデル概念(属性や操作を含む)は特定の意味を持ちます。

名詞、動詞、形容詞を含むすべての概念を境界付けられたコンテキスト内にグループ化することで、その境界付けられたコンテキストにおける共通言語を構築します。この共通言語は、チームメンバー全員がコミュニケーションに使用する言語であり、ビジネスアナリスト、コーダー、テスターは皆、この共通言語を通して直接コミュニケーションを取るべきです。

上述の様々なサブドメイン間の統合問題は、本質的には境界付きコンテキスト間の統合問題です。統合においては、主にドメインモデルと統合手法の関係に着目します。例えば、RESTリソースとの統合が必要な場合、インフラストラクチャ(SpringのRestTemplateなど)を提供する必要がありますが、これらの機能はコアドメインモデルには含まれていません。どうすればよいでしょうか?答えは、アンチコラプションレイヤーです。このレイヤーは、外部サービスプロバイダーとのやり取りを行い、外部の概念をコアドメインが理解できる概念に変換する役割を担います。もちろん、アンチコラプションレイヤーは、境界付きコンテキスト間の数多くの統合手法の一つにすぎません。他にも、共有カーネルやオープンホストサービスなどがあります。詳細については、原著『ドメイン駆動設計の実装』を参照してください。境界付きコンテキスト間の統合関係は、異なるコンテキストにおけるドメイン概念間のマッピング関係としても理解できます。そのため、境界付きコンテキスト間の統合はコンテキストマッピンググラフとも呼ばれます。

まとめ

この記事では、Spring Webアプリケーションの欠点を例に挙げ、改善策を紹介した後、ドメイン駆動開発(DDD)の関連する概念と設計戦略を紹介します。多くの概念を解説してきたので、読者の皆様はDDDの実装方法を学びたいと強く願っていることでしょう。次の記事では、いくつかの種類のドメインモデルとDDDの具体的な実践例を紹介します。