アクションフォームのスコープをどう扱うか?

気になっているのはActionForm。というか、私のところでは先代から引き継いだ特殊な改造版Strutsを使っているので、SAStruts以前に、生のStrutsでActionFormをどう扱っているかよくわからない。具体的には、スコープの扱い。Formをセッションスコープにすることができるが、これはいろいろまずいことがあるはず。

SAStrutsでは基本的には、リクエストスコープでActionFormを定義します。


そして、(稀なケースだと思いますが)必要に応じて、
ActionFormをセッションスコープで定義する場合は、
1.0.3-rc1 で追加される @Execute(removeActionForm=true)を使うことが
今後のセオリーになると思います。


@Execute(removeActionForm=true) を指定しておくと、
実行メソッド終了後にセッションにあるActionFormが削除されます。


参考:
[SASTRUTS-74]実行メソッド終了後にセッションにあるActionFormを削除できるように@ExecuteにremoveActionForm要素を追加しました
https://www.seasar.org/issues/browse/SASTRUTS-74

JavaEE勉強会に行ってきました

行ってきました〜。この勉強会への出席は1年ぶりくらいでしたが、
勉強会も2次会も大盛り上がりでめちゃくちゃ楽しかったです。

DDD読書会の進め方について

洋書を使った読書会の進め方も1時間近く議論しただけあって、
今後、良い感じに進めることができるような感じがします。
このノウハウが生まれる場に居ただけでも収穫ありですね。

今、自分が考えているWebアーキテクチャに DDD の良いところを貪欲に導入したいものです。


今後、読書会の成果は↓に反映される(はず)

Java de Haskell/GLAD!!さん

Haskellっぽく組める自作Javaライブラリを披露!
内部実装は、今まで自分が見たソースコードの中で一番ジェネリクスを多用してました。
次回は、ぜひJavaな人にもわかりやすいJavaモナドの説明が聞きたい。

Webアプリケーション論/ひがさん

詳細はこちらのエントリでうまくまとめられています。
http://d.hatena.ne.jp/kunit/20080629#1214709773

とにかくハイレベルな議論でした。
複数の視点で物事を捉える重要さを再認識させられました。
今度、論点を整理して、再びホットモットな議論をしたいですね。

2次会 & 3次会

偶然にも 2次会と3次会で隣の席だった PHPid:kunit さんの話が熱すぎて
ひがさんからかわいがりを受けちゃったよぉ(笑)。

もうねぇ、出羽さんのグループ。少しは気を使ったほうがいいよ。


そして、技術系飲み会によく参加するあなた、女性に限らないんだけど、みんながそれなりに会話に入れるよう気を使いましょう。これは、技術系飲み会に限らず、どんな飲み会にもあてはまるけどね。

ハイッ。来月はリベンジという訳で、ホットモット盛り上がりましょう!


それにしても、今でも、id:kunit さんの熱い語りが頭から離れません。
言い回しはともかく、主旨は以下のような内容でした。

一方がシンプルになると、他方が複雑化する。
(そもそも)アーキテクチャは、その押し付け合いを見極めることが重要。
日本の技術系コミュニティ界隈で他のコミュニティと決定的に異なる
3つのコミュニティが存在する。
それらは『Seasar』と『PostgreSQL』と『Ruby』だ。
理由は、他のコミュニティーは「ユーザーコミュニティ」であるが、
これら3つは「開発コミュニティ」であること。
(そもそも)「開発コミュニティ」は「ユーザーコミュニティ」と背負っている
重みが違うことがオレには良く分かる。
(そもそも)ピィーーー! (← 掲載危険w)


うーん、私の文章力では id:kunit さん熱さを上手く伝えられないなぁ。

※ いずれも「そもそも」は出羽の注釈(笑)。


ps. 後でブログを見て知ったのですが、id:kunit さんはMapleというフレームワークを開発されている方だったんですね。今度いろいろ聞かせてもらおう。

アクションフォーム名は「〜Form」の方が分かりやすいのでは?

SAStrutsではFormはSmartDeplayの対象になっていません。DTOをActionのフィールドにするようなルールです。


SAStrutsの規約に従うのも良いんですが、リクエストからセットされるフィールドは全てString型にしたかったのでDTOとは区別したいです。そこでActionFormとして使うDTOはFormとし、DTOは本来のレイヤ間のデータ転送にのみ使用するようにしたい。(僕の意向というよりも今までの人が違和感感じそうなので)


要は役割が違うので分離したいだけです。

私もこの方式に賛成です。
dtoパッケージ以下にいろいろな役割のデータクラスがごちゃごちゃしてしまうので、アクションフォームはFormという名前のクラスで扱ったほうが管理しやすいと思います。


この対応をアプリケーション開発者に個別でやってもらうのもいいのですが、SAStruts本体に取り込むのはどうでしょうか? > id:higayasuo さん
今後、アクションフォームの使用を推奨するとのことなので、やるならば今がタイミング的に良いと思います。
(もし、対応する場合は、私の方で修正するでも構いません。)

