ひがやすを技術ブログ

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

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

「Type2:Serviceに業務ロジックを書く」でも、同じようなロジックが複数のViewに分散する問題は以前未解決のままです。それを解決しようとするのがこのTypeです。

  • Type3:Entityに業務ロジックを書く

Viewごとに業務ロジックを書けば、分散してしまう可能性がありますが、Entityにロジックを書けば、分散の危険性が減ります。なぜなら、Entityを利用するときに、既に似たようなロジックがないか探すことができるからです。

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

開発者が書く必要があるのは、Action、Daoのインターフェース、Daoの実装、Entityに対する業務ロジックの実装です。
プレゼンテーションロジック、データアクセスロジック、業務ロジックがそれぞれ、Action、Dao、Entityにきれいに割り当てられました。データアクセスロジック、業務ロジックも分散していません。もしかして、これで、パーフェクト?
一見パーフェクトに見えますが、次の問題を解決する必要があります。

  1. 業務ロジックで自分と関連の無いEntityのデータを必要としたときに、どのように取得するのか。
  2. 複数のEntityを処理するメソッドをどこに置くのか。

1はどういうことかというと、口座Entityのメソッドで、引き出した金額に応じて残高から手数料を引き落とす必要があったとします。手数料の金額は、金額(の範囲)に応じて決まっていて、手数料テーブル(Entity)に格納されています。1つの行に、金額の上限と手数料が設定されているようなイメージ。はたして、口座Entityはどのようにして、手数料を求めるのでしょうか。口座Entityから手数料Entityへの関連はありません。
解決策の1つとして、JNDIをlookupしてEntityManagerもしくはDaoを取得する方法があります。でも、DI以前に退化してますよね。余り良いとは思えません。
別の解決策として、あらかじめ手数料を計算しておいて、口座Entityに引数で渡す方法があります。それで問題ないように思えますが、口座Entityが、Actionからみて、5番目に呼ばれるオブジェクトだったとしましょう。口座Entityに手数料を渡すために、1番目から4番目のオブジェクトの引数にも手数料の追加が必要になります。自分のためには必要ないのに。これも良いとは思えません。
2はどういうことかというと、金額に応じた適切な手数料を求めようとした場合に、手数料Entityの個々のインスタンスではどうしようもありません。全部(複数)の手数料Entityを使わないと計算ができないのです。
解決策の1つとして、staticメソッドに置く方法があります。手数料Entityにstaticメソッドとして手数料を求めるメソッドを追加するのです。そのstaticメソッドの中でも、データを取得するためにJNDIからEntityManagerもしくはDaoをlookupすることになるのでしょう。
結局、どれも今一で、納得のいく解決策はありません。Entityにロジックを集約しようとするのは無理があるのです。落としどころは、自分と関連の無いEntityを取得するときには、JNDIからEntityManagerもしくはDaoをlookupする。複数のEntityを扱うときには、staticメソッドを使うということになるのでしょう。そうなると、いっそDaoの機能もEntityのstaticメソッドにする方法もあります。これで、Entityに関するデータアクセスロジックと業務ロジックが1つのクラスにカプセル化されてバンバンザイ???。改良(?)されたバージョンは次のようになります。

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

JNDIやstaticメソッドを使うとテストが非常にしづらくなります。最も重要な業務ロジックがテストしにくいのは、良いアーキテクチャだとは思えません。

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

「Serviceに業務ロジックを書く」も「Entityに業務ロジックを書く」も結局問題を抱えています。それを解決するのがこのTypeになります。

  • Type4:EntityとEntityFacadeに業務ロジックを書く

Entityに業務ロジックを書いた場合

  1. 業務ロジックで自分と関連の無いEntityのデータを必要としたときに、どのように取得するのか。
  2. 複数のEntityを処理するメソッドをどこに置くのか。

という問題がありました。これらの問題を解決するのがEntityFacadeになります。EntityFacadeはEntityと一対一で作成します。業務ロジックを呼び出したい場合、Entityのメソッドを直接呼び出すのではなく、EntityFacadeのメソッドを呼び出すようにします。Entityは引数で渡します。
処理がEntity内で完結している場合は、EntityFacadeは、Entityにその処理を委譲します。
直接自分と関連の無いEntityのデータを必要とする場合は、EntityFacadeが必要なデータを取得した上で、Entityのメソッドの引数で必要なデータを渡します。これで、Type3の最初の問題を解決することができました。
複数のEntityを処理するメソッドもEntityFacadeに置けば良いのです。これで、2つめの問題も解決することができました。

  • TransactionServletFilter
  • Action(プレゼンテーションロジック)
  • EntityFacade(Entity単独で処理できない業務ロジック)
  • EntityManager(EntityFacadeにDI)
  • Entity(Entity単独で処理できる業務ロジック)

