エンタープライズギークス (Enterprise Geeks)

企業システムの企画・開発に携わる技術者集団のブログです。開発言語やフレームワークなどアプリケーション開発に関する各種情報を発信しています。ウルシステムズのエンジニア有志が運営しています。

DDD超入門(後編) - Domain-Driven Designの適用Step

前編ではDDDの概要についてふれたが、後編ではDDDの適用Stepを3つのStepに分けて紹介する。

まずStep1として、アプリケーションに登場しうる概念を抽出してドメインモデルで表現する。 次にStep2として、各ドメインの概念を深掘りしてソフトウェアとしてどう表現するかにについて紹介する。 最後にStep3でドメイン部品の仕上げとDDDの恩恵ついてふれていく。

Step1: ドメインモデルによる表現

DDDでは機能やユースケースに加えて、その裏に潜んでいる「概念」を抽出する必要がある。そのため、DDDを導入するにあたってアプリケーションに登場しうる概念を整理した「ドメインモデル」がまず必要となる。全体のコンテキストを整理して、該当のドメインモデルを描くことがDDDの最初の1歩となる。

例として、よくある一般的な受注管理の簡単なドメインモデルをあげる。
「顧客」からある「商品」の「注文」を受けて、該当の「商品在庫」を「倉庫」の所定の「保管場所」から「引当」するまでのドメインモデルである。販売時の価格の「割引」に関するビジネスルールは企業によって色々あると思うが、ここではごくごく簡単な商品に対する「商品割引条件」をあげている。

ドメインモデル

f:id:rxyt:20160718132622p:plain

ドメインとはビジネス上の関心領域のことで、 今回の受注管理のケースでは「受注コアドメイン」を中心に、周辺に「顧客サブドメイン」「商品サブドメイン」「在庫サブドメイン」の各サブドメインが取り巻くという形で整理した。

一口に受注管理といっても、その注文の周辺に関連する概念やサブドメインが登場してくる。ドメインモデルを利用することで、登場する概念がどこのドメイン境界内に位置して、他の概念とどう相互作用するかを可視化できる。これによりドメインに対する理解を深めることができる。また、ドメインモデルをもとにシステム境界やパッケージ境界をきちんと定めることは、概念上のドメイン配置とプログラム上の機能配置との整合性や妥当性の確認にも寄与する。

Step2: ソフトウェアによる表現

ドメインモデルによって概念の整理が完了したら、次にその概念が持っている知識をさらに細かくかみ砕いていき、概念を深掘りしていく。この辺りから前編で言うところの「デザインパターンとしてのDDD」に入ってくる。

一口に知識といっても、アプリケーションには様々なタイプの知識がある。ユースケースに関する知識、ビジネスルールに関する知識、データや構造に関する知識などなど、多様かつ複合的な知識が登場する。DDDでは、これらの知識をソフトウェアとして表現するための構成要素を以下のように定めている。

レイヤー化アーキテクチャ

DDDの全体構成はレイヤー化アーキテクチャを採っている。

レイヤー化は、関心事を上手く分離してレイヤーの責務を明確にするために有効な手段で、ソフトウェアの世界で昔から広く採用されている手法である。DDDでもドメインに関する関心事と、ユーザーインターフェイスや技術要素に関する関心事を隔離するために、レイヤー化を行っている。

DDDでは、以下の4つのレイヤーを定めている。

A. プレゼンテーションレイヤー

ユーザーに情報を提示して、ユーザーの操作やコマンドの解釈を担うレイヤー。 DDDでは、このレイヤーにビジネスロジックを組む込むことを皮肉を込めて「利口なUI」というアンチパターンとしており、避けるべきパターンである。

B. アプリケーションレイヤー

アプリケーションが行うことになっている仕事(ユースケース)を定義し、その仕事をドメインレイヤーのオブジェクトが解決するように指揮(orchestrate)するレイヤー。このレイヤーもビジネスロジックは含まず、実際の処理はドメインレイヤーに委譲して調整役を担う。

C. ドメインレイヤー

ビジネスの概念と、ビジネスルールおよびビジネスが置かれた状況に関する情報を表現するレイヤー。このレイヤーがアプリケーションの核心となるレイヤーで、モデルが息づく場所である。但し、技術的な関心事についての実装はインフラストラクチャレイヤーに委譲する。

D.インフラストラクチャレイヤー

上述のレイヤーを支える一般的な技術的機能を提供するレイヤー。データの永続化に関する技術的な機能(トランザクション管理やO/Rマッパー)や、ユーザーインターフェイスレンダリング機能などが該当する。

