背景
ドメイン駆動設計(DDD)はビジネスドメインの概念をソフトウェアという人工物にマッピングすることです。以下の記述や記事の主題ほとんどがEric Evans氏の書籍"Domain Driven Design"に基づいたもので、概念や設計の視点からドメインモデリングと設計の側面を扱っています。これらの記述はEntity(エンティティ)やValue Object(バリュー・オブジェクト)、Service(サービス)といったDDDの中心的要素について論じているかあるいはユビキタス言語(サイト)やコンテキスト境界、腐敗防止レイヤといった概念に言及しています。
この記事の目的はドメインモデルの採用と実装という現実的な視点からドメインモデリングと設計をカバーすることにあります。指針、ベストプラクティス、フレームワークや技術指導者やアーキテクトが実装に利用できるツールについて見ていきます。ドメイン駆動設計・開発は下記のようなアーキテクチャや設計、実装側面の影響を受けています。
- ビジネスルール
- 永続化
- キャッシング
- トランザクション管理
- セキュリティ
- コード生成
- テスト駆動開発
- リファクタリング
この記事ではこれらの異なる要素が実装プロジェクトのライフサイクルを通してどのようにプロジェクトに作用し、DDDによる実装の成功を現実的にするためにアーキテクトが注視すべきことについて言及しています。まず典型的なドメインモデルが備えるべき特徴から始めます。続けてエンタープライズ分野でどのような時にドメインモデルを利用すればいいのか(ドメインモデルをまったく利用しない場合やドメイン・モデル貧血症の場合と対比しながら)を示します。
記事にはここで話題にする設計の側面や開発のベストプラクティスがどのように実際のドメイン駆動開発プロジェクトで使えるのかを証明するサンプルとしてローン処理アプリケーションを含んでいます。サンプルアプリケーションはローン処理のドメインモデルを実装するのにSpring, Dozer, Spring Security, JAXB, Arid POJOs,やSpring Dynamic Modulesといったフレームワークを利用しています。例示されているコードはJavaで記述されていますが、開発言語の背景を問わずほとんどの開発者にとってすんなりと理解できるものになっているはずです。
はじめに
ドメインモデルは以下に示すような利益をもたらします。
- 企業内のビジネス側、IT側双方のステーク・ホルダーに対して共通のモデルを作成するのを助けます。そのモデルでビジネス要求、データ・エンティティ、処理モデルについて話し合うことができます。
- モデルはモジュール化されており、拡張性が高く、ビジネス・モデルを反映しているので保守が容易です。
- ビジネス・ドメインにあるオブジェクトの再利用性やテスト可能性を改善します。
反対に中~大規模のエンタープライズ・アプリケーションを開発する際にITチームがドメイン・モデルを使った手法を採用しなかった場合にどうなるか見てみましょう。
ドメイン・モデルと開発に注力しないと"太ったサービス・レイヤ"と"ドメイン・モデル貧血症"によるアプリケーション・アーキテクチャになってしまいます。この場合、ファサード・クラス(通常はステートレス・セッション・ビーン)にどんどんビジネス・ロジックが溜まっていき、ドメイン・オブジェクトがgetter/setterからなる単なるデータの運び屋のようになってしまいます。このアプローチをとるとドメイン固有のビジネス・ロジックやルールが複数の異なるファサード・クラスに散在(時には重複)することになります。
"ドメイン・モデル貧血症"はたいていの場合、コストに見合いません。他の企業と比較して利点があるわけではなく、このアーキテクチャの下でビジネス要求の変化を実装するには開発と本番環境へのデプロイするのに時間がかかり過ぎます。
DDD実装プロジェクトにおけるいろいろなアーキテクチャや設計について見ていく前に、まずはリッチ・ドメイン・モデルの特徴を見てみましょう。
- ドメイン・モデルは特定のビジネス領域に焦点を合わせていなければなりません。(ドメイン・モデルは)ビジネス・モデル、戦略やビジネス・プロセスと並んでいなければならない。
- ドメイン・モデルはアプリケーション・アーキテクチャの面で他のレイヤから切り離されてなければならないように、ビジネスの面でも他のドメインから切り離されていなければならない。
- ドメイン・モデルはモデルの重複やビジネス領域の中心となる要素が重複して実装されるのを避けるために再利用性が高くなければならない。
- ドメイン・モデルはアプリケーション内の他のレイヤと疎結合となるように設計されていなければならない。これは両側のレイヤ(例えばデータベース・レイヤとファサード・レイヤ)に依存してはならないということを意味する。
- ドメイン・モデルは抽象化され完全に分離されたレイヤにあって保守、テスト、バージョン管理が容易でなければならない。ドメイン・クラスはコンテナ外部で(そしてIDEの内部から)ユニット・テストが実行できなければならない。
- ドメイン・モデルはどんなフレーム・ワークにも依存せず、POJOプログラミング・モデルで設計されていなければならない(私は社内で一緒に働くプロジェクト・チームによく、私達がソフトウェア開発に使っているのはJavaであると伝えている)。
- (技術要素はドメイン・モデルにある程度の制約を与えるものの)ドメイン・モデルは永続化に関する実装の詳細からは完全に独立してなければならない。
- いかなる基盤系のフレーム・ワークに対しても最小限の依存関係しか持たないようにしなければならない。なぜなら、そうすることでフレーム・ワークの寿命は長くなるからであり、いかなる外部フレーム・ワークとの密結合も望ましくない。
ソフトウェア開発における投資回収率(ROI)を向上を達成するため、業務部門とITの上級管理職はドメイン・モデリングやその実装における(時、金そしてリソースといった)投資をしなければなりません。それではドメイン・モデルの実装に不可欠となるその他の要素について見ていきましょう。
- チームのメンバがいつでもビジネス領域の専門家と接触をとれる状況でなくてはならない。
- ITチーム(モデラー、アーキテクトそして開発者)は高度なモデリング、設計技術を保有していなければならない。
- アナリストは優れたビジネス・プロセスのモデリング技術を保有していなければならない。
- アーキテクトや開発者はオブジェクト指向設計(OOD)とオブジェクト指向プログラミング(OOP)の十分な経験を保有していなければならない。
エンタープライズ・アーキテクチャにおけるドメイン駆動設計の役割
ドメイン・モデリングとDDDはエンタープライズ・アーキテクチャ(EA)において極めて重要な役割を担います。EAのゴールの一つはITをビジネスと連携させることにあるので、ビジネスの実体を表現するドメイン・モデルはEAの中核となるのです。従ってたいていの(ビジネスまたはインフラの)EAコンポーネントはドメイン・モデルに基づいて設計・実装されなければならないのです。
ドメイン駆動設計とSOA
ビジネス・プロセスに基づいたソフトウェア・コンポーネントやサービスの開発と製品を市場に送り出す速度を早めるため、サービス指向アーキテクチャ(SOA)は近年どんどん勢いづいてきています。ドメイン駆動設計は業務ロジックとルールをドメイン・オブジェクト内にカプセル化するのでSOAアーキテクチャのキー要素となります。ドメイン・モデルはサービスの契約を定義する言語と状況も提供します。
もしまだSOAの取り組みに含めていないのなら、ぜひドメイン・モデルの設計と実装を含めるべきです。SOAサービスにばかり力を注いでドメイン・モデルの重大さを無視すると、アプリケーション・アーキテクチャはドメイン・モデル貧血症と太り過ぎたサービスになってしまいます。
ドメイン・モデルを使う側であるアプリケーション・レイヤとSOAの開発がDDDの取り組みと一緒に反復的に行われるのが理想的な筋書きです。リッチなドメインの実装によりSOAの設計はドメイン・オブジェクトへの殻(プロキシ)を提供すれば比較的簡単なものになるでしょう。しかし背後に適切なドメイン・モデルがないままであまりにSOAレイヤに注目してしまうと、ビジネス・サービスは不完全なドメイン・モデルを呼び出すことになり、もろく不安定なSOAアーキテクチャを生み出すことになります。
プロジェクト・マネジメント
ドメイン・モデリングのプロジェクトは一般的に以下の手順を踏むことになります。
- まず、ビジネス・プロセスに関するモデルとドキュメントを作成する。
- 候補となるビジネス・プロセスを選択してビジネス領域の専門家と一緒にユビキタスな言語を使ってそれをドキュメント化する。
- 候補としたビジネス・プロセスを実現するのに必要な全てのサービスを洗い出す。これらのサービスはアトミック(1つの手順のみ)であっても統制された(複数の手順と場合によってはワークフローからなる)ものでまったく構いません。さらに(例えば、ローンの引受や融資のように)ビジネスであっても(例えば、eメールやジョブ・スケジューリングのような)インフラであってもよい。
- これまでに洗い出されたサービスが利用することになるオブジェクトの振舞いと状態を洗い出してドキュメント化する。
最初からモデルをビジネス領域の中核要素に対して高度に集中させることが大切です。
プロジェクト・マネジメントの観点からすると実際のDDDによる実装のプロジェクトは他のソフトウェア開発プロジェクトと同じフェーズから構成されることになります。これらのフェーズは以下のものからなります。
- (ビジネス)領域のモデル化
- 設計
- 開発
- ユニット及びインテグレーション・テスト
- 設計と開発の結果を元にドメイン・モデルを洗練及びリファクタリングする(概念モデルの継続的インテグレーション(CI))
- 修正されたドメイン・モデルを使って上記を繰り返す(ドメインの実装に対するCI)。
DDDがソフトウェア・システムとビジネス・モデルを連動させようとしているようにアジャイル・ソフトウェア開発の方法論はビジネス価値を送り出すことに焦点を合わせており、このような場合はアジャイル開発が適しています。加えて、DDDの反復的な性質からするとSCRUMやDSDMといったアジャイルの方法論がプロジェクト・マネジネメントのよいフレームワークとなるでしょう。(プロジェクト・マネジメントに)SCRUMと(ソフトウェア開発の目的に)XPという方法論使うというのはDDDによる実装プロジェクトにはとてもよい組み合わせでしょう。
このDDDイテレーション・サイクルのプロジェクト・マネジメントのモデルを図1に示します。
図1.DDDイテレーション・サイクルの図(大きな図を見るには上図をクリックして下さい。)
ドメイン駆動設計への取り組みはドメイン・モデリングが完了したところから始まります。Ramnivas Laddad氏は以下の手順でドメイン・オブジェクト・モデルの実装をすることを勧めています(サイト)。Ramnivas氏はドメイン・モデルの中のサービスよりもドメイン・オブジェクトの方に注力することを強調しています。
- ドメイン・エンティティとドメイン・ロジックから始める。
- まずはサービス・レイヤ抜きで始めて、ロジックがいずれのドメイン・エンティティやバリュー・オブジェクトにも属さない場合にのみサービスを追加する。
- ユビキタス言語、契約による設計(サイト)(DbC)、テストの自動化、CIそしてリファクタリングを採用して実装が極力ドメイン・モデルに近いものになるように努める。
設計と実装の観点からすると、典型的なDDDフレームワークは以下のような機能をサポートすることが求められる。
- POJO(あるいは.NETを採用している企業ならPOCO)ベースのフレームワークであること。
- DDDの考え方に基づいたビジネス・ドメイン・モデルの設計と実装をサポートしていること。
- 依存性の注入(DI)やアスペクト指向プログラミング(AOP)といった概念を最初からサポートしていること。(注意:これらの概念の詳細については後述します)
- JUnit、TestNG、Unitilsといったテスト・フレームワークと統合されていること。
- JPA、Hibernate、TopLinkといったその他のJava/Java EEフレームワークとのよく統合出来ること。
サンプル・アプリケーション
この記事で使うサンプル・アプリケーションは住宅ローン処理システムで住宅ローンの申込を承認するビジネス・ユース・ケースを扱います。ローンの申請が住宅ローン融資会社に送信されると、引受プロセスに送られそこで顧客の収入状況やクレジット・カードの使用状況、その他の要素を考慮して引受業者(アンダーライター)が申請を承認または拒否します。ローンの申請が承認されると、ローン承認プロセスの完了手続きと融資のステップに進みます。
ローン処理システムの融資モジュールは借主への融資資金の支払い処理を自動化します。融資プロセスは一般的に融資会社(通常は銀行)が主幹企業にローン・パッケージを転送することから始まります。その後、主幹企業がローン・パッケージを検査し貸主、借主と一緒にローンの返済完了までの適切な日程計画を立てます。借主と貸主は主幹企業で保証会社と会って不動産の所有権を移譲する書類にサインします。
アーキテクチャ
エンタープライズ・アプリケーションの典型的なアーキテクチャは以下の4つの概念的なレイヤから構成されます。
- ユーザ・インタフェース(プレゼンテーション・レイヤ):ユーザへ情報を提供し、ユーザからの指示を解釈する責務を負う。
- アプリケーション・レイヤ:このレイヤがアプリケーションの挙動を調整する。一切のビジネス・ロジックを含まない。ビジネス・オブジェクトの状態を持たないが、アプリケーションの処理の進捗状態を保持することはある。
- ドメイン・レイヤ:このレイヤにビジネス領域の情報が含まれる。ビジネス・オブジェクトの状態はここに保持される。ビジネス・オブジェクトと場合によってはその状態の永続化についてはインフラストラクチャ・レイヤに委譲することがある。
- インフラストラクチャ・レイヤ:このレイヤは他の全てのレイヤを補完するライブラリとして振舞う。レイヤ間のコミュニケーション方法を提供し、ビジネス・オブジェクトの永続化を実装し、ユーザ・インタフェース・レイヤを補完するライブラリを含む、など。
では、アプリケーション・レイヤとドメイン・レイヤについてもう少し詳細を見てみましょう。まずアプリケーション・レイヤについて見ます。
- アプリケーションに含まれるUI画面間の遷移に関する責務を負うと同時に他のシステムのアプリケーション・レイヤとの連携に関する責務を負う。
- アプリケーション内の他の(より低位の)レイヤに渡す前に、ユーザが入力したデータに対する基本的な(業務内容に依存しない)検証を行うことがある。
- 業務内容やドメインに依存したロジック及びデータ・アクセス・ロジックは一切含まない。
- ビジネス・ユース・ケースを反映する状態は一切保持しないが、ユーザ・セッションの状態や処理の進捗状態については管理することがある。
ドメイン・レイヤ
- ビジネス領域の概念、ビジネス・ユース・ケースに関する情報、そしてビジネス・ルールに関する責務を負う。ドメイン・オブジェクトはビジネス・エンティティの状態と振舞いをカプセル化する。ローン処理アプリケーションにおけるビジネス・エンティティの例はMortgage(融資会社)、Property(不動産)、そしてBorrower(借主)である。
- ユース・ケースがユーザからの複数のリクエストにまたがる場合(例えば、複数の手順を含むローンの登録処理:ユーザがローンの詳細を入力し、入力されたローンの情報を元にシステムが商品と利率の組み合わせを返し、ユーザが任意の商品と利率を選択し、最終的にシステムがその利率でローンを確定する)にはビジネス・ユース・ケースの状態(セッション)を管理することがある。
- どのドメイン・オブジェクトにも含まれない定義された振舞いのみを保持するサービス・オブジェクトを含む。サービスはドメイン・オブジェクト自身が管理するには相応しくないビジネス・ドメインの振舞いをカプセル化する。
- ビジネス・アプリケーションの心臓部であり、アプリケーションの他のレイヤからは独立していなければならない。さらに他のレイヤで使われるアプリケーション・フレームワーク(JSP/JSF、Struts、EJB、Hibernate、XMLBeansなど)に依存していてもいけない。
下の図2ではアプリケーションで使われるそれぞれのアーキテクチャ上のレイヤとそれらがDDDとどのように関連するのかが示されています。
図2.レイヤ化されたアプリケーション・アーキテクチャ(大きな図を見るには上図をクリックして下さい。)
現在以下のような側面が、DDDによる実装の秘訣として重要な要素であると考えられています。
OOPはドメインの実装において最重要な要素となります。ドメイン・オブジェクトは継承、カプセル化そしてポリモルフィズムといったOOPの理念を使った普通のJavaクラスとインタフェースを用いて設計されていなければなりません。ドメインも含まれるほとんどの要素は状態(属性)と振舞い(状態に作用するメソッドや操作)を持った真のオブジェクトになります。さらにそれらは現実世界の概念と調和し、OOPの理念と符合し得るものです。DDDにおけるEntity(エンティティ)やValue Object(バリュー・オブジェクト)は状態と振舞いとを持つという意味でOOP理念の古典的な例でしょう。
典型的なUnit of Work(UOW:処理単位)において、ドメイン・オブジェクトはService(サービス)、Repository(リポジトリ)、Factory(ファクトリ)といった他のオブジェクトと協調する必要があります。ドメイン・オブジェクトはドメイン状態の変更に対するトラッキング、監査、キャッシュ、トランザクション管理(トランザクションの再試行を含む)といった本質的に横断的な関心事も管理しなければなりません。これらは再利用可能でドメインに依存しない関心事であり一般的にドメイン・レイヤを含むあちこちのコード内に散在し、重複する傾向があります。これらのロジックをドメイン・オブジェクトに埋め込むとドメイン・レイヤがドメインに依存しないコードによって掻き乱され、からみ合うことになります。
オブジェクト間の密結合なくコードの依存関係を管理したり、横断的な関心事を分離するということになるとOOPだけでドメイン駆動設計・開発に洗練された解決策をもたらすことは出来ません。このような場合にこそDIやAOPといった設計概念がOOPを補完することにより、密結合を最小化し、モジュール化を促進し、横断的な関心事を管理するのです。
依存性の注入
DIはドメイン・オブジェクトから設定や依存関係に関するコードを排除する素晴らしい方法です。さらに、ドメイン・クラスがData Access Object(DAO:データ・アクセス・オブジェクト)クラスに依存し、サービス・クラスがドメイン・クラスに依存するという設計上の依存関係がDDDによる実装に際してDIを"なくてはならない"ものにしています。DIはRepositoryやServiceといった異なるオブジェクトをDomain Object(ドメイン・オブジェクト)に注入することで、DIは設計を容易に美しくし、疎結合にします。
サンプル・アプリケーションでは、サービス・オブジェクト(FundingServiceImpl)がEntityオブジェクト(Loan:ローン, BorrowerそしてFundingRequest:融資の要求)を注入するためにDIを使っています。さらに、EntityはDIを通してRepositoryを参照しています。同様DataSource、Hibernate Session Factory(Hibernateのセッション生成を管理するクラス)、そして(トランザクションを管理する)Transaction Managerといったその他のJava EEリソースもServiceオブジェクト、Repositoryオブジェクトに注入されています。
アスペクト指向プログラミング
AOPは監査、ドメイン状態の変更に対するトラッキングといった横断的な関心事をドメイン・オブジェクトから取り除くことでよりよい設計(ドメイン・モデルの分断を減少したり)をするのに役立つ。AOPはとりわけコンテナ管理されていない(永続化されているオブジェクトのような)ドメイン・オブジェクトに対して協調するオブジェクトやServiceを注入することが出来る。ドメイン・レイヤに含まれるその他の要素でAOPが役立つと思われるのは、キャッシュ、トランザクション管理、そしてロール・ベースのセキュリティ(認可)などである。
ローン処理アプリケーションではサービス・オブジェクトにデータ・キャッシュ機構を持たせるために独自のアスペクトを使っています。ローン商品と利率情報は(クライアントから初めてそのデータが要求された際に)データベース・テーブルから一度だけロードされた後オブジェクト・キャッシュ(JBossCache)に保存され、それ以降の商品や利率への問い合わせに備えます。商品や利率のデータは頻繁にアクセスされますが、更新頻度は高くありません。従って、毎回バックエンドにあるデータベースにアクセスするのではなくキャッシュする対象として最適な候補になります。
DDDにおけるDIやAOPといった概念の役割は最近のスレッドで主要なトピックとなっていました。それらの議論は、DDDはAOPとDIの助けなしには実装できない(サイト)と主張されているRamnivas Laddad氏のプレゼンテーションを元にしています。そのプレゼンテーションの中で、Ramnivas氏はAOPを使うことでドメイン・オブジェクトに洗練された振る舞いを取り戻させる"きめ細かいDI"という概念について語っています。Ramnivas氏はドメイン・オブジェクトはリッチな振る舞いを提供するために他のきめ細かいオブジェクトにアクセスする必要があり、これを解決するにはService、Factory、Repositoryを(コンストラクタやsetterの実行時に依存性を注入するためにアスペクトを使って)ドメイン・オブジェクトに注入する必要があると説明しています。
Chris Richardson氏も結合度を削減し、モジュール化を促進することでアプリケーションの設計を向上するためにDI、オブジェクト、そしてアスペクトを使うこと(サイト)について論じています。Chris氏は高結合でバラバラで複雑に絡み合ったアプリケーション・コードが招く"肥大化したサービス"というアンチ・パターンに触れ、DIとAOPという概念を使ってこれを避ける方法にも触れています。
アノテーション
DIやアスペクトを定義したり管理したりするのにアノテーションを使うのが流行りです。アノテーションによってEJBやWebサービスといったリモート・サービスを実装するために必要となる成果物を減らすことが出来ます。さらに、設定の管理を簡単にします。Spring 2.5、Hibernate 3、さらにその他のフレームワークがアノテーションの利点をフル活用してJavaエンタープライズ・アプリケーションにおける様々なレイヤのコンポーネントの設定をしています。
柔軟性という付加価値が加わるので、決まりきったコードを生成するのにアノテーションを利用すべきです。その一方でアノテーションの利用には注意も必要です。使う際には本来のコードを理解するのを邪魔したり誤解させたりしないようにすべきです。HibernateのORMマッピングがアノテーションの良い利用例になります。この場合クラス名や属性名のすぐ隣でSQLに使うテーブル名やカラム名を特定するという付加価値を与えています。反対にJDBCドライバの設定(ドライバ名、JDBCのURL、ユーザ名やパスワード)のような細かいことについてはアノテーションを使うよりXMLファイルに保存する方が向くでしょう。これにはデータベースとドメイン・モデルが同じ様な状況で使われるという前提があります。もし、ドメイン・モデルとデータベース・テーブルの間で多くの変換処理が必要となるのだとしたら、設計の際にそのことを考慮に入れるべきです。
Java EE 5は通常のJavaクラスに永続化に必要な詳細な情報を付与するために@@Entity、@PersistenceUnit、@PersistenceContextといったJPAアノテーションを提供しています。ドメイン・モデルの中では、Entity、Repository、Serviceがアノテーションを使う候補となります。
@ConfigurableはSpringを使う際にDomain ObjectにServiceやRepositoryを注入する方法です。Springフレームワークは@Configurableをアノテーションによって"ドメイン・オブジェクトのDI"という考え方を拡張しています。Ramnivas氏は最近のブログで来るSpring 2.5.2(プロジェクトのスナップショット379から入手可能です)のリリースにおける改良点について書いています(ブログ)。ドメイン・オブジェクトのDIにシンプルさと柔軟性を提供するために三つの新しいアスペクト(AnnotationBeanConfigurerAspect、AbstractInterfaceDrivenDependecyInjectionAspect、そしてAbstractDependencyInjectionAspect)が追加されています。Ramnivas氏は真ん中のアスペクト(AbstractInterfaceDrivenDependencyInjectionAspect)を追加した主な理由はドメイン固有のアノテーションやインタフェースの利用を認めるためであると語りました。Springではさらに@Repository、@Service、そして@Transactionalといったアノテーションを提供してドメイン・クラスの設計を補助しています。
サンプル・アプリケーションでもいくつかのアノテーションが使われています。Entityオブジェクト(Loan、Borrower、そしてFundingRequest)は@Entityアノテーションを使っています。これらのオブジェクトはさらに@Configurableアノテーションを使ってRepositoryオブジェクトを関連付けられています。そしてServiceクラスは@Transactionalアノテーションを使ってサービス・メソッドにトランザクションの振る舞いを追加しています。
ドメイン・モデルとセキュリティ
ドメイン・レイヤにおけるアプリケーション・セキュリティ機能によって認証を受けたクライアント(人間や他のアプリケーション)だけがドメインの操作やドメインの状態にアクセス出来ることを保証します。
(Spring Portfolioのサブ・プロジェクトである)Spring Securityはプレゼンテーション・レイヤ(URLベース)とドメイン・レイヤ(メソッド単位)の両方に対してきめ細かいたアクセス制御機能を提供します。このフレームワークはSpringのBean Proxyを使ってメソッドの実行を横取りしてセキュリティ制約を適用します。MethodSecurityInterceptorというクラスを使ってJavaオブジェクトに対するロール・ベースの宣言的なセキュリティを提供するのです。さらにインスタンス単位のユーザ・アクセスを制御するためにドメイン・オブジェクトに対するアクセス制御リスト(ACL)という形でインスタンス単位にセキュリティをかけることも出来ます。
ドメイン・モデル内の認証という要求に対してSpring Securityを使う主な利点は、このフレームワークが侵略的でないアーキテクチャなのでドメインとセキュリティという二つの面をきれいに分離することが出来るということです。そして、ビジネス・オブジェクトがセキュリティ関連の詳細な実装によって分離されることもありません。共通的なセキュリティ・ルールを一か所にまとめて記述することが出来、それが必要な個所で(AOP技術を使って)好きなようにそのルールを適用することが出来るのです。
ドメイン・クラスとサービス・クラスに対してはクラス・メソッドに対する実行の単位で認証を管理します。例えば、Underwriting(保険業務)というドメイン・オブジェクトにある"ローンを承認する"というメソッドは、100万ドル迄のローンに対しては"Underwriter(保険業者)"というロールを持ったユーザなら誰でも実行することが出来ますが、同じドメイン・オブジェクトにある承認メソッドは100万ドルを超えるローン申請に対して、"Underwriting Supervisor(保険業管理者)"というロールを持ったユーザしか実行できません。
下の表はアプリケーション・アーキテクチャの各レイヤにおける様々なアプリケーション・セキュリティ対策についてまとめたものです。
表1. 各アプリケーション・レイヤにおけるセキュリティ対策
レイヤ | セキュリティ対策 |
---|---|
クライアント/コントローラ | 認証、Webページ(URL)単位での認可 |
ファサード | ロールベースの認可 |
ドメイン | ドメイン・インスタンス単位での認可、ACL |
データベース | DBオブジェクト単位の認可(ストアド・プロシージャ、ストアド・ファンクション、トリガ) |
ビジネス・ルール
ビジネス・ルールはビジネス・ドメインの重要な部分となります。ビジネス・ルールはデータ検証や特定の業務上のシナリオで適用すべきその他の制約などを定義します。ビジネス・ルールは一般的に下のようなカテゴリに分類されます。
- データ検証
- データ変換
- ビジネス上の決定・判断
- 処理の振り分け(ワーク・フローのロジック)
DDDの世界ではコンテキスト(文脈や状況)というのがとても重要な意味を持ちます。コンテキストがドメイン・オブジェクトの協調動作やどのビジネス・ルールを適用するのかといった実行時の要素などを決定します。検証やその他のビジネス・ルールはいつも特定のビジネス・コンテキストでのみ実行されるのです。これはつまり同じドメイン・オブジェクトでも異なるビジネス・コンテキストにあれば別々のビジネス・ルールを実行する必要があるということを意味します。例えば、Loanというドメイン・オブジェクトのいくつかの属性(loan amount:ローン金額やinterest rate:金利など)はローン承認プロセスの保険業務というステップ以降は値を変更できなくなります。しかし、それらの属性はローンが登録されたばかり、あるいは特定の金利が決定ばかりの段階では変更することが出来ます。
ドメイン固有のビジネス・ルールは全てドメイン・レイヤにカプセル化されるべきですが、いくつかのアプリケーションの設計方法ではそれらのルールをファサード・クラスに配置します。こうするとドメイン・クラスはビジネス・ルールのロジックに関して"貧血症"になってしまいます。この方法は小規模なアプリケーションでは受け入れられるものかも知れませんが、複雑なビジネス・ルールを含む中~大規模のエンタープライズ・アプリケーションには推奨されません。よりよい設計はルールを本来あるべき場所、ドメイン・オブジェクト内部、に配置することです。もしビジネス・ルールのロジックが複数のエンティティ・オブジェクトにまたがるのであれば、サービス・クラスに含めるべきです。
また、もしアプリーションに詳しくない場合、ビジネス・ルールの設計はコード内に多くのswitch文を埋め込むという形に終わることになります。そして時間が経ちルールの複雑さが増しても、開発者はコードから"switch"文を取り除きより管理しやすい設計へと変更するリファクタリングに時間を掛けようとしないのです。複雑な分岐処理や判断ルールをクラス内にハードコーディングすることは長いメソッド、コードの重複、そして究極的には長い運用段階における保守の悪夢を生み出す固定化されたアプリケーションの設計へと繋がるのです。全てのルール(とりわけ経営戦略の変更に伴って頻繁に変わる様な複雑なルール)は(JBoss Rules、OpenRulesやMandaraxのようなルール・フレームワークを使って)ルール・エンジンに配置しドメイン・クラスから実行するようにするのがよい設計です。
検証のルールは一般的にJavascript、XML、Javaのコードやその他のスクリプト言語といった様々な言語で記述されます。しかしビジネス・ルールの動的な性質を考慮するとRuby、Groovyやドメイン固有言語(DSL)といったスクリプト言語がこれらのルールを定義し管理するのに適しています。Struts(アプリケーション・レイヤ)、Spring(Service)、そしてHibernate(ORM)は全てそれぞれ独自の検証用のモジュールを持っていて入力や出力するデータ・オブジェクトに対する検証ルールを適用することが出来ます。検証ルールをアスペクト(AOPルールの記事をリンクする)として管理することで異なるレイヤ(例えばサービスとコントローラ)に渡ってそれを織り込むことが出来るような場合もあります。
ビジネス・ルールを管理するためにドメイン・クラスを書いている場合、ユニット・テストについて考慮しておくのは大切なことです。ルールに対するあらゆる変更は独立してユニット・テストが出来なければなりません。
サンプル・アプリケーションにはローンのパラメータが許容される商品と利率に合致することを検証するためのビジネス・ルールが含まれています。それらのルールはスクリプト言語(Groovy)で定義されていてFundingServiceオブジェクトに渡されるローンのデータに適用されます。
設計
設計の観点からすると、ベンダ依存の翻訳処理、データのフィルタリング、変換処理といったドメイン・レイヤの中心的な関心事以外の要素によってドメイン・レイヤが汚染されるのを避けるため、ドメイン・レイヤの境界は明確に定義しておくべきです。ドメイン要素はドメインの状態と振舞いを正確に保持するように設計するべきです。異なるドメイン要素は状態と振舞いを基に異なる構造となるでしょう。表2はドメインの各要素とそれぞれが保持する情報を示しています。
表2.ドメイン・レイヤの要素とその状態および振舞い
ドメイン・レイヤの要素 | 状態/振舞い |
---|---|
Entity, Value Object, Aggregate | 状態と振舞い |
Data Transfer Object | 状態のみ |
Service, Repository | 振舞いのみ |
Entity、Value Object、そしてAggregateは状態(データ)と振舞い(操作)の両方を持ち、状態と振舞いが明確に定義されていなければなりません。同時にその振舞いは各オブジェクトの境界を超えるものであってはなりません。Entityはほとんどの場合自身の状態に作用するだけのはずです。しかし、関係のないことについてはあまり多くの情報を知るべきではありません。
ドメイン・オブジェクトの状態をカプセル化する必要がある属性に対してのみgetter/setterを用意するというのがよい設計方法です。ドメイン・オブジェクトを設計する際には、変更可能なフィールドに対してのみsetterを提供しましょう。そして、パブリックなコンストラクタはドメイン・クラスが保有するすべてのフィールドを引数に取るのではなく値の設定が必須であるフィールドだけを引数に取るようにすべきです。
ほとんどのケースでは、直接オブジェクトの状態を変更しなければならないことはないはずです。従って、内部の状態を変更する代わりに変更された状態を持つ新しいオブジェクトを生成してそれを返すようにします。ほとんどの場合はこれで十分事足りますし設計の複雑さを削減することも出来ます。
Aggregateクラスは協調動作をするクラスの利用について呼び出し元から隠ぺいすることが出来ます。Aggregateクラスは複雑さや、割り込み、そしてドメイン・クラス内での状態に依存した要求をカプセル化するのに使えます。
DDDを補助するデザイン・パターン
ドメイン駆動設計及び開発に役立ついくつかのデザイン・パターンがあります。以下はそれらデザイン・パターンの一覧になります。
- Domain Object (DO)
- Data Transfer Object (DTO)
- DTO Assembler
- Repository: Repositoryにはドメインの中枢となるメソッドが含まれておりデータベースとの通信にDAOを利用する。
- 一般的なDAO
- 時制に絡むパターン:これらのパターンはリッチ・ドメイン・モデルに時間軸を追加します。Bitemporalフレームワーク(サイト)は、Martin Fowler氏のTemporal Pattern(サイト)を基にしたものですが、ドメイン・モデルにおいてbitemporal(現在のデータと過去の履歴データの両方を保持すること)に関する問題に対応するための設計方法を提供します。中心的なドメイン・オブジェクトとそれらのbitemporalな属性はHibernateのようなORM製品を使うことで永続化することが出来ます。
その他にもStrategy、Facade、そしてFactoryといったデザイン・パターンがDDDでは使われます。Jimmy Nilsson氏はFactoryはドメインで使用するパターンであると自身の本(サイト)の中で言及しています。
DDDアンチ・パターン
ベスト・プラクティスとデザイン・パターンの対極にドメイン・モデルを実装する際にアーキテクトや開発者が注意をしていなければならないDDDの臭いというのがあります。これらのアンチ・パターンはドメイン・レイヤをアプリケーション内の最も重要でない部分へとおとしめたりFacadeクラスが重要な役割を担うような結果を招きます。以下はそのようなアンチ・パターンの例です。
- 貧血症のドメイン・オブジェクト
- 重複の多いDAO
- 肥満化したサービス・レイヤ。全てのビジネス・ロジックを取り込んだサービス・クラスのなれの果て。
- 機能への羨望:これはMartin Fowler氏のRefactoringという本(サイト)で指摘された古典的な臭いの一つであるクラスのメソッドが他のクラスのデータに強く依存し過ぎている状態を指します。
データ・アクセス・オブジェクト
DAOやRepositoryもドメイン駆動設計の重要な要素です。DAOはリレーショナル・データベースとアプリケーションの間の契約です。データベースのCRUD操作をWebアプリケーションから隠蔽します。一方で、RepositoryはDAOと協調しドメイン・モデルに対する"ビジネス・インタフェース"を提供する抽象概念です。
Repositoryはドメインのユビキタスな言語を提供します。必要となる全てのDAOと協調しドメイン・モデルが理解できる形でデータ・アクセス・サービスを提供します。
DAOのメソッドは粒度が細かくよりデータベースに近い形である一方、Repositoryのメソッドはより粗い粒度でよりドメインに近い形をとります。さらに一つのRepositoryクラスに複数のDAOが注入されることもあります。RepositoryとDAOによってドメイン・モデルはデータ・アクセスや永続化に関する詳細と疎結合になります。
ドメイン・オブジェクトはRepositoryのインタフェースにのみ依存すべきです。DAOではなくRepositoryを注入した方がドメイン・モデルが美しくなるのはこのためです。DAOクラスは決してクライアント(Serviceクラスそしてその他のクラス)から直接呼び出されるべきではありません。クライアントは常にドメイン・オブジェクトを呼び出すようにして、ドメイン・オブジェクトがデータを永続化するためにDAOを呼び出すべきです。
ドメイン・オブジェクト間(例えば、EntityとRepositoryの間)の依存関係を管理することは開発者がしばしば陥る古典的な問題です。この問題を解決する一般的な設計方法はServiceやFacadeクラスが直接Repositoryクラスを呼び出し、RepositoryクラスがEntityオブジェクトをクライアントに返すようにするものです。ところがこの設計は結果的に上述したようにFacadeクラスが次第にビジネス・ロジックを蓄積し始めドメイン・オブジェクトが単なるデータ運搬オブジェクトと化すドメイン・モデル貧血症へと繋がります。DIやAOPの技術を使ってRepositoryとServiceをドメイン・オブジェクトに注入するのがよい設計です。
サンプル・アプリケーションではローン処理のドメイン・モデルを実装する際にこれらの設計原理に従っています。
永続化
永続化はドメイン・レイヤ疎結合でなくてはならない基盤的な側面になります。JPAはクラスから永続化に関する実装の詳細を隠蔽することでこの概念を提供しています。JPAはアノテーション駆動なのでXMLによるマッピング・ファイルは一切必要ありません。しかし、一方でテーブル名とカラム名がコード内に埋め込まれるので場合によっては柔軟性に欠ける方法となるかもしれません。
Oracle Coherence、WebSphere Object GridやGigaSpacesといったデータ・グリッド・ソリューションを提供するグリッド・コンピューティング製品を使うと、開発者はビジネス・ドメインのモデリングや設計の際にRDBMSのことを意識する必要すらなくなります。データベース・レイヤはドメイン・レイヤに対してイン・メモリのObject/Dataグリッドという形で抽象化されます。
キャッシュ
ドメイン・レイヤの状態(データ)について語るとき、キャッシュの存在を忘れるわけにはいきません。(住宅ローン・アプリケーションにおけるproduct(商品)やrate(利率)のように)頻繁にアクセスされるドメイン・データはキャッシュ対象の候補となります。キャッシュによって性能は向上しデータベース・サーバにおけるロード処理を削減します。サービス・レイヤはドメインの状態をキャッシュする格好の場所となります。TopLinkやHibernateのようなORMフレームワークもデータ・キャッシュの機能を提供します。
ローン処理のサンプル・アプリケーションはproductとrateの詳細な情報をキャッシュするのにJBossCacheフレームワークを使ってデータベース呼び出しを最小化しアプリケーションの性能を向上させています。
トランザクション管理
データの完全性を保ち、UOW全体をコミットまたはロールバックするためにトランザクション管理は重要になります。アプリケーション・アーキテクチャ・レイヤのどの部分でトランザクションを管理するべきなのかということについては常に議論になります。エンティティをまたぐトランザクション(一つのUOW内で複数のドメイン・オブジェクトに渡るトランザクション)もまたトランザクションをどこで管理するべきなのかという設計上の判断に影響します。
一部の開発者はDAOクラスでトランザクションを管理することを好みますがこれは貧弱な設計です。そうしてしまうとトランザクションを制御する単位があまりに細かくなり過ぎてしまい複数のドメイン・オブジェクトにまたがるトランザクションを管理したい場合に対応できなくなります。Serviceクラスこそがトランザクションを制御するべきです。ほとんどのユースケースではServiceクラスがフローを制御するので、たとえトランザクションが複数のドメイン・オブジェクトにまたがったとしても、トランザクションを管理することも出来るのです。
サンプル・アプリケーションのFundingServiceImplクラスは融資の申し込みに関するトランザクションを管理し、Repositoryを呼び出すことで複数のデータベース操作を実行した後データベースに対する全ての変更を一つのトランザクションとしてコミットまたはロールバックします。
Data Transfer Object(DTO:データ転送オブジェクト)
SOA環境ではドメイン・オブジェクト・モデルはビジネス・サービスとの間で送受信されるメッセージと構造的な互換性がないので、DTOもまたSOA環境における設計の重要な部分です。メッセージは通常XMLスキーマ(XSD)の形式で定義され管理されていて、XSDからDTOオブジェクトを作成する(あるいは自動生成する)しそれをドメインとSOAのサービス・レイヤ間のデータ(メッセージ)転送の目的に使うのが一般的なやり方です。一つ以上のドメイン・オブジェクトのデータを一つのDTOとマッピングするのは分散アプリケーションの場合には必要悪です。なぜならばドメイン・オブジェクトをネットワーク転送することは性能とセキュリティの観点から実用的ではないからです。
DDDの観点からすると、DO(ドメイン・オブジェクト)はドメイン・レイヤとサービス・レイヤで使用されるのに対してDTOはプレゼンテーション・レイヤで使用されるという点でDTOはServiceとUIのレイヤの分離を管理するのにも役立ちます。
Dozerフレームワークは一つ以上のドメイン・オブジェクトを一つのDTOオブジェクトにまとめるのに使われます。このフレームワークは双方向に使えるのでドメイン・オブジェクトをDTOに変換したりする際の変換コードの量やその作成時間を抑えることが出来ます。DOとDTO両方のオブジェクトを双方向にマッピングすることでDO → DTOとDTO → DOの変換ロジックを分離することが避けられます。さらにこのフレームワークは型変換や配列の変換も適切に扱うことが出来ます。
サンプル・アプリケーションでは融資処理への要求があった際にFundingRequestDTOオブジェクトをLoan、Borrower、そしてFunding Request Entityに分離するためにDozerのマッピング・ファイル(XML)を使っています。このマッピングによってさらにクライアントへの応答処理の際に融資処理による戻りデータを多くのEntityから一つのDTOオブジェクトへとまとめることも引き受けます。
DDD Implementation Frameworks DDD実装のフレームワーク
SpringやReal Object Oriented(ROO)、Hibernate、そしてDozerのようなフレームワークはドメイン・モデルの設計や実装を支援してくれます。その他にもJMatter、Naked Objects、Ruby On Rails、Grails、そしてSpring Modules XT FrameworkといったフレームワークがDDD実装を支援してくれます。
SpringはService、Factory、Repositoryといったドメイン・クラスの初期化や関連付けを担当します。さらに@Configurableアノテーションを使ってServiceクラスをEntityに注入します。このアノテーションはSpring独自のもので、同様の依存性注入処理を行うのにHibernate Interceptorを使うことも出来ます。
ROOは"ドメイン・ファースト、インフラ・セカンド(最初にドメイン、その後インフラ)"という哲学に基づくDDD実装のフレームワークです。このフレームワークはWebアプリケーションでよく見られる、コードが重複するパターンを削減する目的で開発されました。ROOを使う際には、まずドメイン・モデルを定義するとフレームワークが(Maven Archetypesをベースにして)モデル・ビュー・コントローラ(MVC)、DTO、ビジネス・レイヤ・ファサード、DAOレイヤのコードを自動生成します。さらにはユニット・テスト、インテグレーション・テストのためのスタブも生成します。
ROOにはいくつかの実践的で役立つ実装パターンが含まれています。例えば、ROOは状態を管理するフィールドを区別し、永続化レイヤはフィールド・レベルのアクセス修飾子を使い、パブリックなコンストラクタは必須となるフィールドだけを反映します。
開発
実装のないモデルでは不十分です。実装フェーズでは可能な限り開発の自動化を行うべきです。どのようなタスクが自動化できるかを確認するため、ドメイン・モデルを使って典型的なケースを見てみましょう。以下がこのユースケースに含まれるステップの一覧です。
リクエスト
- クライアントが(XSDに準拠した)XMLドキュメントを送ってFacadeクラスを呼び出す。FacadeクラスはUOWのためのトランザクションを初期化する。
- 入力されたデータに対する検証を行う。ここで行う検証には初期の検証(基本的な/データ型/フィールド単位のチェック)とビジネス検証が含まれる。一つでも検証エラーがあれば適切な例外を生成する。
- リクエストの内容を(ドメインが分り易いように)コードとして翻訳する。
- データ形式をドメイン・モデルが扱いやすいように変換する。
- 属性を適宜分離する(例えばCustomer Entityオブジェクトに含まれる顧客名を上の名前と下の名前に分ける)。
- DTOデータを一つ以上のドメイン・オブジェクトに分解する。
- ドメイン・オブジェクトの状態を永続化する。
レスポンス
- データストアからドメイン・オブジェクトの状態を取得する。
- 必要であれば状態をキャッシュしておく。
- ドメイン・オブジェクトをアプリケーションが扱いやすいデータ・オブジェクト(DTO)にまとめる。
- 必要なデータ要素の結合と分割を行う(例えば上の名前と下の名前をつなげて一つの顧客名という属性にする)。
- コードの内容をレスポンスの詳細に翻訳する。
- クライアントが使う形式に合わせてデータ形式を変換する。
- 必要であればDTOの状態をキャッシュしておく。
- 制御フローが終わるのに合わせてトランザクションをコミット(もしエラーがあればロールバック)する。
以下の表はアプリケーション内であるレイヤから別のレイヤにデータを運ぶオブジェクトを示しています。
表3.アプリケーションの各レイヤにおけるデータ・フロー
レイヤ | 元オブジェクト | 宛先オブジェクト | フレームワーク |
---|---|---|---|
DAO | データベースのテーブル | DO | Hibernate |
ドメイン Delegate | DO | DTO | Dozer |
データ転送 | DTO | XML | JAXB |
見て分かるようにアプリケーション・アーキテクチャの中で同じデータが異なる形式(DO、DTO、XMLなど)で通過するレイヤはほとんどありません。DAO、DAOImplそしてDAOTestと同じようにデータを保持しているこのようなオブジェクト(JavaかXML)のほとんどは元々インフラ的な要素があるものなのです。重複の多いコードと構造を持つこれらのクラスとXMLファイルはコードの自動生成の対象としてはいい候補でしょう。
コードの自動生成
ROOのようなフレームワークは新しいプロジェクトのために(Mavenプラグインを使って)標準的で一貫性のあるプロジェクトのテンプレートも作成します。生成されたプロジェクト・テンプレートを使うことでどこにソースやテスト・クラス、そして設定ファイルを格納すればいいのかといったディレクトリ構造や内部、外部(サード・パーティ製)のコンポーネント・ライブラリとの依存関係などを一貫した形式で得ることが出来るのです。
典型的なエンタープライズ・ソフトウェア・アプリケーションの開発のためには無数のクラスや設定ファイルを作成しなければならないということには抗えません。この問題に対処するにはコードの自動生成が最良の方法になります。コード生成ツールは一般的にある種のテンプレート・フレームワークを使ってコードを生成するためのテンプレートやマッピングを定義します。Eclipse Modeling Framework(EMF)にはWebアプリケーション・プロジェクトで必要とされる多くの成果物の自動生成を目的とするいくつかのサブ・プロジェクトがあります。AndroMDAのようなModel駆動アーキテクチャ(MDA)ツールはEMFを使ってアーキテクチャ・モデルに基づいたコードを生成しています。
ドメイン・レイヤにおけるデリゲート・クラスの作成については開発者が手動でクラスを書いているのを見かけます(たいていの場合、最初のクラスを一から作成した後は"コピー&ペースト"パターンを使って他のドメイン・オブジェクト用のデリゲート・クラスを作成しています)。このようなクラスはほとんどの場合、単にドメイン・クラスへのファサードでしかないので自動生成するよい候補となります。コードの自動生成という選択肢はコード生成エンジンを作成しテストするという面で(コードの量とそれに掛かる時間という)ある程度の初期投資が必要ですが、長い目で見るとよい解決策となるでしょう。
テスト・クラスの生成については、ユニット・テストの対象となるメインのクラスに含まれる複雑なビジネス・ロジックを伴うメソッドに対して抽象メソッドを用意するのがよい方法です。こうすれば、開発者は生成されたテスト・クラスのベース・クラスを拡張することで自動生成不可能な独自のビジネス・ロジックを実装することが出来るようになります。自動生成出来ないテスト・ロジックを持つテスト・メソッドに対しても同じことが言えます。
オーバーヘッドが少ないこととテンプレート生成そしてカスマイズ出来るという特性からスクリプト言語はコードの生成部を作る方法としてよい選択肢となります。DDDプロジェクトでコードの自動生成の恩恵を受けることにすると、一から作らなければならないクラスは少なくなります。一から作らなければならない成果物は以下のようなものです。
- XSD
- Domain Object
- Service
いったんXSDとJavaのクラスを定義してしまえば、以下のようなクラスや設定ファイルをすべてもしくはそのほとんどを自動生成することが出来ます。
- DAOのインタフェースと実装クラス
- Factoryクラス
- Repositoryクラス
- (必要に応じて)ドメインのDelegate(デリゲート)クラス
- (EJBやWebサービス用のクラスを含む)Facadeクラス
- DTOクラス
- 上記のクラスに対するユニット・テスト(テスト・クラスとテスト・データを含む)
- Springの設定ファイル
下に示す表4はWebアプリケーション・アーキテクチャの異なるレイヤとそれぞれのレイヤでどのような成果物(JavaのクラスやXMLファイル)が自動生成可能であるかを示しています。
表4:DDD実装プロジェクトにおけるコードの自動生成
レイヤ/機能 | パターン | 手動作成するコード | 自動生成するコード | フレームワーク |
---|---|---|---|---|
データ・アクセス | DAO/リポジトリ | DAOのインタフェース, DAOの実装クラス, DAOのテスト, テスト用のデータ |
Unitils, DBUnit |
|
ドメイン | ドメイン・オブジェクト | ドメイン・クラス | ドメインのテスト | |
永続化 | ORM | ドメイン・クラス | ORMマッピング, ORMマッピングのテスト |
Hibernate, ORMUnit |
データ転送 | DTO | XSD | DTO | JAXB |
DTOの集約 | アセンブラ | マッピング | DO-DTOのマッピング | Dozer |
デリゲート | ビジネス・デリゲート | DOからDTOへの変換処理 | ||
ファサード | ファサード | Remote Service, EJB, Web Service |
||
コントローラ | MVC | コントローラのマッピング・ファイル | Struts/Spring MVC | |
プレゼンテーション | MVC | ビューの設定ファイル | Spring MVC |
デリゲート・レイヤがドメイン・オブジェクトとDTOの両方を知る唯一のレイヤになります。永続化レイヤのような他のレイヤはDTOについて知るべきではありません。
リファクタリング
リファクタリングとはアプリケーションの機能や振舞いを変更せずにアプリケーションのコードを修正したり構造を変更することです。リファクタリングは設計かコードに対して行うものです。設計のリファクタリングは継続的にモデルを精査するために行われコードのリファクタリングによってドメイン・モデルを改善します。
ドメイン・モデルの反復的で発展的な性質からリファクタリングはDDDプロジェクトで重要な役割を担います。プロジェクトにリファクタリングのタスクを統合する一つの方法は各イテレーションの完了宣言の前にそのタスクを追加することです。理想を言えばリファクタリングは各開発タスクの前後で行われるべきです。
リファクタリングは正確を期して行われるべきです。リファクタリング、CI、ユニット・テストを併用し、コードの修正が既存の機能を破壊しないことやコードの修正によってコード自体やパフォーマンスが改善されたことを確認します。
自動テストはアプリケーション・コードの自動化に重要な役割を担います。開発段階でのテストがほどよく自動化されていなかったり、テスト駆動開発(TDD)(サイト)の試みがなされていないと、リファクタリングはむしろ非生産的にすらなり得ます。なぜならば、リファクタリングの一環で行う設計やコードの変更が既存の振舞いを変更していないこと、既存の機能を破壊していないことを自動で確認する方法がないということになるからです。
Eclipseのようなツールでは、開発作業の一環として繰り返しリファクタリングをすることでドメイン・モデルの実装を助けます。Eclipseにはメソッドの抽出や別のクラスへのメソッドの移動、サブクラスへのメソッドのプッシュ・ダウンといった機能があります。さらにEclipse向けに様々なコード解析用のプラグインがあり、コードの依存性を管理したりDDDアンチ・パターンを発見するのに役立ちます。私はプロジェクトで設計やコードレビューを担当する際にはJDepend、Classycle、やMetricsといったプラグインを使ってドメインやアプリケーション内のその他のモジュールの品質を評価しています。
Chris Richardson氏はEclipseの提供するリファクタリング機能(サイト)を使って手続き的な設計をOO的な設計へリファクタリングすることについて記しています。
ユニット・テスト/継続的インテグレーション
これまで(初期開発時と同様に既存のコードのリファクタリング時にも)ドメイン・クラスはコンテナやその他の基盤に大きく依存しないでユニット・テスト可能でなければならないということについて論じてきました。TDDは開発チームがプロジェクトの早い段階で設計上の問題を発見したりコードとドメイン・モデルの整合性が取れていることを検証するのに役立ちます。状態と振舞いを共にドメイン・クラスが保持し、ドメイン・クラスは独立してテストし易いようになっているはずなので、DDDにはテスト・ファースト開発が最善の方法となるはずです。ドメイン・モデルの状態と振舞いをテストすることが重要であり、データ・アクセスや永続化といった実装の詳細にこだわり過ぎるべきではありません。
JUnitやTestNGのようなユニット・テスト向けフレームワークはドメイン・モデルの実装や管理をするのにとても有効なツールです。DBUnitやUnitilsのようなテスト・フレームワークもドメイン・レイヤのテスト、特にDAOクラスにテスト・データを投入するような際に役立ちます。これによってユニット・テスト・クラスでテスト・データを初期化するための余計なコードを最小限に抑えることができます。
モック・オブジェクトもドメイン・オブジェクトを個別にテストするのに役に立ちます。ただし、ドメイン・レイヤで過剰にモック・オブジェクトを使わないように留意する必要があります。もしドメイン・クラスをテストする簡単な方法が他にあるようなら、モック・オブジェクトではなくてそちらを使うべきです。例えば、(DAOのモック・オブジェクトを実装する代わりに)実際のデータベースではなくてイン・メモリで稼働するHSQLDBをバックエンドにして実際のDAOクラスを使ってEntityクラスをテストすることが出来ます。こうすることでドメイン・レイヤのユニット・テストの実行時間が短縮されますが、それはモック・オブジェクトを使う根拠でもあったはずです。このようにしてドメイン・オブジェクト間の協調動作(相互作用)と同時にオブジェクト間でやり取りされる状態(データ)もテストすることになるのです。モック・オブジェクトを使うと、ドメイン・オブジェクト間の相互作用しかテストできません。
ひとたび開発が完了すれば、開発工程で作成された全てのユニット・テスト、インテグレーション・テストは(TDDを採用したかどうかにかかわらず)自動テストの一部となるのです。これらのテストは保守され、新たなコードの変更がドメイン・クラスにバグを埋め込んでいないことを確認するためにローカル環境やより高次の開発環境で頻繁に実行されなければなりません。
Eric Evans氏は自書(サイト)の中でCIに言及していて、CIへの取り組みは常にコンテキスト境界に対して適用されコードと同様に人々の同期が取れていなければならないとしています。CruiseControlやHudsonのようなCIツールを使って自動ビルドやテスト環境のセット・アップ、(AntやMavenといったビルド・ツールによって生成された)アプリケーションのビルド・スクリプトの実行、(CVS、Subversionなどの)SCMリポジトリからコードをチェックアウトし、(アプリケーション内の他のクラス同様)ドメイン・クラスをコンパイルしてビルド・エラーがなければ(ユニット、インテグレーションを含む)全てのテストを実行することが出来ます。ビルドやテストのエラーがあったかどうかをプロジェクト・チーム(のメンバ)に対して(eメールやRSSフィードの形式で)通知するようにCIツールを設定することも可能です。
デプロイ
ドメイン・モデルは静的なものではありません。プロジェクトのライフサイクルにおいてビジネス要求が進化するのに合わせてドメイン・モデルは変化し、別のプロジェクトとして新しい要求の発生もあるでしょう。さらに、ドメイン・モデルを開発し実装していく過程であなた自身も学習し、向上することで新しい知見を既存のモデルに適用したいと思うでしょう。
ドメイン・クラスのパッケージングとデプロイをする際には分離することが鍵となります。ドメイン・レイヤは一方でDAOレイヤに他方ではService Facadeレイヤに依存するので(図2のアプリケーション・アーキテクチャの図を参照)、これらの依存関係をキレイに管理するためにドメイン・クラスを一つ以上のモジュールとしてデプロイすることには十分な意味があります。
DI、AOP、そしてFactoryのようなデザイン・パターンは設計時のオブジェクト間の結合度を最小化しアプリケーションのモジュール化を促進しますが、OSGi(以前Open Services Gateway initiativeと呼ばれていたものです)は実行時におけるモジュール化の問題を解決します。OSGiはエンタープライズ・アプリケーションのパッケージングと配布に関する標準的な仕組みになりつつあります。OSGiはモジュール間の依存関係をうまく解決します。さらにドメイン・モデルのバージョン管理の目的にOSGiを使うこともできます。
DAOクラスを一つのOSGiバンドル(DAOバンドル)に、Service Facadeクラスを別のバンドル(Serviceバンドル)にパッケージングすることが出来ます。こうしておくとDAOまたはServiceの実装が変更された場合や別のバージョンのアプリケーションがデプロイされる場合に、OSGiの効果により、アプリケーションの再起動は不要となります。さらに仮に後方互換性を確保するためにあるオブジェクトの既存のバージョンと新しいバージョンの両方をサポートする必要がある際には同じドメイン・クラスの二つの異なるバージョンをデプロイすることも可能になります。
OSGiの能力を活かすために、アプリケーションのオブジェクトは使用される前に(つまりクライアントがそのオブジェクトを探す前に)OSGiプラットフォームへ登録されていなければなりません。これはつまり登録処理のためにOSGiのAPIを使わなければならないと同時に、OSGiコンテナを使ってサービスの開始と終了が行われる際に処理が失敗した場合の対応もしなければならないということを意味しています。Spring Dynamic Modules(Spring DM)フレームワークはどのような種類のオブジェクトであってもコードを変更することなくアプリケーションに対してエクスポート、インポート出来るようにするのでこの領域で役立ちます。
Spring DMはさらにコンテナを使わずにOSGiとのインテグレーション・テストを実行するためのテスト・クラスも提供しています。例えば、AbstractOsgiTestsはIDEから直接インテグレーション・テストを実行する際に使えます。テストの準備はテスト用の基盤部分が行うのでテストのためにMANIFEST.MFファイルを作成したり、パッケージングやデプロイをする必要がありません。このフレームワークは現在使われているほとんどのOSGi実装(Equinox、Knopflerfish、そして Apache Felix)に対応しています。
ローン処理アプリケーションではOSGi、Spring DM、そしてEquinoxコンテナを使ってモジュール間の依存関係とドメイン、その他のモジュールのデプロイを管理しています。LoanAppDeploymentTestsクラスはSpring DMのテスト・モジュールの使い方を示しています。
サンプル・アプリケーションの設計
ローン処理のサンプル・アプリケーションで使っているドメイン・クラスの一覧を下に示します。
エンティティ
- Loan
- Borrower
- UnderwritingDecision
- FundingRequest
バリュー・オブジェクト
- ProductRate
- State
サービス
- FundingService
リポジトリ
- LoanRepository
- BorrowerRepository
- FundingRepository
以下の図3はサンプル・アプリケーションのドメイン・モデルを図示しています。
図3.レイヤ分けされたアプリケーションのドメイン・モデル(拡大表示するには画像をクリックして下さい。)
この記事で説明してきた多くのDDD設計に関するコンセプトや技術がサンプル・アプリケーションに適用されています。DI、AOP、アノテーション、ドメイン・レベルのセキュリティ、そして永続化などのコンセプトが使われています。そしてDDD開発と実装をするためにいくつかのオープン・ソースのフレームワークも使っています。これらのフレームワークを以下に挙げます。
- Spring
- Dozer
- Spring Security
- JAXB (データをまとめたり分解するのにSpring-WSを使った)
- Spring Testing (ユニット・テスト、インテグレーション・テストのために)
- DBUnit
- Spring Dynamic Modules
サンプル・アプリケーションのドメイン・クラスはEquinoxとSpring DMという2つのフレームワークを使ってOSGiモジュールとしてデプロイしています。以下の表にサンプル・アプリケーションにおけるモジュールのパッケージングの詳細を示します。
表5.パッケージングとデプロイの詳細
レイヤ | デプロイする成果物の名前 | 構成モジュール | Springの設定ファイル |
---|---|---|---|
クライアント/コントローラ | loanapp-controller.jar | Controller(コントローラ), クライアント側のDelegateクラス | LoanAppContext-Controller.xml |
ファサード | loanapp-service.jar | (リモート)サービスのFacade, サーバ側のDelegateクラス, XSD | LoanAppContext-RemoteServices.xml |
ドメイン | loanapp-domain.jar | ドメイン・クラス, DAO, 共通的なDTO | LoanAppContext-Domain.xml, LoanAppContext-Persistence.xml |
フレームワーク | loanapp-framework.jar | フレームワーク, ユーティリティ, モニタリング(JMX)用のクラス, アスペクト | LoanAppContext-Framework.xml, LoanAppContext-Monitoring.xml, LoanApp-Aspects.xml |
結論
開発チームがひとたびDDDの訓練を受け"ドメイン・ファースト、インフラ・セカンド"という哲学を適用し始めると、DDDはモデリング担当者、アーキテクト、開発者、そしてテスト担当者のソフトウェアに対する見方を変えるほど強力な考え方になります。(ITや業務部門といった)異なる背景と異なる分野の専門知識を有する様々なステークホルダがドメイン・モデリング、設計、そして実装に関与するので、Eric Evans氏の言葉を引用すると"(DDDによる)設計哲学とそれを実現するための(OOP、DIそしてAOPといった)技術要素の境界を見失わないことが大切"なのです。
未開の分野の開拓
この節ではDDD設計と実装に影響を与える進化中の手法について紹介します。これらの考え方のいくつかは現在も進化している段階でそれらがどのようにDDDに影響するのかを見ておくのは面白いことだと思います。
アーキテクチャ上の規約や契約による設計を強制することはドメイン・モデルの標準化や実装に関するベスト・プラクティスといったポリシーを強制したり管理するのに重要な役割を担います。Ramnivas氏はRepositoryオブジェクトはFactoryを通して生成しなければならないという、破られ易いドメイン・レイヤのルールを強制するためにアスペクトを利用することについて論じています(サイト)。
ドメイン特化言語(DSL)と業務自然言語(BNL)は近年さらに注目を集めています。これらの言語はドメイン・クラス内でビジネス・ロジックを表現するのに使うことが出来ます。BNLはビジネス要求を捉え、ビジネス・ルールを文書化し、実行可能なコードも記述出来るという意味でとても強力です。さらにはシステムが期待通りに動いていることを確認するためのテスト・ケースを記述するのにも使えます。
振舞い駆動開発(BDD)(サイト)も近年議論されるようになった興味深い考え方です。BDDはビジネスと技術の橋渡しをする共通の語彙(ユビキタス言語)を提供することで、優先順位付けされた、検証可能な業務上の価値を提供することに焦点を当てた開発をするのに役立ちます。テストではなくシステムの振舞いという側面に注目した専門用語を使うことで、BDDは開発者がTDDの中に見つかる本来の価値に直接注力できるようになっています。ドメイン・オブジェクトの開発は、全てのドメイン・オブジェクトが状態と振舞いをカプセル化することが望まれるというBDDの考え方の影響を明らかに受けているので、正しく実践すればBDDはDDDを大いに補完することになるでしょう。
イベント駆動アーキテクチャ(EDA)(Wikipedia)もドメイン駆動設計においてある種の役割を担うことがあるでしょう。例えば、ドメイン・オブジェクトのインスタンスの状態が変わったことを知らせるイベントはドメイン・オブジェクトの状態が変更した直後に処理すべきタスクを制御するのに役立つでしょう。EDAは中心となるドメイン・ロジックの中にイベント・ベースのロジックが紛れ込まないようにカプセル化するのに役立ちます。Martin Fowler氏がDomain Event(ドメインにおけるイベント)(サイト)のデザイン・パターンについて記述しています。
文献
- Domain-Driven Design, Tackling Complexity in the Heart of Software, Eric Evans, Addison Wesley
- Applying Domain-Driven Design and Patterns, Jimmy Nilsson, Addison Wesley
- Refactoring to Patterns, Joshua Kerievsky, Addison Wesley
- Can DDD be Adequately Implemented Without DI and AOP?
サンプル・アプリケーションのコードはこちらからダウンロード出来ます。