ひがやすを技術ブログ

電通国際情報サービスのプログラマ

EJB3時代のアーキテクチャパターン

EJB3JSFJPAを使ったときのアーキテクチャは、ある一定のパターンで説明できると思っています。私見ですが、説明したいと思います。
まず、プレゼンテーション層であるJSFですが、ページ(View)ごとにManagedBean(s)を定義します。ManagedBeanの作り方は3パターンあり

  1. イベント処理専用(Action)でモデルとしてはEntity(ドメインモデル)を使う(Action only)
  2. イベント処理とプレゼンテーションモデルを兼用(Page only。Pageでイベントも処理)
  3. イベント処理(Action)とプレゼンテーションモデル(Page)を分離(Action + Page)

があります。
私は、ドメインモデルは、ドメイン層でのみ使い、プレゼンテーション層では、専用のプレゼンテーションモデルを使うべきだと思っています。なぜなら、ドメイン層とプレゼンテーション層では、モデルとして必要な構造・役割が異なるからです。
ただ、頑固にドメインモデルをプレゼンテーション層で使いたがる人もいて、プレゼンテーションモデル(昔のDTO)を使うことは、OOではないと言い張る人もいます。OOとは、それぞれの役割に応じて適切にクラスを分割することであり、データと振る舞いを単純に一緒にすることではないと思いますが、まぁいいでしょう。2のPage onlyのパターンは、プレゼンテーション層におけるデータと振る舞いを一緒にしたものであり、頑固なOO派も満足するかもしれません(笑)。
プレゼンテーションモデルを使うほうが、より適切な役割にもとづいているので、OO的に望ましいと思いますが、問題点は、ドメインモデルとプレゼンテーションモデルとの変換コストです。これについては、Dxoを使うことで解決することができます。Dxoの説明は、ここを見てください。近日中(たぶん1週間以内)にリリースします。
プレゼンテーション層でEntityを使うときの問題点は、Viewのレンダリングのときに、Entityの関連をたどる場合があることです。例えば、#{employee.department.name}。lazy loadの場合、関連をたどるとき(関連をたどるのがはじめての場合)に永続コンテキストを必要とします。JPAでは、コンテナ管理の場合、永続コンテキストは、トランザクションに関連付けられているため、Viewのレンダリングのときも、トランザクションが必要だということになってしまいます。
この問題を解決するために登場するのが、ServletFilterを使ったトランザクション管理です。レンダリングという本来ならトランザクションに無関係な処理で、フレームワークの都合のためにトランザクション処理をするというのは、好ましくないことですし、無駄にトランザクションが長くなってしまうのも好ましくないことですが、目をつぶることにします。今後、ServletFilterを使ったトランザクション管理をするための機能を提供するフレームワークもいろいろ現れるでしょうが、このような問題があることは認識しておくべきです。これらの問題は、プレゼンテーションモデルを使えば、起きることはありません。どう考えてもプレゼンテーションモデルを使うべきだと思うのですが、まぁいいでしょう。詳しくは、ここを見てください。
ManagedBeanのパターンは、この3つです。次は、業務ロジックの実装の仕方を説明したいのですが、とりあえず時間切れ。

EJB3時代のアーキテクチャパターン 業務ロジックType1

EJB3JSFJPAを使ったときに業務ロジックはどのように実装すればよいのでしょうか。

  • Type1:ManagedBeanに業務ロジックを書く

最初は、ManagedBean(多くの場合Action)に業務ロジックを書くパターンです。

  • TransactionServletFilter
  • Action(プレゼンテーションロジック、業務ロジック、データアクセスロジック)
  • EntityManager(ActionにDI)
  • Entity

で構成されます。EntityManagerを使ってDBにアクセスし、後はすべてActionで処理します。
TransactionServletFilterは、(おそらく)フレームワークで提供され、EntityManagerは、JPAの実装から提供されるので、開発者が書くのは、ActionとEntityになります。Entityは、近いうちにツールで自動生成することがほとんどになってくると思われるので、開発者が実際に書くのは、Actionだけになるでしょう。
クラスを1個だけ書けば良いので、シンプルで素敵!!!なわけありません。Type1の問題点は次のとおりです。

  • プレゼンテーションロジック、業務ロジック、データアクセスロジックがActionに集中するので、クラスが肥大化してメンテナンスが困難になる。
  • 業務ロジックがViewごとに実装されるので、同じようなロジックが複数のViewに分散する可能性がある。
    • Viewごとにロジックを実装する場合、自分が開発していないViewのことは、ほとんど気にかけないため、他のViewで同じようなロジックが実装されていても気付かない。
  • データアクセスのロジックが個別に実装されるので、同じようなロジックが複数のViewに分散する可能性がある。

どっちもかなり問題であり、Type1を使うのはお勧めしません。登場するクラスが少なければ、シンプルで良いんだみたいな単純な意見を言う人がたまにいるようですが、もちろんそんなことはありません。それぞれ、適切に役割分担されているのが良い設計なのです。

EJB3時代のアーキテクチャパターン 業務ロジックType2

  • Type2:Serviceに業務ロジックを書く

ManagedBeanに業務ロジックを書くパターンの問題点である「クラスが肥大化してメンテナンスが困難になる」を解決するのが、このTypeで、Actionから業務ロジックを分離して、Serviceで業務ロジックを実装します。ServiceはActionごとに用意され、実体はStateless SessionBeanになります。データアクセスロジックはDaoで実装して、Serviceクラスから利用します。DaoはEntityごとに用意します。DaoをEntityごとに用意すれば、データアクセスロジックを使いたい人は、最初に対象となるEntityのDaoをチェックするでしょうから、データアクセスロジックの分散を避けることができます。EntityManagerはDaoにDIします。

  • TransactionServletFilter
  • Action(プレゼンテーションロジック)
  • Service(業務ロジック。ActionにDI)
  • Dao(データアクセスロジック。ServiceにDI)
  • EntityManager(DaoにDI)
  • Entity

開発者が書く必要があるのは、Action、Serviceのインターフェース、Serviceの実装、Daoのインターフェース、Daoの実装です。クラス数は増えてしまいましたが、Actionの肥大化に問題、データアクセスロジックの分散問題は解決されました。
しかし、View:Action:Serviceは1:1:1の関係なので、「Viewごとに業務ロジックが実装されるので、同じようなロジックが複数のViewに分散する可能性がある」問題は、依然として解決されていません。
後、Daoの必要性については考えてみる必要があるでしょう。Daoがなければデータアクセスロジックが分散する危険性がありますが、小さなアプリケーションの場合、多少分散してもたかが知れているという考えもあります。以前のようにデータアクセスのフレームワークを隠蔽するという必要性は、JPAデファクトになるので余り考えなくてもいいでしょう。ただし、メンテナンスのことを考えると、データアクセスロジックが分散しているより、ここを見れば分かるってなっていたほうが望ましいと思います。