Struts開発パターン2

前回の開発パターン1に加えて、パターン2を作成しました。
今回は新規作成画面を追加します。

  • 1つの画面から2種類のアクションが呼び出されるケース
  • 2つのアクションで、同じ画面を共有するケース(新規作成画面と、更新画面が同一)

を含んでいます。

前提条件として資料はこんな感じです。

一覧画面から、新規作成画面へ遷移できます。
新規作成画面は、今までの編集画面(更新時)と、ほぼ同じレイアウトを使用します。
一部の文言が違うことと、商品番号が未決定になっています。商品番号は登録時に自動採番します。(ここでは仮に最大の商品番号+1としています)

画面遷移とアクションを検討し、ロバストネス図を作成しました。
パターン2 ロバストネス図

新規作成用の編集画面表示アクション(ProductEditNewAction)と、更新用の編集画面表示アクション(ProductEditUpdateAction)で、2つのActionクラスを作成することにしました。
ProductEditUpdateActionは、更新対象のデータを取得しますが、ProductEditNewActionはなにもしていません。
この2つのアクションを、1つのActionクラスで記述し内部で分岐することも可能です。しかし全く違う処理になった場合に、大きな分岐で2つの処理が記述されることも想定し、2つに分けてあります。

編集画面のJSPファイルは1つで、新規作成用と更新編集用を共用しています。
Actionは分けたのに、JSPはなぜ共有するか?」これは、JSPは画面デザインが大部分であり、このデザインは新規作成画面と編集画面で大きく変わることが少ないと判断したためです。タイトルやボタンの文言などの一部異なるデザイン部分だけ、if分岐処理で分けて書いてあります。
ちなみに分岐処理で判断するために、2つの編集アクションで、フラグ「isUpdate」をセットしています。更新時にはtrueを新規作成時にはfalseをセットしてあります。

■JSPでの分岐処理



${title}

同じようにこのフラグを使用して、遷移先のアクションパスが変化します。更新アクション(ProductUpdateAction)と新規作成アクション(ProductInsertAction)です。前回と同じように、このアクション自体は画面(JSP)に遷移せず、検索アクションに連鎖させます。

ここで問題になるのが、ProductUpdateActionと、ProductInsertActionに送信するActionFormです。
これらは同じJSPから送信されるほとんど同じデータです。唯一違うのは新規作成時には商品番号がないことです。
パターン1の方針を考えると、1Action-1ActionFormなのでここは別々にすべきです。
パターン2でも同じ方針で設計しました。ほとんど同じActionFormですが、ルールに従って迷わずコピーして2つのActionFormを作成します。

しかしながら、同様のデータを持つActionFormを同じにしたいと考えるかもしれません。その場合は、このActionFormは他で使用されていないか?また今後他の箇所で使われないか?ということを設計してから作成しなくてはなりません。ネーミングも悩ましいところです
今回であれば、ProductUpdateFormを両方のActionで使用してしまえばいいのですが、ネーミングがUpdateというのが気に食わないので、名前変更したくなります。
そこまで考えて2つのActionで、同一のActionFormを使用していたのに、のちの仕様変更で片方だけにある情報が増えてきたら、やはりまたまた分割しなくてはなりません。
このようなことがあるので、例え同じようなデータを送っているように見えても、2つのActionForm作成した方が良いという判断になりました。

まとめると、パターン2と書きましたが、実はパターン1プラスアルファの設計になります。
パターン1の設計に加えて、遷移先Actionが複数になるケースを想定しています。

開発パターン2

  • DTO使用せずに汎用的なコレクションを使用
  • Actionクラスを継承して作成
  • DAOを作成せずに、Actionクラスでビジネスロジックを作成
  • 1Action-1ActionFormのルールで作成(ActionFormのないActionも可)
  • 同一の画面を2つのActionから共有する場合はフラグを使用する
  • 1つの画面から2つのActionに遷移する際にはActionFormが同じであっても2つ作成する

ソースコードはこちらです。(kenkyu02パッケージが今回の対象です)
kenkyu02.zip