上記の4つのレイヤーを図示すると、以下のような図になる。

DDDの構成要素の図

f:id:rxyt:20160718190255p:plain

DDDの構成要素

続いて、各レイヤーで定められているDDDの構成要素について、上の図にそって説明していく。

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

アプリケーションレイヤーに登場する構成要素。ドメインレイヤーの大小様々な知識を指揮(orchestrate)して、ユーザーに対して意味のある単位の機能を提供するサービス。ユースケースに登場してくるような機能は、アプリケーションサービスで表現する。しかし実際のビジネスロジックの記述はドメインレイヤーの部品に委譲して、アプリケーションサービス自体はドメインレイヤーの部品の調整に徹する。

2. エンティティ(Entity)

ドメインレイヤーに登場する構成要素。アプリケーション上で同一性の識別が必要なオブジェクト。つまり、アプリケーション上でオブジェクトの一意性を意識して扱う必要があるオブジェクトである。以下のようなエンティティは業務を遂行するうえで一意に特定する必要がある。

  • 物、サービス(商品、在庫)
  • 人、組織(顧客)
  • 場所(配送先、倉庫、保管場所)
  • 取引(注文、在庫引当)

仮に同姓同名別人の複数の「顧客」がいたとしたら、その顧客達は別々のインスタンスとして扱われるべきで、名称が一緒だからといってその存在を一括りにしてしまうと、注文や配送が目茶苦茶になってしまう。逆に、オブジェクトごとの同一性を意識しない単なる数量や金額といった同値性で語られる概念は、この後に紹介するバリューオブジェクトとなる。

3. バリューオブジェクト(Value Object)

ドメインレイヤーに登場する構成要素。ドメイン上に登場する値そのものをオブジェクト化した同一性を持たない小型のオブジェクト。以下のようなオブジェクトは、プリミティブ型や標準型(String型やDate型)で定義するのが一般的である。

  • 数量
  • 金額
  • コード
  • 日付
  • 区分

これらを「注文番号」クラス、「注文数量」クラス、「配送予定日」クラス、「商品コード」クラスといった業務上の意味を宿らせたクラスとして明示的に定義することで、その値自身がもつ知識をバリューオブジェクトに凝縮させることができ、ソフトウェア上の表現としてもより明確にできる。

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

ドメインレイヤーに登場する構成要素。 エンティティ(物、人、場所、取引など)にも、バリューオブジェクト(値そのもの)にも属さない、ドメイン上のビジネスルールやアクションを提供するサービス。
これらは、エンティティやバリューオブジェクトのように実体のモノとして捉えるのは不自然で、ビジネスルールにもとづいた計算や判定といったアクションを担うサービスとするのが適切である。

例えば今回のドメインモデルで、商品の価格を算定しようとした場合、標準価格だけならばエンティティから簡単に取得できるが、まとめ買いによる割引や期間限定での割引まで考慮しようとすると簡単には取得できない。
実際の販売価格を決定するためには、諸々のビジネスルールを複合的に加味する必要があるが、価格の算定方法をモノとして定義するのは不自然なため、ドメインサービスとして定義する。
この算定方法が至るところに散在すると、コード重複、メンテナンス性低下、価格差異発生などのバグの温床になるため、価格決定に関する知識は、このドメインサービスに凝縮させて外に流出させてはいけない。

ドメインサービスの提供粒度は、アプリケーションサービスの様にそれ単体でユースケースを満たすほど大粒ではなく、そのドメイン固有の知識範囲に特化したサービスとなる。また、ドメインサービスは、計算や判定を行う上で複合的な知識が必要となることが多く、大抵の場合は複数のエンティティやバリューオブジェクトを束ねた中粒のサービスを提供することになる。
逆に言うと、エンティティ単体やバリューオブジェクト単体の知識で提供できるメソッドがあれば、それはドメインサービスではなく、エンティティやバリューオブジェクトに記述すればよい。

5. アグリゲート(Aggregate)

ドメインレイヤーに登場する構成要素。 密接に関連のあるエンティティやバリューオブジェクト同士を集約したオブジェクトのクラスタ

例えば今回のドメインモデルにおける「注文ヘッダ」-「注文明細」の関連セットは、アプリケーションの中でそれぞれ個々で扱われるよりも「注文」という1セットで扱われることが多いだろう。注文商品の明細が分からない注文ヘッダや、注文番号が分からない注文明細だけあっても役に立たないので、結局は都度都度 お互いの関連を辿る羽目になる。

