ひがやすを技術ブログ

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

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