楽天用チェックボックスOFFのブックマークレット

おこづかい稼ぎに、楽天ポイントをセコセコためたりしています。
楽天ポイントって100円で1ポイントなのですが、キャンペーンとか注目していると結構簡単に貯まるんですよね。
まともに楽天市場で商品買うっていうのも、ポイントが貯まることなんですが、2倍キャンペーンとか、50ポイントプレゼントとかで稼いでる方が、はっきり言って効率がいい。
稼ぐのはいいのですが、いつも有効期限切れに焦って、割安ではあるんだけど、自分のお金+ポイントで何かつまらないものを買ってしまい、楽天の戦略にまんまとはまっているような気がします。

最近は楽天オークションの、入札すると10ポイントプレゼントのキャンペーンで1円入札してポイントを稼いでいます。
楽天オークションの入札時や、楽天市場で商品購入時に、「ショップからのメールを受け取る」とか、「楽天からの案内メールを受け取る」という選択肢が、初期値としてチェックされています。これをチェックしたまま、購入してしまうと、うざったいダイレクトメールが届くようになるので、いつもOFFにしてから購入しています。
あとから簡単に購読解除はできるのですが、常にONになって表示されるのが、非常にうざったいのです。

話はそれますが、確認画面のようなページで、ページ先頭に、「利用規約に同意して入札する」ボタンがあり、スクロールしないと見えない位置に、ダイレクトメール承諾のチェックボックスがONになって配置してあるのは、ずるい気がする。
面倒な話は無視して、メール配信を承諾させてしまうインターフェースで気に食わない。

そこで、ブックマークレットを作成しました。
ページ中のすべてのチェックボックスをOFFにするブックマークレットです。
以下のリンクを、右クリックしてブックマーク(orお気に入り)に登録してください。
使いたい場面でそのブックマークを選択することで、OFFにすることができます。

チェックボックスOFF←ここを右クリックしてブックマーク登録する

検索してみたら、ブックマークレット以外にも、スタイルシートやスクリプトで外す方法を紹介しているページもありました。そっちの方がいいかも。


Struts開発パターン1

以前の記事(Struts開発での考察)から継続してStrutsの開発パターンを1つ作成してみました。
とても作成するものが少ないパターンだと思います。

まず前提として、データベースの構造なんですが、商品データベースの一覧画面を想定しています。
商品データベースのER図

画面のイメージはこんな感じを想定しています。
一覧画面のイメージ編集画面のイメージ

特に難しいことはないと思います。
このテーブルに対する一覧画面と、編集画面を作成することを考えて、ロバストネス図を書きました。
パターン1 ロバストネス図

初めに動作するのは、検索アクションです。DBからProductsテーブルを全件検索し、一覧画面に検索結果を渡します。
一覧画面の「編集」リンクをクリックすると、その商品の編集画面を表示します。これが編集アクションです。編集アクションは、編集画面を表示するだけの処理で、更新処理は行いません。
編集画面から「更新」ボタンを押すと、更新アクションが動作します。更新アクションはデータベースの更新処理を行い、更新アクション自体は画面を表示せず、検索アクションに連鎖します。
<>は、ブラウザからのサーバへのHTTPリクエストの流れになります。
<>は、ActionからJSPへのフォワードになります。
<>は、ActionからActionへのフォワードのことですが、
Action-Action間の連鎖を区別してaction-chainと呼んでいます。
ここでは、更新処理が終わった後に、検索処理に連結して、再び一覧画面を表示するために使っています。更新アクション中に、検索のための処理を記述する必要はなくなります。

ソース

このソースファイルはkenkyu01.zipになります。
Struts関係のライブラリは、サイズが大きくなるので削除してあります。(WEB-INF/lib配下)
DB周りの操作にDBUtilsを使っています。Strutsのカスタムタグはstruts-htmlのみ使用し、あとはJSTLを使用しています。

注目すべきところ