こういった関連をカプセル化してアクセスを単純化する役割を担うのがアグリゲートである。

アグリゲートをきる場合、アグリゲート内の1つのエンティティをアクセスポイント(アグリゲートルート。今回だと「注文ヘッダ」が自然)として定めて外部に公開し、アグリゲート内の入り組んだ関連パスを隠蔽することで、外部からはアグリゲートルートを通じてアグリゲート内の情報に透過的にアクセス出来るようになる。

6. リポジトリ(Repository)

ドメインレイヤーおよびインフラストラクチャレイヤーに登場する構成要素。エンティティやアグリゲートの保管(データストアへの永続化やキャッシュへの追加など)と、保管済みのエンティティやアグリゲートの取り出しを担う役割を持たせる。その名のとおり、まさにオブジェクトの貯蔵庫である。

尚、オブジェクトの保管に関する技術要素については、RDBを利用する場合もあれば、NoSQLを利用する場合もあるが、これらの技術要素に関するコードについてはインフラストラクチャレイヤーで実装(例えば、SQLのクエリコードなど)して、ドメインレイヤーでは技術要素を意識することなく、ドメインの言葉でオブジェクトの保管・取り出しを行う。

その他

ここまで紹介したもの以外でも、上記の構成要素のインスタンス生成を担う「ファクトリ(Factory)」や、概念グループ間の内外の関係を高凝縮・低結合に保つ「モジュール(Module)」など他の構成要素もある。より詳しくは、Eric Evans氏の著書を参照してほしい。

DDDではこれらの構成要素の知識を相互に駆使して、アプリケーションとしての機能やユースケースを成立させようとする。オブジェクト間の相互作用は1対1ではなくネットワーク的に作用するため、コミュニケーション図やロバストネス図などのオブジェクト同士の相互作用モデルリングが欠かせない。

例として、「注文を新規に登録する」ケースの簡単な相互作用の図をあげておく。例では1ユースケースに限っているので樹形図のようになっているが、実際のアプリケーションでは、複数のユースケースから複数のサービスを利用し合うのでネットワーク的に相互作用することになる。

ロバストネス図

f:id:rxyt:20160713175203p:plain

Step3: ドメイン部品の仕上げ

Step2まで来れば、一応アプリケーションとして稼動するラインまでに到達する。ここからさらにDDDの効果を高める上での重要なポイントは、より洗練されたドメイン部品(ドメインサービス、エンティティ、バリューオブジェクト、アグリゲート、リポジトリ)に仕上げていくことである。ここで言う洗練とは、「凝縮性が高い」と「再利用性が高い」を意味している。

凝縮性・再利用性を高めていくには、特定のユースケースに偏らないドメイン本来の知識をドメイン部品で表現できるようにリファクタリングを重ねていく必要がある。特定のユースケースに偏ったドメイン部品を組むと、別のユースケースからは再利用しづらくなり、結果としてユースケースごとに似たようなロジックが生まれ凝縮性も失ってしまう。

凝縮性・再利用性の高いドメイン部品を拡充していくと、アプリケーションサービスはそれらを1ブロック1ブロック独立したレゴのように組み合わせていけば機能を実現できる。結果として、機能やユースケースを追加・変更しやすくなり、システムが成長しやすくなる。前編で紹介したPoEAAのグラフで後半からドメインモデルのアドバンテージが発生しているのは、このためである。短期決戦ではアドバンテージを得ることが難しい。

DDDの恩恵を授かれるのは、苦労してリファクタリングを重ねてドメイン部品を安定させた後であり、ときとしてそれは開発終盤であったり稼動後からの場合が多い。

まとめ

2回に分けてDDDについて紹介してきた。これまで見てきたように、DDDは難解でとっつき難いかもしれない。ただ、2000年代初頭にEric Evans氏が「Domain-Driven Design」の考えを世に出して以来、15年近く経過した今なお開発者の間でDDDについて議論されて、影響を与え続けている。

ドメインにおける概念をオブジェクトとして組み立て、それらの相互作用でアプリケーションを構成していくという、オブジェクト指向の定石と言える設計手法に対して、デザインパターンをきっちりと体系化している。加えて、デザインパターンの粒度としても「MVC」ほどマクロ(粗め)なパターンでもなく、「GoF」ほどミクロ(局所的)なパターンでもなく、その中間で程よい懐の深さでアプリケーション全体を支えている。オブジェクト指向設計に取り組むエンジニア達が昔から思い描いていたが、具体的なデザインパターンとして落とし込めていなかった像をDDDは体現してくれている。

[田上 悠樹]