開発者が書く必要があるのは、Action、EntityFacadeのインターフェース、EntityFacadeの実装、Entityに対する業務ロジックの実装です。Daoは省略しています。その場合でも、データアクセスロジックは、EntityFacadeに集約されているので、分散する危険もありません。
一般的なEJB3時代のアーキテクチャは、このType4で良いのではないかと思います。

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

一般的なEJB3時代のアーキテクチャはType4で十分だと思います。でも、EJB3って開発が重いよね。修正するたびに、パッケージング化して、アプリケーションサーバにデプロイしないと、実際に動かしてみることできないって耐え切れない。
でも大丈夫。Seasar2では、HOT deployの機能を提供しています。HOT deployとは、ソースを変更したときに、アプリケーションサーバのリブート無しにその変更が即座に認識される技術です。このデモをみると、直ぐにどんな技術か理解できるでしょう。
Type4をベースにHOT deployを適用できるようにしたのがこのTypeになります。

  • Type5:EntityLogicに業務ロジックを書く

Entityに業務ロジックを書いた場合、業務ロジックの変更をHOTに認識することができません。なぜなら、Entityを管理しているJPAフレームワークは、HOT deployに対応していないからです。現在、Seasarファウンデーションでは、HOT deployに対応したJPAの実装であるKuinaを開発していますが、完成は、2007年の初め前後になりそうなので、今使うとなるとEntityではHOT deployを利用できないのです。
そのため、EntityにあるロジックをすべてEntityFacadeに移したのがこのTypeです。ロジックはすべてEntityFacadeにあるので、名前もEntityLogicに変更してます。
また、Seasarでの開発の場合、プレゼンテーションモデルを使うことが推奨なので、プレゼンテーションモデルとS2Dxoを使っています。

  • Page(プレゼンテーションモデル + プレゼンテーションロジック)
  • Dxo(データ交換ロジック。PageにDI)
    • S2Dxoを使う。インターフェースを書くだけでよく、実装は必要ない。
  • EntityLogic(業務ロジック。PageにDI)
  • Dao(業務ロジックにDI)
    • KuinaDaoを使う。インターフェースを書くだけでよく、実装は必要ない。したがって、EntityManagerも気にしなくても良い。
  • Entity

開発者が書く必要があるのは、Page、Dxoのインターフェース、EntityLogicのインターフェース、EntityLogicの実装、Daoのインターフェースです。ただし、実際はEntityLogicの実装以外は、ツールで自動生成する予定なので、開発者のコーディングは激減するはずです。また、テストコードの雛型も自動生成する予定です。
Viewテンプレートは、HTMLになります。いちいち、JSPに変換する必要もありません。HTMLの動的に変えたいタグに、対応するEntityのプロパティ名を設定し、anchorタグやsubmitタグにイベント名を指定するだけで、後は、ソースコードが自動生成されます。残りは、EntityLogicの実装を書くだけです。
ソースコードが自動生成されるだけではありません。Entity以外は、HOT deployに対応しているので、余分な待ち時間は必要なくさくさく開発できます。
Entityに修正が入る場合、それは、テーブルのレイアウトが修正されるのとほぼ同様の意味になります。その場合、スキーマやデータの移行もツールでサポートします。
このアーキテクチャの場合、開発手順は次のようになります。実装に近い部分の説明をします。

  1. HTMLのモックを作成する。
  2. HTMLのモックより、業務項目を洗い出して、データベースのスキーマを作成する。
  3. スキーマより、Entityを自動生成する。
  4. HTMLのidにプロパティ名とイベント名を設定する。
  5. Page、Dxo、EntityLogic、Daoを自動生成する。
  6. EntityLogicの業務ロジックを実装&テストする。

このサイクルをHOT deployとDBスキーマ・データ移行がサポートします。設定ファイルも書く必要がありません。開発の最初に基本的な情報を記述するだけです。
Type5の変形として、Pageからプレゼンテーションロジックを分離し、データ交換ロジック(Dxo)をServiceに持っていったのが次のパターンです。

  • Page(プレゼンテーションモデル)
  • Action(プレゼンテーションロジック)
  • Service(ActionにDI)
  • Dxo(データ交換ロジック。ServiceにDI)
    • S2Dxoを使う。インターフェースを書くだけでよく、実装は必要ない。
  • EntityLogic(業務ロジック。ServiceにDI)
  • Dao(業務ロジックにDI)
    • KuinaDaoを使う。インターフェースを書くだけでよく、実装は必要ない。したがって、EntityManagerも気にしなくても良い。
  • Entity

ちょっとクラスは増えますが、それぞれのクラスが1つの役割に徹していて、個々のクラスのソースは最も単純になるでしょう。
これで、一通り基本的なアーキテクチャについて説明しました。