Genericsを使ったDaoの雛形

ジェネリックの部分があまりスマートなやり方じゃない気がしていますが、ひとまず以下のやり方だとOKです。


まずはジェネリックな共通親サービスクラスです。

上記の引用エントリの元エントリ → 続・SAStruts + S2JDBCのアーキテクチャ


SeasarSVNリポジトリ引用先のエントリで書いたものとほぼ同様の機能がS2本体に追加されていました。おおおっ。


 [CONTAINER-195]Genericsを使ったDaoの雛形を追加しました。
 http://svn.seasar.org/browse/?view=rev&root=s2container&revision=3595


ひがさんの手によって、ブラッシュアップされた AbstractService のソースコードを見て、流石だなぁ、なるほどなぁ、一味違うなぁ、と感心させられました。


フレームワーク側で AbstractService が用意されているので、アプリを作る側は、AbstractService を継承することでより簡単にS2JDBCを使うことができそうですね。


ps. うーむ、テストコードとはいえ、AbstractService2 というクラス名は微妙ですねぇ。例えば、AbstractServiceを S2AbstractService という名前に変更して、AbstractService という名前でS2AbstractService を継承するやり方をアプリケーション開発者に推奨する案はどうでしょう?アプリケーション開発者は、ネーミングに迷わないし、プロジェクトごとに工夫をこらして実装されるAbstractService クラスについての情報交換がやりやすくなるメリットがあります。

1.5階層のAction-Service-Logicパターン

趣旨とあんまり関係ないですが、Service・Logicをとりまぜた3階層にするならば、エンティティによったものをService、アクションによったものはLogicと呼んだ方が、フレームワーク側の呼び方との親和性は高いように思います。


ちなみに、今はこんな感じの設計はどうかと思っています。


Serviceクラス:エンティティと対につくる。ドメインモデル的な考え方がプロジェクト内でついていけないならばいっそのこと導入しない。


Logicクラス
ユースケースを跨がる画面まわりの制御処理や、ユースケースをまたがるビジネスロジック特有の処理を書く。いわゆる、サブシステム間共通関数のイメージ。たとえば複数ユースケースであるケースでは沢山の表にインサートするが、あるケースではアップデートのみするようなものを使う。
・ただし、Serviceクラス的設計が難しい場合には、画面制御的なクラス


Serviceクラスはフレームワークチュートリアルや今後出版される本などでも、ENTITYと連関した紹介がされるでしょうから、学習コストを考えると、アクション内の共通処理的なものの名前はLogicなど別の名称のほうがよいのではと思いはじめています。

kataoka_ayumu さん、ブログ上では初めまして(笑)。
kataoka_ayumu さんの考えているアーキテクチャを図示するとこんなイメージでしょうか?


図1: 2階層のAction-Service-Logicパターン


「Logicクラスの役割」や「フレームワーク側の呼び方との親和性」などは私も同意です。
もし、想定しているアーキテクチャが上の図と同じであれば、『ユースケース固有の要求も全てエンティティ単位のService経由で処理する点』については、意見が分かれるところだと思うので、深く検討してみる価値はあると思います。


私の考えている具体的な懸念点は次のとおりです。

  • エンティティ粒度のモジュールにふさわしくない特定のユースケースや特定の画面に依存した名前のメソッドが散在してしまう。(ユースケースに依存しない名前を振る方法も考えられるが、名が体を表さないメソッドを量産してしまうので混乱が生じる。)
    • 特定のユースケース名のメソッドがServiceクラスに配置されるようになると、ロジックの重複が発生しやすくなる。(重複対策としてエンティティ単位のServiceを導入したはずなのに。。。)
    • 責務を超えたメソッドが配置されるため、特定のサービスが肥大化する。(マッチング系アプリだとこの傾向が顕著に現れるはず。)しかも、多人数で特定のファイルを編集しなければならないので作業効率が悪化する。


上記の問題に対処するために、以下のアーキテクチャはいかがでしょうか。


図2: 1.5階層のAction-Service-Logicパターン


先ほどのアーキテクチャとの違いは、ActionからServiceを経由せずに直接JdbcManagerを経由したデータアクセスを想定している点です。ちょっとした違いですが、先ほどの問題点は全て解消されるはずです。


デメリットとその対処方にも触れておきます。
ユースケース固有の要求は、Action単独で処理するやり方とServiceを利用して処理するやり方の2通りになってしまうことで、選択肢が増えてしまうのがデメリットです。ただ、このデメリットもActionクラスにテストコードを用意しておけば、リファクタリングは容易なので、「完璧な使い分け方針決めなければ」と気構えずに、最初にゆるい方針を決めて、プロジェクト途中で方針を明確化してゆくやり方が割と現実的な感じがします。

また、Actionにデータアクセス処理を配置すると、データアクセス処理部分をテストやメンテナンス性を考慮して、メソッド抽出したくなるかも知れません。この場合、メソッドの入力要素がメソッド引数とフィールドに分散してしまうのでメソッドのインターフェースが曖昧になりがちなデメリットが想定されます。これについては、Action内でテストすると決めたメソッド抽出したメソッドに対しては、フィールドアクセスは使わずに、全てメソッド引数を使うようにすれば、メソッドのインターフェースは自然と明確になると思います。


