ログインユーザごとに動作の異なるサービスをどうやって実装するか
Springを何度か実戦投入してみたが、まだどうやって使うのが正しいのか分からないところがある。
例えば「ログインユーザごとに動作の異なるサービス」をどうやって実現するか。
同じサービスであっても、誰が実行するかによって実際に行われることが異なる、というのは普通にあること。
それをDIではどうやって捌くのがよいか、というのが知りたい。
お題
例:
購買の申請画面で「申請」ボタンを押すと
- 平社員なら「権限がありません」エラーになる
- 申請担当者なら、支払申請をDBに書き、上長に承認依頼メールを飛ばす
- 各部門長なら、
- 100万円以下なら支払承認をDBに書く
- 100万円以上なら支払申請をDBに書き、経理部長に承認依頼メールを飛ばす
- 経理部長なら、金額がいくらでも支払承認をDBに書く
これをSpring管理下のサービスとして実装する場合、どうするか。
使う道具は以下の通り:
購買申請サービスインターフェース:
public interface KoubaiService { // 購買申請実行 public void shinsei(Object param); }
ログインユーザを表すインターフェース:
public interface User { // 申請担当者ならtrueを返す public boolean isShinseiTanto(); // 部長ならtrueを返す public boolean isBucho(); // 経理部長ならtrueを返す public boolean isKeiriBucho(); }
あと、Struts + Springで実装するということにする。
やりたいこと
例えば、申請実行の Struts Action の中で、こんなことはしたくない:
User user = request.getSession().getAttribute("user"); KoubaiService service; // 購買サービス if (user.isKeiriBucho()){ // ユーザが経理部長 service = beanFactory.getBean("購買サービス_経理部長"); } else if (user.isBucho()){ // ユーザが部門長 service = beanFactory.getBean("購買サービス_部門長"); } else if (user.isShinseiTanto()){ // ユーザが申請担当者 service = beanFactory.getBean("購買サービス_申請担当"); } else { // ユーザが平社員 throw new XXXXException("権限がありません"); } // 申請実行 service.shinsei(param);
当然、こうしたい:
// ↓実装型はログインユーザが誰なのかによって切り替わる。同じ"購買サービス"というキーを渡されていても KoubaiService service = beanFactory.getBean("購買サービス"); service.shinsei(param);
Spring にはアプリケーションの権限レベル実装(ユーザには「申請担当」「部門長」「経理部長」「その他」の4種類がある、とか)
なんて分からないから、Spring 自体が上記の「こうしたい」を実現してくれるわけがない。
しかし「同じキーを渡しても、ログインユーザによって違うBeanを返してほしい」というニーズはある。
では、落としどころはどこか。
これはダメな解
以前作ったシステムでは、アプリケーションのセッションを表す SessionService というサービスを作って、サービス層の各オブジェクトの中でユーザごとに異なる情報を参照できるようにしてみたが、これはあんまり良い解ではない。
その仕組みを使って、上記のお題を解くと、KoubaiService の実装はこんなコードになってしまう。
public class KoubaiServiceImpl implements KoubaiService { // この中にログイン中の全ユーザのUserオブジェクトが収容されている SessionSerivce sessionSerivce; public void sinsei(Object param) { /* * 単一の実装の内部で激しくif分岐して、全権限の動作をサポートする。 */ User user = sessinService.getUser(); // 現在のスレッドに関連付けられたUserオブジェクトを返す if (user.isKeiriBucho()){ // ユーザが経理部長 (承認実行のコード) } else if (user.isBucho()){ // ユーザが部門長 if (金額 <= 100万円) { (承認実行のコード) } else { (経理部長に承認依頼メールを飛ばすコード) } } else if (user.isShinseiTanto()){ // ユーザが申請担当者 (上長に承認依頼メールを飛ばすコード) } else { // ユーザが平社員 (権限がありませんエラー) } } }
Open-Closed原則違反てやつですか。これはダメだ。
正解は、アプリケーション(この場合は Struts の Action クラス)はフレームワークが提供する BeanFactory をナマで参照するのではなく、
アプリケーション独自の権限実装を理解するBeanFactoryラッパーを作って、それ経由でサービスを取り出すことなのかな。
世の中の人たちはどうしているのだろう?
Spring とか Seasar でこの問題を解いているサンプルアプリをご存知の方、ぜひご教示願います。