9

一般的な MVC 設計のアプリケーションでは、サービス層をユーザー セッションに依存させるのは悪い考えですか? データベースからいくつかのオブジェクトをフェッチするサービス メソッドがあり、誰が呼び出しを初期化したかに応じて異なる結果を返したいとします。たとえば、管理者は 10 行のオブジェクトを取得し、通常のユーザーは 7 行しか取得しない場合があります。最後の 3 つは「管理者専用」オブジェクトだったためです。これを解決するいくつかの方法は次のとおりです。

  • 呼び出し元のユーザーを含める新しいメソッド パラメーターを導入します。依存関係はありませんが、多くのメソッドでユーザー パラメーターを投入する必要があるのは面倒です。
  • 異なるユーザー ロールに対して異なるメソッドを作成します (複数の結果が得られます)。また、依存関係はありませんが、基本的に同じことを行う多くのメソッドがあり、コードの重複のリスクが高くなります。
  • メソッドが、現在のユーザー セッションを格納する静的コンテキストの ThreadLocal 変数から読み取れるようにします。この変数は、各リクエストの前に設定されます。

最近、私は最後の方法をますます使い始めました。なぜなら、それはきれいなインターフェースを提供し、作業するのが非常に実用的だからです。Filter は、現在のスレッドに常にユーザー セットがあることを確認します。これはデザインが悪いのでしょうか?これをサービス層から Web 層への依存関係と見なす人もいると思いますが、個人的にはそれらはかなり切り離されていると思います。最大の結果は、別のクラスの状態に応じてメソッドの動作が異なることです。これは、良いことと悪いことの両方になる可能性があります。

これについてどう思いますか?それが悪い解決策だとしたら、より強力な解決策は何でしょうか?

4

4 に答える 4

5

ThreadLocal スタイルのアプローチに反対することを強くお勧めします。「グローバル変数」の設計臭が強すぎるようです。これには多くの問題があり、最も顕著なものは次のとおりです。

  • テストの前にセットアップする暗黙的なグローバル状態がますます多くなるという滑りやすい坂道を下るにつれて、コードのテストが難しくなります
  • 特定のコードがどのパラメーターを使用しているかを明確に確認できなくなります。これは、メンテナーにとって、または数か月後にコードに戻った場合に非常に混乱する可能性があります。
  • 作業を別のスレッドに引き渡すと、非常に厄介なバグや複雑さが生じる可能性があります。実行中のスレッドに依存するコード実行を効果的に作成しました..何がうまくいかない可能性がありますか? :-)
  • 循環依存関係を作成しています(サービス層 <-> ユーザー インターフェイス層)。決して良い考えではありません。依存関係を一方向のみに流れるようにする必要があります (ほとんどの場合、ユーザー インターフェイス レイヤー -> サービス レイヤー)。

他の 2 つのアプローチの間では、「場合による」と思います。

  • ユーザーが本質的にデータ モデルの一部である場合 (たとえば、ソーシャル グラフ データベースがある場合)、ユーザーをパラメーターとして渡すのは自然なことのように思われます。
  • ユーザーデータが認証などのフロントエンドにのみ使用される場合、特定のユーザーの詳細への依存を最小限に抑え、代わりにさまざまな役割に対してさまざまなメソッドを作成する傾向があります (または同等に「役割」パラメーターを追加します)。

別のオプションは、一連の関連するセッション データ (つまり、ユーザー名以上のもの) を含む "コンテキスト" オブジェクトをサービス層に渡すことです。パラメータの肥大化を最小限に抑えたい場合、これは理にかなっています。良いレイヤリングの原則を「回避」する方法になることに注意してください. それが単なる「データ」であれば、おそらく問題ありませんが、人々がコンテキストでコールバック オブジェクト/UI コンポーネントを渡し始めるとすぐに、おそらく少し混乱することになります....

于 2012-09-03T11:50:19.573 に答える
3