追記:
このアーキテクチャは、画面項目プロパティをアクションフォームに配置する方法を想定しています。Actionクラスのデータアクセス処理するメソッドへの引数は、アクションフォームをそのまま渡す、もしくは、アクションフォーム内で定義した内部クラスを渡すことを想定しています。


Action-Service-Logic の3つを使うにもかかわらず、階層数が ではなく1〜2というのがオモローですね。今後、名前がないと対話しずらくなるので、ひとまず『1.5階層のAction-Service-Logicパターン』命名しておきますね。

Action-Service-Logic の3階層は冗長か?

エンティティ固有のドメインロジックは別出しにします。
ひがさんが最近呼んでる「Service」に近いです。
でも、みなさん Action-Service-Logic の3階層は冗長ってお考えなんですね。


そうでもないですよ。今、私が携わらせて頂いている案件では、

 Action : コントローラ
 Service: ユースケース単位のロジック + データアクセス
 Logic : エンティティ単位のロジック + データアクセス 

 ※ LogicはActionとServiceの両方から呼び出し可能とする。

の方式に数人の中核のメンバーから納得感を得ています。
無難な選択肢だと思います。


一見冗長に思えますが、「ユースケース単位のロジック」と「エンティティ単位のロジック」を1種類のクラスに寄せる方式はどこかで無理が生じてしまいます。そう考えると、この方式は自然な発想ではないでしょうか。


この方式のポイントは、LogicをActionからも呼出し可にするかどうかです。階層的なアーキテクチャを重視すると、呼び出し順が Action → Service → Logic となり、設計的には美しい感じがします。しかし、Actionから直接「エンティティ単位のロジック + データアクセス」を呼び出したくなるケースでも、わざわざLogicをService経由で呼び出さなければならないので冗長になってしまいます。そうならないためには、階層を取っ払ってLogicはServiceとActionの両方から呼び出し可能にする方が良いと思います。そう考えると、この方式はAction-Service-Logicの3階層とAction-Logicの2階層、Action-Serviceの2階層が共存するアーキテクチャとなります。階層を飛び越した呼出しを許さない常に3階層のアーキテクチャと区別するために、以下ではこのアーキテクチャを2〜3階層の中間をとって『2.5階層のAction-Service-Logicパターン』と呼ぶことにします。


現在、この『2.5階層のAction-Service-Logicパターン』と『1.5階層のAction-Serviceパターン』のどちらが良いかを思案中です。1.5階層のAction-Serviceパターンの基本構成は次のとおりです。

 Action : コントローラ + ユースケース単位のロジック + データアクセス
 Service: エンティティ単位のロジック + データアクセス

1.5階層のAction-Serviceパターンの詳細については、別のエントリで紹介します。

Entity単位のServiceに共通の親クラスを持たせる

どうにかして型パラメータからAbstractServiceのclazzへセットしたかったのですが、やり方わからず。。。

ジェネリックの部分があまりスマートなやり方じゃない気がしていますが、ひとまず以下のやり方だとOKです。


まずはジェネリックな共通親サービスクラスです。

public abstract class AbstractService<E> {
    // JDBCマネージャー
    public JdbcManager jdbcManager;

    // Entityのクラス
    protected Class<E> clazz;
    
    @SuppressWarnings("unchecked")
    public AbstractService() { 
        Type type = this.getClass().getSuperclass().getGenericSuperclass();
        Type[] arrays = GenericUtil.getGenericParameter(type);
        clazz = (Class<E>)GenericUtil.getRawClass(arrays[0]);    
    }

    @SuppressWarnings("unchecked")
    public E find(Integer id) {
        return (E) jdbcManager.from(clazz).id(id).getSingleResult();
    }
    
    @SuppressWarnings("unchecked")
    public List<E> findAll() {
        return (List<E>) jdbcManager.from(clazz).getResultList();
    }

    public int delete(Integer id) {
        E entity = (E)find(id);
        if (entity == null) {
            return 0;
        }
        return jdbcManager.delete(entity).execute();
    }

    ...

}

ポイントは、コンストラクタ内で AbstractService における E のクラス型を取得して clazzへセットしている箇所です。


次にエンティティ単位のサービスクラスです。

public class EmployeeService extends AbstractService<Employee>{
   /**
    * ここに個別のメソッドを定義
    */

}

EmployeeエンティティをAbstractServiceのジェネリックパラメータに指定しているだけのスッキリした構成です。


サービスを呼び出し側のコードはこんな感じです。

@Resource
protected EmployeeService employeeService;

・・・

List<Employee> list = employeeService.findAll();

Employee employee= employeeService.find(1);

int result = employeeService.delete(1);


SAStruts + S2JDBCで『Serviceの粒度 = Entity単位』で実装する場合は、このやり方だとかなり良い感じですね。