編集アクションでは、2つのActionFormを使っています。
1つは、一覧画面から送信されたものです。Strutsにより生成されたProductEditFormをキャストして使用します。
もう1つは、編集アクションでnewして生成したものです。編集画面には、formタグが配置されています。このアクションで使用するActionFormがProductUpdateFormであるため、これをnewしてsetAttributeしています。
編集アクションはDBからデータを取得して返却するわけですが、その返却の方法としてActionFormを使用しています。返却のためにActionFormを使用するのは、返却後にテキストボックスなどのform部品として使用する場合のみです。
それ以外のケースでは、リクエストスコープの変数を使って画面にデータを転送します。一覧画面では検索結果のListをリクエストスコープのデータとして返却しています。

Strutsの設計パターンというよりも,Webアプリの設計パターンになるのですが、データベースへの接続処理は、ServletFilterで行っています。
ServletFilterのinit処理で、DataSourceを取得し、あとは1つのHTTPリクエストごとに、DB接続を生成し、リクエストの終了時に切断しています。ActionServletを継承し、executeメソッドを呼び出す前にDB接続する方法もありますが、アクション連鎖の際に再び接続してしまうので、HTTPリクエストの単位でDB接続を継続するようにしました。

トランザクションは、例外が発生しなければ常にコミットし、例外発生時にロールバックするようにしました。このため、各アプリケーションではコミットやロールバックする必要はなく、例外を投げるか正常に終了するかで決定します。正常処理でロールバックするというケースは少ないので、このような実装にしました。

DTOについて

今回の設計パターンでは、DTO(Data Transfer Object)を使用していません。MapやListなどの汎用的なコレクションクラスで代用しています。
こうすることで、新たなクラスを作成する必要がなくなりますが、反面静的な型チェックが行われなくなります。
JSPで、${product.pname}と記述できるのは、Mapに対して”pname”という文字列をキーに、商品名を格納してあるからです。
データベースから、検索した結果を、getString(フィールド名)で取得、DTOのプロパティに格納、JSPでデータ取得という作業が、DBUtils, BeanUtils, JSP-ELで非常に簡単に記述できています。

DAOについて

今回の設計パターンは、シンプルなパターンで、DAO(Data Access Object)は用意していません。
SQLがシンプルな場合などは、DAOを作成しなくてもActionクラスで処理してしまっていいのではないか?と思うのが私の考え方です。
DAOを作成する場合には、画面とは切り離して、Entityとしての役割を果たす形で考えるべきだと思います。
私の会社で開発している人を見ていると、DAOを作成するのはいいんですが、画面ごとにDAOを作成しているのを見かけます。そのため、同じようなSQLがあちこちのDAOに存在し、単にActionのソースファイルから、抜き出してDAOを作っているだけになっています。

そうなるくらいならば、Actionに記述したほうが、ロジックが分散しなくて見やすいです。どのみちDAO単独で再利用したりなんてしたことないし。(それどころか、DAOにActionFormを引き渡したりしているのを見かけたりもします←再利用できなくなる)

DAOの作成単位は、オブジェクト指向のモデリングによって決定すると思います。「画面1つにつきDAO1つ」とルール化したい気も分からないでもないですが、ルール化したければ、「テーブル1つにつきDAO1つ」の方が良いと思います。

ビジネスロジックの実装箇所について

Actionクラスから利用されるビジネスロジック用のクラスも作成していません。Actionに記述します。
ビジネスロジックは、別途作成した方がよいかも知れません。ですが本当にそのビジネスロジックを再利用できるように作成するつもりでビジネスロジックを別に分けるべきだと思います。分けたはいいが、結局、再利用できなくて別ファイルにロジックがジャンプしているだけにならないようにすべきです。

まとめると、今回の開発パターンはこのような感じです。

開発パターン1

  • DTO使用せずに汎用的なコレクションを使用
  • Actionクラスを継承して作成
  • DAOを作成せずに、Actionクラスでビジネスロジックを作成
  • 1Action-1ActionFormのルールで作成(ActionFormのないActionも可)

これをベースとして、他のパターンも応用して作成してみたいと思います。