今更ながらStrutsでの開発の相談を受けたりするのですが、どうもStrutsは好きになれません。
自分が使うぶんにはいいのですが、他人が設計しているのを見ると、いつも同じところでつまづいています。
Actionの設計単位が明確に決まっていないために、破綻しているケースが多いです。
私が考える設計方針
- Actionはイベント単位で設計する
- 画面遷移の単位でActionを定義しているために、つじつまが合わなくなったりするケースが多々見られます。
あくまでSubmit時に、Action(=行動)を呼び出し、そのイベントにあった処理を実行し、その結果として次の画面が得られると考えるべきだと思います。
Actionを画面遷移単位で考えると、同じ遷移だけどValidateをしたりしなかったりという矛盾が生じたりします。
でも、私の業務を考えると、大抵の場合はHTMLでの紙芝居的なモックアップを作るところから、外部設計が行われます。それをベースに考えると、画面遷移単位でActionを作るのが作りやすいのは確かです。(その対策については後述の振り分けActionを参照) - ActionFormはフォームデータの入れ物
- ActionFormはFormのデータを入れる器と考えます。
Formのデータというのは、テキストボックスや、チェックボックスや、hidden項目です。あくまでこれらの器として使うべきで、Actionの実行結果をJSPに渡すのは、別のBeanを用意すべきだと思います。いわゆるDTO(Data Transfer Object)とか、VO(Value Object)と言われるものです。検索条件をActionFormに持って、検索結果はDTOに格納してJSPにrequest.setAttributeで渡すべきだと思います。
そうなると得てして、ActionFormの中身をDTOに移し替える処理が発生しますが、これはいたし方ないと思います。BeanUtilsで中身をコピーしてしまいます。
JSPでは、そのままでもActionFormを参照できるので無駄なような気がしますが、そうしないとページがActionFormに強く結びついてしまいます。それはつまりActionに依存しがちになるため、ページがActionに依存してきてしまい。他のActionから同じページを使いにくくなります。 - ActionFormをnewするケース
- 前の項目とかぶりますが、次画面用のActionFormをnewしてそこに値を埋め込むこともあると思います。Actionで次画面用のデータを作成して、DTOに格納するわけですが、次画面のFormデータに関しては、そのActionFormをnewしてそちらで渡したほうがよいと思います。
これはStrutsのhtmlカスタムタグを使うにはその方が都合がよいからです。「ActionFormは入力フォームのデータ」だという概念があれば、次画面のActionFormをnewして渡すことには違和感はないかと思います。 - Actionの連鎖を使う
- action-forwardでの遷移先はJSPに限らず、Actionも定義できるので登録実行後に、再度検索を行うという処理は、登録Actionの遷移先が検索Actionという定義にすればOKです。
これをせずに登録Actionの後処理で次画面の検索処理を行うというのは、せっかくstruts-configにそういった連鎖の定義できるのを無駄にして、コード上に記述してしまっていることになります。
こういう書き方をしている人は、おそらくstruts-configの存在意義が分からず、定型的な記述になっていて、「なんのためにstruts-configがあるのだろうか?」と疑問をもっているかもしれません。
別Actionとして定義し、その関連をstruts-configに書くことで、struts-configを見れば、処理の連鎖になっているのかが分かります。
以上のように設計することで、非常にすっきりと迷うことなくActionを定義したり、ロジックを記述することができるようになりました。
1つ問題点は、イベント単位でアクションを設計することです。外部設計時には、画面遷移が重視されて設計されるのですが、実装時にはイベント重視で実装します。このギャップに戸惑うことが多いし、人に説明しても設計が手間取る部分であります。
その場合には、「振り分けAction」という考え方で設計すれば、(ちょっとオーバーヘッドはありますが)もう少し分かりやすくなると思います。
ある画面から呼び出されるActionは全て、その画面が1つだけもつ「振り分けAction」とします。
画面1つにつき1つの振り分けActionを持ちます。振り分けActionは、requestデータからイベントを判断して、実処理のActionにforwardします。これは前述のActionの連鎖を使います。
画面→振り分けアクション→検索Action
→登録Action
このほうが画面遷移を考慮した設計にマッチしやすいです。呼び出すActionはあくまで振り分けActionになります。
振り分けActionが単にフラグでもって判断する場合には、StrutsにDispatchActionというクラスがあるので、それを使えば振り分けActionを作る必要がなくなります。
画面→フラグで判断→画面Actionの検索メソッド
→画面Actionの登録メソッド
これもありだと思います。1Actionの記述が増えすぎないように、別途ビジネスロジックは画面に関係なく設計するようにします。
私自身もStrutsに関する設計が試行錯誤状態で、Struts in Actionとか読みたいとか思っているのですが、まだ手がつかないでいます。間違っていたらコメントいただきたいです。
遭遇した実装方式で、いちばん?だったケースはこんな感じ。
- 画面遷移単位でAction作ったけど、同じ遷移で処理が異なるケースがある。
- そのためフラグも送信するようにして、Action内でフラグをチェックして、処理をif文で分ける。
- 1Actionの記述が長くなりすぎたから、if文の分岐単位でロジッククラスを作って分岐する
こうすると、Actionに書いているのは、単なるif文だけで、実処理はロジッククラスと呼ばれるクラスに記述されます。
前述の振り分けActionに動きは似ていますが大きく違います。
分けるべきActionを1つにしてしまった上に、処理が多すぎるからまた分割しているという状態です。struts-configの記述が少ないだけで、実は本来struts-configに書くべきマッピングを、Action内のif文で記述しているだけです。
簡単な画面を作成したのですが、ボタン1では画面1、ボタン2では画面2へと画面遷移を振り分けたいと思ってaction1クラスとaction2クラスを作成し処理をわけましたが、今いちstruts-configでの処理の振りわけがわかりません教えてください。
こちらのページの開発パターン3を参考にしてください。
http://www.civic-apps.com/2006/05/26/struts-dev-pattern3/
最初の画面から呼び出されるアクション0で、処理の分岐を行い、forward先を2種類書きます。
1つは画面1を呼び出すアクション1で、もう1つは画面2を呼び出すアクション2です。
アクション0はアクション1,2に振り分けるだけのアクションとして動作します。