Hatena::ブログ(Diary)

yvsu pron. yas このページをアンテナに追加 RSSフィード

2006-08-18

Pageクラス

Pageクラスとは、HTML(JSP)と一対一に対応するクラスで、画面に関する情報イベントを処理するロジックで構成されています。

HTMLの入出力項目がPageクラスプロパティボタンクリックイベントがPageクラスのメソッドに対応すると考えると分かりやすいのではないでしょうか。

TeedaExtensionを例に説明します。

例えば、<input type="text" id="arg1"/>というタグは、Pageクラスのarg1というプロパティに対応します。arg1のプロパティJavaで値を設定しておけば、HTMLに自動的に表示され、HTMLに入力した値は、自動的にarg1のプロパティに設定されます。

また、<input type="submit" id="doCalculate" .../>のタグクリックするとPageクラスのdoCalculate()メソッドが呼び出されます。

このように、HTML(JSP)とPageクラスを一対一に対応させるアーキテクチャをPage駆動アーキテクチャといいます。Page駆動アーキテクチャ以外で有名なものには、StrutsのようなRequest駆動アーキテクチャJSFのようなEvent駆動アーキテクチャがあります。Page駆動アーキテクチャは、Event駆動アーキテクチャの一種です。

Page駆動アーキテクチャは、HTML(JSP)との関連が明確で分かりやすいので、今後のプレゼンテーション層での主流になるのではないかと思っています。

Pageクラスは、プレゼンテーション層に最適な構造を持っているので、「プレゼンテーションモデル」ということもできます。HTMLid属性を書くだけでJavaと連携できるのも、プレゼンテーション層に最適な構造を持っているためです。

プレゼンテーションモデルを使うとプレゼンテーション層の開発がシンプルで分かりやすくなるということは理解していただけたのではないかと思います。

プレゼンテーションモデル(Page)を使う方法以外で有力なソリューションは、イベントハンドラ(Action) + ドメインモデル(Entity)を使う方法です。それぞれのプレゼンテーション層におけるメリット・デメリットは以下のとおりです。

プレゼンテーションモデル(Page)

イベントハンドラ(Action) + ドメインモデル(Entity)

つまり、プレゼンテーション層の開発の簡単さとモデルの変換のコストトレードオフになっています。ここで、モデル変換のコストがほとんどかからないとしたらどうでしょうか。プレゼンテーションモデル(Page)のデメリットがなくなり、メリットだけが残ります。

モデル変換のコストをほとんどなくすために使われるのが、Dxo(S2Dxo)です。S2Dxoを使うとインターフェースにメソッドを書いておくだけで、モデル変換のロジックは、アスペクトが自動生成します。

例えば、EmplooyeeというEntityをEmployeePageというPageに変換するとします。EmployeePageDxoに次のメソッドを書いておきます。

void convert(Employee employee, EmployeePage employeePage);

実装クラスアスペクトが自動生成するのが書く必要はありません、後は、EmployeePageにEmployeePageDxoのsetterメソッドを書いておけば、自動的にDIされるので、EmployeePageで必要なときに次のメソッドを呼び出せば変換完了です。

employeePageDxo.convert(employee, this);

ほとんどコストをかけずにモデルの変換ができることが分かっていただけたのではないかと思います。

私の結論はこうです。

Pageクラスを使うことでプレゼンテーション層の開発がシンプルで簡単になり、モデルの変換のコストDxoでほとんどなくすことができる。そのため、今後は、Page駆動アーキテクチャを使ったほうが開発は楽になる。

Pageクラスにはイベント処理をするための振る舞いがあるので、インターフェースが必要なのではと思う方もいるかもしれません。この件については次のように考えています。

2006-08-17

Seasar2.4のパッケージ構成

Seasar2.4では推奨のパッケージ構成があります。推奨のパッケージ構成を決めた理由は2つあります。

1つめは、プロジェクトのメンバの知識を共有すること。ある機能を持ったクラスは、どのパッケージに配置するのかあらかじめ決まっていると、直ぐに目的クラスを探すことができるようになります。

2つめは、パッケージ構成を決めることで、フレームワークやツールがいろいろと作業を自動化できることです。例えば、HOT & COOL deployでは、このパッケージ構成にもとづいて自動的にコンポーネント探してをコンテナに登録します。

もちろん、推奨のパッケージ構成を使わずに自由に決めることもできます。その場合は、HOT deployは使えませんが、Seasar2.3の自動登録は、そのまま使うことができます。

パッケージの構成パターンは2つあります。

1つめは、ルートパッケージ + 個別パッケージの構成です。例えば、examples.chura.logic, examples.chura.daoというパッケージの場合、examples.churaルートパッケージlogicやdaoが個別パッケージになります。

もう1つのパターンは、ルートパッケージ + web + サブアプリケーションの構成です。例えば、足し算のサブアプリケーションパッケージは、examples.chura.web.addになり、addがサブアプリケーションに相当します。

サブアプリケーションとは、1つのアプリケーションを幾つかの論理的な固まりに分割したものです。複数の関連のある画面を一つにまとめたものと言ってもよいでしょう。

サブアプリケーション固有のものはサブアプリケーション配下に置き、複数のサブアプリケーションで共有されるものは個別パッケージに置くというのが、2つのパッケージ構成をとっている理由です。

サブアプリケーションに置かれるのは、

