2.4. アプリケーションのレイヤ化¶
Caution
本バージョンの内容は既に古くなっています。最新のガイドラインはこちらからご参照ください。
目次
本ガイドラインでは、アプリケーションを、次の3レイヤで分割する。
- アプリケーション層
- ドメイン層
- インフラストラクチャ層
各層には、以下のコンポーネントが含まれる。
各層について、説明する。
Note
アプリケーション層、ドメイン層、インフラストラクチャー層は Eric Evansの”Domain-Driven Design (2004, Addison-Wesley)”で説明されてる用語である。 ただし、用語は使用しているが以後Domain Driven Designの考えにのっとっているわけではない。
2.4.1. レイヤの定義¶
入力から出力までのデータの流れは、アプリケーション層→ドメイン層→インフラストラクチャ層であるため、 この順に説明する。
2.4.1.1. アプリケーション層¶
2.4.1.1.1. Controller¶
Spring MVCでは、@Controller
アノテーションがついた、POJOクラスが該当する。
Controllerの結果がView(の論理名)になる。
2.4.1.1.2. View¶
View
クラスが該当する。2.4.1.1.3. Form¶
Note
変換処理を実装する際、Controller内で行うと、ソースコードが長くなり、 本来のControllerの処理(画面遷移など)の見通しが、悪くなりがちである。 その場合は、Helperクラスを作成し、変換処理を委譲することを推奨する。
Spring MVCでは、Formオブジェクトは、リクエストパラメータを保持するPOJOクラスが該当する。form backing beanと呼ばれる。
2.4.1.1.4. Helper¶
Helperはoptionであり、必要に応じて、POJOクラスとして作成すること。
Note
HelperはControllerの見通しを良くするためのものであり、HelperはControllerの一部だと思えばよい。
Controllerの役割はルーティング(URLマッピングと遷移先の返却)であり、それ以外の処理(JavaBeanの変換等)が必要になったらHelperに切り出してそちらに処理を移すことを推奨する。
あくまでもControllerをすっきりさせて、本来のControllerの処理が見やすくなることを目的としており、Controller内のprivateメソッドみたいなものである。
2.4.1.2. ドメイン層¶
2.4.1.2.1. Domain Object¶
- EmployeeやCustomer, Productなどのリソース系モデル(一般的には、名詞で表現される),
- Order, Paymentなどイベント系モデル(一般的には動詞で表現される)、
- YearlySales, MonthlySalesなどのサマリ系モデル
データベースのあるテーブルの、1レコードを表現するオブジェクトを表現するEntityは、Domain Objectである。
Note
本ガイドラインでは主に、状態のみもつモデルを扱う。
Martin Fowlerの”Patterns of Enterprise Application Architecture (2002, Addison-Wesley)”では、 Domain Modelは、状態と振る舞いをもつものと定義されているが、 厳密には触れない。
Eric Evansの提唱するようなRichなドメインモデルも、本ガイドラインでは扱わないが、 分類上はここに含まれる。
2.4.1.2.2. Repository¶
2.4.1.2.3. Service¶
業務処理を提供する。 この処理も、トランザクション境界となる。
2.4.1.3. インフラストラクチャ層¶
2.4.1.3.1. RepositoryImpl¶
Spring Data JPAを使用する場合は、Spring Data JPAが実体を(一部)自動で作成する。
2.4.1.3.2. O/R Mapper¶
Note
MyBatis, Spring JDBCは「O/R Mapper」というより、「SQL Mapper」と呼んだ方が正確であるが、本ガイドラインでは「O/R Mapper」に分類する。
2.4.1.3.3. Integration System Connector¶
2.4.2. レイヤ間の依存関係¶
- アプリケーション層にSpring MVC
- インフラストラクチャ層にSpring Data JPA, MyBatis
レイヤ化を意識して、疎結合な設計を行うことを推奨する。
各レイヤのオブジェクトの依存関係は、DIコンテナによって解決される。
入力から出力までの流れで表現すると、次の図のようになる。
更新系の処理を例に、シーケンスを説明する。
- Controllerが、Requestを受け付ける
- (Optional) Controllerは、Helperを呼び出し、Formの情報を、Domain ObjectまたはDTOに変換する
- Controllerは、Domain ObjectまたはDTOを用いて、Serviceを呼び出す
- Serviceは、Repositoryを呼び出して、業務処理を行う
- Repositoryは、O/R Mapperを呼び出し、Domain ObjectまたはDTOを永続化する
- (実装依存) O/R Mapperは、DBにDomain ObjectまたはDTOの情報を保存する
- Serviceは、業務処理結果のDomain ObjectまたはDTOを、Controllerに返却する
- (Optional) Controllerは、Helperを呼び出し、Domain ObjectまたはDTOを、Formに変換する
- Controllerは、遷移先のView名を返却する
- Viewは、Responseを出力する。
この場合の各コンポーネント間の呼び出し可否を、以下にまとめる。
Caller/Callee | Controller | Service | Repository | O/R Mapper |
---|---|---|---|---|
Controller | ||||
Service | ||||
Repository |
Note
この呼び出し可否ルールを守ることは、アプリケーション開発の初期段階では、煩わしく感じられるかもしれない。 確かに、一つの処理だけみると、たとえばControllerから直接Repositoryを呼び出したほうが、速くアプリケーションを作成できる。 しかし、ルールを守らない場合、開発規模が大きくなった際に、修正の影響範囲が分かりにくくなったり、横断的な共通処理を追加しにくくなるなど、 保守性に大きな問題が生じることが多い。後で問題にならないように、初めから依存関係に気を付けて開発することを強く推奨する。
この場合の呼び出し可否は、次のようになる。
Caller/Callee | Controller | Service | O/R Mapper |
---|---|---|---|
Controller | |||
Service |
2.4.3. プロジェクト構成¶
上記のように、アプリケーションのレイヤ化を行った場合に推奨する構成について、説明する。
ここでは、Mavenの標準ディレクトリ構造を前提とする。
基本的には、以下の構成でマルチプロジェクトを作成することを推奨する。
- [projectname]-domain … ドメイン層に関するクラス・設定ファイルを格納するプロジェクト
- [projectname]-web … アプリケーション層に関するクラス・設定ファイルを格納するプロジェクト
- [projectname]-env … 環境に依存するファイル等を格納するプロジェクト
([projectname]には、対象のプロジェクト名を入れること)
Note
RepositoryImplなどインフラストラクチャ層のクラスも、project-domainに含める。
本来は、[projectname]-infraプロジェクトを別途作成すべきであるが、 通常infraプロジェクトを隠蔽化する必要がなく、domainプロジェクトに格納されている方が開発しやすいためである。 必要であれば、[projectname]-infraプロジェクトを作成してよい。
Tip
マルチプロジェクト構成の例として、サンプルアプリケーションや共通ライブラリのテストアプリケーションを参照されたい。
2.4.3.1. [projectname]-domain¶
[projectname]-domainのプロジェクト推奨構成を、以下に示す。
[projectName]-domain
└src
└main
├java
│ └com
│ └example
│ └domain ...(1)
│ ├model
│ │ ├Xxx.java
│ │ ├Yyy.java
│ │ └Zzz.java
│ ├repository ...(2)
│ │ ├xxx
│ │ │ └XxxRepository.java
│ │ ├yyy
│ │ │ └YyyRepository.java
│ │ └zzz
│ │ ├ZzzRepository.java
│ │ └ZzzRepositoryImpl.java
│ └service ...(3)
│ ├aaa
│ │ ├AaaService.java
│ │ └AaaServiceImpl.java
│ └bbb
│ ├BbbService.java
│ └BbbServiceImpl.java
└resources
└META-INF
└spring
├[projectname]-domain.xml ...(4)
└[projectname]-infra.xml ...(5)
項番 | 説明 |
---|---|
(1)
|
ドメインオブジェクトを格納する。
|
(2)
|
リポジトリを格納する。エンティティごとにパッケージを作成する。
関連するエンティティがあれば、主となるエンティティのパッケージに、従となるエンティティのRepositoryも配置する。
(OrderとOrderLineなど)。DTOが必要な場合は、このパッケージに配置する。
RepositoryImplは、インフラストラクチャ層に属するが、通常、このプロジェクトに含めても問題ない。
異なるデータストアを使うなど、複数の永続化先があり、実装を隠蔽したい場合は、別プロジェクト(またはパッケージ)に、RepositoryImplを実装するようにする。
|
(3)
|
サービスを格納する。業務(またはエンティティ)ごとに、パッケージインタフェースと実装を、同じ階層に配置する。
入出力クラスが必要な場合は、このパッケージに配置する。
|
(4)
|
ドメイン層に関するBean定義を行う。
|
(5)
|
インフラストラクチャ層に関するBean定義を行う。
|
2.4.3.2. [projectname]-web¶
[projectname]-webのプロジェクト推奨構成を、以下に示す。
[projectName]-web
└src
└main
├java
│ └com
│ └example
│ └app ...(1)
│ ├abc
│ │ ├AbcController.java
│ │ ├AbcForm.java
│ │ └AbcHelper.java
│ └def
│ ├DefController.java
│ ├DefForm.java
│ └DefOutput.java
├resources
│ ├META-INF
│ │ └spring
│ │ ├applicationContext.xml ...(2)
│ │ ├application.properties ...(3)
│ │ ├spring-mvc.xml ...(4)
│ │ └spring-security.xml ...(5)
│ └i18n
│ └application-messages.properties ...(6)
└webapp
└WEB-INF
├views ...(7)
│ ├abc
│ │ ├list.jsp
│ │ └createForm.jsp
│ └def
│ ├list.jsp
│ └createForm.jsp
└web.xml
項番 | 説明 |
---|---|
(1)
|
アプリケーション層の構成要素を格納するパッケージ。
|
(2)
|
アプリケーション全体に関するBean定義を行う。
|
(3)
|
アプリケーションで使用するプロパティを定義する。
|
(4)
|
SpringMVCの設定を行うBean定義を行う。
|
(5)
|
SpringSecurityの設定を行うBean定義を行う。
|
(6)
|
画面表示用のメッセージ(国際化対応)定義を行う。
|
(7)
|
View(jsp)を格納する。
|
2.4.3.3. [projectname]-env¶
[projectname]-envのプロジェクト推奨構成を、以下に示す。
[projectName]-env
└src
└main
└resources
└META-INF
└spring
├[projectname]-env.xml ...(1)
└[projectname]-infra.properties ...(2)
項番 | 説明 |
---|---|
(1)
|
環境に依存するBean定義(DataSource等)を行う。
|
(2)
|
環境に依存するプロパティを定義する。
|
Note
[projectname]-domainと[projectname]-webを別プロジェクトに分ける理由は、依存関係の逆転を防ぐためである。
[projectname]-webが[projectname]-domainを使用するのは当然であるが、[projectname]-domainが[projectname]-webを参照してはいけない。
1つのプロジェクトに[projectname]-webと[projectname]-domainの構成要素をまとめてしまうと、誤って不正な参照してしまうことがある。 プロジェクトを分けて参照順序をつけることで[projectname]-domainが[projectname]-webを参照できないようにすることを強く推奨する。
Note
[projectname]-envを作成する理由は環境に依存する情報を外出し、環境毎に切り替えられるようにするためである。
たとえばデフォルトではローカル開発環境用の設定をして、アプリビルド時には[projectname]-envを除いてwarを作成する。 結合テスト用の環境やシステムテスト用の環境を別々のjarとして作成すると、そこだけ差し替えてデプロイするということが可能である。
また使用するRDBMSが変わるようなプロジェクトの場合にも影響を最小限に抑えることができる。
この点を考慮しない場合は、環境ごとに設定ファイルの内容を行いビルドしなおすという作業が入る。
環境依存に関するファイルを別プロジェクトにする意義については、環境依存性の排除を参照されたい。