11月号の特集2で「最新Eclipseで良いJavaプログラムを書こう」に関連する話題として、さらに視野を広げて実用的なウェブアプリケーションでのレイヤー構造とかドメインオブジェクトの関係はどうなるのか?という点について解説してみたいと思います。(まだ日経ソフトウエア11月号を手にしていない方はぜひ買ってくださいw)結論から先に出しますが、ドメイン駆動設計では一般論として下図のようなレイヤー構造やオブジェクトの関連が提唱されています。ドメイン層のオブジェクトについては変わりないのですが、ドメイン以外のレイヤーに新しく2つのサービスが登場しているので、まずそこから簡単に説明します。ドメイン層以外のサービス実はサービスはドメイン層だけではなく、アプリケーション層とインフラストラクチャ層にも存在する場合があります。その役割を以下にまとめてみました。アプリケーション固有のサービスを提供するクラス。ドメインオブジェクトやドメイン層のサービスを使って、アプリケーション固有の振る舞い(ビジネスロジック)を実装します。多くの場合はビジネスロジックをアプリケーションのサービスクラスに実装して、UI層から利用することになります。インフラストラクチャ固有のサービスを提供するクラス。データベースやネットワークに対する振る舞いを提供するサービスです。データベースの特定のテーブルに検索や更新系のクエリを送信するサービス。*1ネットワークであればサーバとクライアントの機能を提供するサービスなどが考えられます。ドメイン層のオブジェクトは、使いようによっては様々な要件をアプリケーションとして実装できる汎用的な部品群です。そのため、アプリケーション固有な要件はドメイン層ではなく、アプリケーション層のサービスに実装します。ドメインは汎用的に、アプリケーションはそれらを使ってより具体的に表現します。ドメイン層はビジネスにおける問題領域を扱うことが責務なので、データベースやネットワークについてはインフラストラクチャ層からサービスとしてドメイン層にインターフェイスを提供するようにします。このようにレイヤーの責務を逸脱しなければ、ドメイン層の見通しがよくなり、わかりやすくて理解しやすい設計に近づけることができます。Object)とは上図ではUI層のモデルと、ドメイン層のエンティティ、インフラストラクチャ層のエンティティは、いずれも似たような情報を使います。顧客の名前などを保持した情報です。その情報を画面から受け取ってDBに保存したり、DBから検索して画面に表示したりします。(上図のDXOの矢印の部分です。矢印の配置が悪いのですが、変換対象はドメイン層のエンティティやバリューオブジェクトになります)似たような情報になるはずなのに、レイヤーがわかれて別々のオブジェクトを定義しているのはなぜでしょうか?その理由を、典型的なユーザ登録画面の内部を例に考えてみたいと思います。仮に以下のようなクラス郡がある前提とします。具体的なソースは下部にあります。UI層は何らかのウェブフレームワーク(たとえばStrutsやJSFなど)で、ユーザ登録画面に入力された情報をRegisterUserFormに、画面の処理をRegisterUserActionで担当します。アプリケーション層では、ユーザ登録のビジネスロジックをUserRegisterServiceで担当します。ドメイン層では、ユーザを識別するエンティティとしてUser、ユーザの名前を表すバリューオブジェクトとして、Nameが存在します。リポジトリとしてはUserRepositoryが存在します。インフラストラクチャ層はユーザ情報は複数のテーブルに分かれていて、それぞれにデータベースアクセスのためのサービスとしてDaoクラスが用意されています。大まかな処理内容を以下に示します。UI層ユーザ登録画面で入力された情報はRegisterUserFormに格納される次にRegisterUserAction#doRegisterメソッドが呼ばれます。引数には1のRegisterUserFormが渡される。RegisterUserFormDxo#convertUserメソッドの引数にRegisterUserFormを指定し、Userに変換する。この際、RegisterUserFormの姓と名、姓かなと名かなの独立しているプロパティを、Nameに変換している。RegisterUserForm#getRegisterDateは画面の都合上、文字列の型ですが、ドメイン上では扱いやすくするためにjava.util.Dateに変換している。UserRegisterService#registerメソッドの引数に渡し、呼び出す。アプリケーション層UserRegisterService#registerメソッドではアプリケーションとしてのビジネスロジックが実装され、その中でDBに永続化する場合にUserRepositoryInDB#storeメソッドが呼ばれます。ドメイン層UserRepositoryInDB#storeメソッドの引数にUserを指定して呼び出す。UserDxo#convertUserProfileTableでテーブルクラスに変換する。convertUserProfileTableメソッドでは、Userが持つそれぞれのNameを姓と名、姓かなと名かなに変換している。UserProfileTable#setRegisterDateは、Timestamp型なので変換が必要となる。UserProfileDaoを使って、データベースに対して登録処理を行ないます。レイヤーが異なるとそれぞれのオブジェクトの持つプロパティの形式や型が変わってくることがわかったと思います。このようなデータの変換処理を行うオブジェクトのことをDXOと呼びます。UserProfileTableの3つのクラスが登場しました。同じ情報でもレイヤーの目的に応じて形を変えて存在するからです。レイヤーを無視して、RegisterUserFormをドメインに持ち込んだり、UserProfileTableをドメインに持ち込んだりしていては、ドメイン層は混乱します。DXOを行って、画面に情報を表示する必要があります。逆の流れの変換処理もあります。*2それぞれのレイヤーの責務に応じたモデルの型を扱うことで、レイヤーとモデルの混同を防止できます。同じプロパティやメソッドを持っていても、レイヤーが異なると概念が別なので、型としては異なるべきだと考えます。することでレイヤーの独立性を維持できると考えています。ドメイン駆動設計も設計手法の一つに過ぎないので、お作法を守りながらもより良い設計を求めていく姿勢が大事だと思う今日このごろです。何事も「守破離」ですね。ユーザ登録画面のコントローラクラス。ドメインオブジェクトをフォームに変換する場合、編集画面などで利用できる。既存のインスタンスに変換する場合はこのようなインターフェイスになる。アプリケーション固有の処理(ビジネスロジック)を扱うサービス。ドメインにおいて汎用的な永続化を扱うリポジトリ。}}*1:DAO=データアクセスオブジェクトもサービスの一つと考えられます*2:この例では手動でオブジェクト間のデータの詰め替え作業を行っていますが、commons-beanutilやSeasarのS2DxoやS2BeanUtilsを使えば手間がかなり軽減できます。その3〜終わったかとみえたスレッドセーフのネタですが、再浮上。Sourcingを学ぶための教材ServiceとDCIについて月別アーカイブはてなブログをはじめよう!j5ik2oさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。