で、個別パッケージに置かれるのが

  • Entity
  • Dao
  • Logic
  • Validator
  • Converter
  • Interceptor
  • Dto
  • Dxo(共通的に使われるDxo)
  • Helper
  • Util

です。ActionやServiceがなくなっていたり、Helperのような見慣れないクラスがいるのですが、その辺は、これから説明します。

2006-06-15

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

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

Viewごとに業務ロジックを書けば、分散してしまう可能性がありますが、Entityにロジックを書けば、分散の危険性が減ります。なぜなら、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つのクラスカプセル化されてバンバンザイ???。改良(?)されたバージョンは次のようになります。

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

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

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

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

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

という問題がありました。これらの問題を解決するのがEntityFacadeになります。EntityFacadeはEntityと一対一で作成します。業務ロジックを呼び出したい場合、Entityのメソッドを直接呼び出すのではなく、EntityFacadeのメソッドを呼び出すようにします。Entityは引数で渡します。

処理がEntity内で完結している場合は、EntityFacadeは、Entityにその処理を委譲します。

直接自分と関連の無いEntityのデータを必要とする場合は、EntityFacadeが必要なデータを取得した上で、Entityのメソッドの引数で必要なデータを渡します。これで、Type3の最初の問題を解決することができました。

複数のEntityを処理するメソッドもEntityFacadeに置けば良いのです。これで、2つめの問題も解決することができました。

開発者が書く必要があるのは、Action、EntityFacadeのインターフェース、EntityFacadeの実装、Entityに対する業務ロジックの実装です。Daoは省略しています。その場合でも、データアクセスロジックは、EntityFacadeに集約されているので、分散する危険もありません。

一般的なEJB3時代のアーキテクチャは、このType4で良いのではないかと思います。

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

一般的なEJB3時代のアーキテクチャはType4で十分だと思います。でも、EJB3って開発が重いよね。修正するたびに、パッケージング化して、アプリケーションサーバデプロイしないと、実際に動かしてみることできないって耐え切れない。

でも大丈夫。Seasar2では、HOT deployの機能を提供しています。HOT deployとは、ソースを変更したときに、アプリケーションサーバのリブート無しにその変更が即座に認識される技術です。このデモをみると、直ぐにどんな技術か理解できるでしょう。

Type4をベースにHOT deployを適用できるようにしたのがこのTypeになります。

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

そのため、EntityにあるロジックをすべてEntityFacadeに移したのがこのTypeです。ロジックはすべてEntityFacadeにあるので、名前もEntityLogicに変更してます。

また、Seasarでの開発の場合、プレゼンテーションモデルを使うことが推奨なので、プレゼンテーションモデルS2Dxoを使っています。

開発者が書く必要があるのは、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. HTMLidプロパティ名とイベント名を設定する。
  5. Page、Dxo、EntityLogic、Daoを自動生成する。
  6. EntityLogicの業務ロジックを実装&テストする。

このサイクルをHOT deployとDBスキーマデータ移行がサポートします。設定ファイルも書く必要がありません。開発の最初に基本的な情報記述するだけです。

Type5の変形として、Pageからプレゼンテーションロジックを分離し、データ交換ロジック(Dxo)をServiceに持っていったのが次のパターンです。

ちょっとクラスは増えますが、それぞれのクラスが1つの役割に徹していて、個々のクラスソースは最も単純になるでしょう。

これで、一通り基本的なアーキテクチャについて説明しました。

2006-06-14

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

EJB3JSF、JPAを使ったときのアーキテクチャは、ある一定のパターンで説明できると思っています。私見ですが、説明したいと思います。

まず、プレゼンテーション層である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

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

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

で構成されます。EntityManagerを使ってDBアクセスし、後はすべてActionで処理します。

TransactionServletFilterは、(おそらく)フレームワークで提供され、EntityManagerは、JPAの実装から提供されるので、開発者が書くのは、ActionとEntityになります。Entityは、近いうちにツールで自動生成することがほとんどになってくると思われるので、開発者が実際に書くのは、Actionだけになるでしょう。

クラスを1個だけ書けば良いので、シンプルで素敵!!!なわけありません。Type1の問題点は次のとおりです。

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

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

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

開発者が書く必要があるのは、Action、Serviceのインターフェース、Serviceの実装、Daoのインターフェース、Daoの実装です。クラス数は増えてしまいましたが、Actionの肥大化に問題、データアクセスロジックの分散問題は解決されました。

しかし、View:Action:Serviceは1:1:1の関係なので、「Viewごとに業務ロジックが実装されるので、同じようなロジックが複数のViewに分散する可能性がある」問題は、依然として解決されていません。

後、Daoの必要性については考えてみる必要があるでしょう。Daoがなければデータアクセスロジックが分散する危険性がありますが、小さなアプリケーションの場合、多少分散してもたかが知れているという考えもあります。以前のようにデータアクセスフレームワークを隠蔽するという必要性は、JPAがデファクトになるので余り考えなくてもいいでしょう。ただし、メンテナンスのことを考えると、データアクセスロジックが分散しているより、ここを見れば分かるってなっていたほうが望ましいと思います。

2006-05-08

ファイルの保存はどこでするのか

それは、ブラウザからアップロードされたファイルの保存をどこで、実装させるかということです。

frog-expressの日記 - GOYA ファイルの保存

ファイルの保存は、プレゼンテーション層のHelperロジックなので、それ用のコンポーネントを作り、ActionにDIします。なぜ、Serviceでやらないかというと、トランザクションは必要としないからです。