SAStruts/サービス層を分割する

SAStruts/サービス層を分割する

サービス層・・・設計視点だとロジック層と言うのだろうか? その分割に関して

なぜ分割したいか?

SAStruts + S2JDBC の標準仕様(Doltengプロジェクトで作る)ではレイヤーとして

  • View担当でのJSP群
  • controller担当のActionクラス群
  • model担当のserviceとentityの組み合わせ

のような形になるが実際使ってみると、S2JDBC-GENの自動生成と標準のイロイロ機能してくれるスーパークラスの存在で、事実上serviceとDBのテーブルの繋がりが結構強くなってしまっている。

このような場合、業務上はひとまとまりの処理なのだけど、複数のテーブルにまたがる操作をどこに書いていいのかわからなくなる。

どこかのServiceクラスに操作を寄せて書いた場合、当然他のServiceを使って操作する部分も出てくるので、他ServiceをDIコンテナからセットしてもらうことになる。 こうなるとService同士に主従関係ができてしまって、もし逆方向の関係が使いたい場合DIコンテナからのセットが無限ループになるとか、使い勝手も悪くなる。

じゃあそれはActionに寄せればいいだろという話もあるが、対応デバイスごとにActionを用意したい場合もあるだろう。 ここで同じ処理なのに同じようなActionを沢山書かないといけない。

そもそもActionはテストしにくい。

なのでServiceの機能を業務に近い部分と、DBのテーブル毎の操作に分割したいというわけだ。

余談だが、よくRailsはこの業務ロジックの層を設けずにやれるなという疑問がある。 それは、Railsの流儀として「モデル」=「画面」のように必要な値とテーブルが一直線になるように業務仕様側を実装側に合わせる思想だからである。なので複雑な結合や1個の処理で複雑なDB操作が発生しないことになる。

これはいいとか悪いとかあるべき姿とかそういう話ではない。単にそういう思想なのだ。

Logic の導入とその担当分け

Service に加え Logic というものを導入する。なぜ Logic かは S2Container が標準で Service と同等のDI設定のクラスをすでに作ってくれているからだ。

Serviceの担当

Serviceは今までどおりの活かしでテーブルに対する操作を記述する。 主に、担当entityのinsert, update, 担当entityを主にreturnするfindBy〜, 担当entityのリストを主にreturnするfindAllBy〜を実装するということだ。

特別なこと(非常に独立性の高い付加情報的entityの操作等)が無い限り他のServiceを自Service内では使わないこととする。 これで DI の循環を気にせず、さらに実装位置も迷う必要がなくなる。

Logic の担当

Logic には業務処理のひとかたまりの操作を記述する。

Logic 分割したクラス内部では当然データベース操作も行うだろうから、適宜、何種類でもServiceをDIコンテナにセットしてもらえばいい。 業務を分析した上で Logic を分割しているのだから、Logic 同士を呼び合う必要は無いのでLogic内部で別 Logic をセットしてもらうことはありえない。

テーブルには無いが完全に業務寄りの集計関数等の処理を使ったDBアクセスは Logic 内部に直接 jdbcManager を使って記述してもよいと思う。

そして Logic は Action に呼び出されて使われる。

業務上アトミック

業務の分析とデータ設計、意味をちゃんと考えてないとLogicの塊を作るのに困る。

Serviceのメソッドが細切れの最小限のDB操作の担当ならば、 業務上アトミックな処理を凝集するのがLogicの担当といえる。

ユーザー視点での業務整理

5W1H・・・

  • いつ
  • 何を
  • どこで
  • なぜ
  • どのように

SAStruts実装のシステム視点で見ると

  • ユーザーが
  • 今(ブラウザで操作した瞬間)
  • 「何を」
  • 仕事場、とか家で
  • 「なぜ」
  • ブラウザの操作 → 実装通り

ということになり、「何を」と「なぜ」以外はほぼ決まっている。そして「なぜ」は業務分析時点で終わっていて、設計方針には影響するが実装には影響しない。だから「何を」がLogic分割のキーになる。

「何を」の主語が「ユーザー」。ユーザー視点方向で「何を」に名前が付けられればそれがLogicになりそうだ。

メールのようなメッセージのやりとりをするシステムを考えてみる。 データ視点で考えるとメッセージやりとりなのだからメッセージ自体を操作するMessageServiceを作ってそれにバンバン実装すればよいように感じる。

しかしユーザー視点で考えるとメッセージ操作には2種類あって、送る側と受け取る側に別れる。 システムにおいても画面は送る画面と受け取る画面でわけることが普通だろう。

ということで、SendMessageLogicとRecieveMessageLogicの2種類に分割して実装すればうまく整理できそうだ。

Actionの担当

Actionの担当は今までとあまり変わらないのだが、Logicが増えるのでDIコンテナにセットしてもらう対象の種類が増えるぐらいだ。 entityを単に表示するだけという画面も多いだろうからserviceは今までどおりセットして使えばいい。

実際には業務上の処理はLogicに凝集されているので、Actionの役割はURLに対するマッピング設定、モデルへ値の要求、Viewの選択と結果受け渡しというコントローラー部分が色濃くなる。 formから値を取り出して、Logicのメソッドを呼び出して、表示するJSPをreturnするという3ステップで終了である。

ポータル画面のような業務上関係ない情報同士が意味もなく並んでいるような画面を表示するアクションでは複数のLogicを呼び出すこともあるだろう。

Logicの導入方法

能書きが長くなってしまったので。Logic用のcreatorクラスがもうすでにあるのでこいつをOnにするだけ

creator.diconに

<component class="org.seasar.framework.container.creator.LogicCreator"/>

を記述しLogicCreatorの使用をONにする

インスタンス生成時のいろんな処理をcustomizer.diconにLogic用の記述を追記する。 特に何もなくService用に書かれている記述をコピペして終了

  <component name="logicCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain">
    <initMethod name="addCustomizer">
      <arg>traceCustomizer</arg>
    </initMethod>
    <initMethod name="addCustomizer">
      <arg>
        <component class="org.seasar.framework.container.customizer.TxAttributeCustomizer"/>
      </arg>
    </initMethod>
  </component>

以上完了。

関連ページ

タグ

java/sastruts/divide_service_layer.txt · 最終更新: 2017-09-26 18:34 by ore