Java EE の役割メカニズムを使用するだけで、それは既に存在します。

例として:

@Stateless
public class AService {

    @Resource
    private SessionContext sessionContext;

    @PersistenceContext
    private EntityManager entityManager;

    public List<AObject> fetchAFewObjects() {

        String query = "aUserQuery";      

        if (sessionContext.isCallerInRole("ADMIN")
            query = "aAdminQuery";

        return entityManager.createNamedQuery(query, AObject.class)
            .getResultList();    
    }  
}

Web 側では、コンテナー ログインを行っていることを確認して、サーバーがログイン ユーザーを認識できるようにします。

于 2012-09-04T09:37:34.437 に答える
2

ユースケースにどちらのソリューションも実装する必要はありません。

EJBでは、必要なものはすでにフレームワークによってサポートされています。これはセキュリティコンテキストと呼ばれ、呼び出すすべてのメソッドに自動的に伝播します(舞台裏では、実際にTLSによって実装されることがよくありますが、これは実装の詳細です)。

このセキュリティコンテキストにより、ユーザー名とその役割にアクセスできます。あなたの場合、あなたが必要と思われるのは役割をチェックすることだけです。

これは、メソッドにアノテーション(@RolesAllowed)を使用するか、EJBセッションコンテキストを挿入して現在のユーザーのロールを要求することにより、宣言的に行うことができます。

于 2012-09-04T09:01:30.180 に答える
1

アーキテクチャの観点からは、最後のオプションが唯一の正気のオプションだと思います。

  1. セキュリティ上のリスクがあります。セッション中のユーザー情報に対して(または他のセキュリティメカニズムを介して)ユーザー名を検証する必要があり、これが持つ可能性のある利点を効果的に無効にします(ユーザーの名前/役割をすでに知っている必要があるため)。

  2. 実際には実用的でもスケーラブルでもありません。より多くの情報 (単なるレコードではなく) を必要とする別のロールを追加する必要があるとします。次に、既存の 2 つのユーザー ロール用のメソッドと、別のシナリオを処理するための異なる署名とif-if-ifロジックを使用して、新しいロール用の別のセットを用意します。それは面倒で、非常に速くなる傾向があり、深刻なメンテナンスの問題につながります.

Java の Web フレームワークを調べると、最後のアプローチを使用していることに気付くでしょう。また、ユーザー情報をより「抽象的な」エンティティ (ID、コンテキストなど) に入れ、情報の階層化を扱う際にユーザー名の代わりにロールを使用することを提唱しています。このようにして、異なる情報ビューを必要とする複数の役割が存在する大規模なユーザー ベースに存在する複雑さを管理するのがはるかに簡単になります。ユーザー プロファイルに役割を追加するだけです。

参考として、Seam の実装を参照してください。 /seam/contexts/Contexts.java#Contexts .

コメントによる更新には十分なスペースがありません

以下のあなたの賞賛から、サービスとUIレイヤーを結合しているようです。これは、外部エンティティがサービス層を消費することを決して期待しない場合にのみ発生する可能性があります。それは問題ないかもしれませんが、シナリオによって異なります。

それとは別に、私はミケラの意見に同意しますが、ある点だけです。ThreadLocals は、静的な値がグローバルであるという意味でグローバルではありません。サービス層にとって、ユーザーの情報は確かにグローバルであり、標準的なビジネス情報から分離することは理にかなっています。また、保存する必要がある他のそのような情報がある場合があることも覚えておいてください。ユーザーの国、言語、または通貨が必要な場合はどうしますか?

ThreadLocal効果的でパフォーマンスの高いソリューションを提供します。完璧じゃない?確かにそうではありませんが、正しく実装されていれば、他のソリューションよりもややクリーンです。とはいえ、適切で誰にでもできる実装を構築するにはいくらかの努力が必要ですが、それはほぼすべてのことについて言えることです。

于 2012-09-03T11:57:28.883 に答える