web-dev-qa-db-ja.com

「パススルー(神)サービス」の利用は悪いですね。

私のチームは、アプリケーションに新しいサービスレイヤーを開発しました。彼らは、インターフェースを実装する一連のサービスを作成しました(例:ICustomerServiceIUserServiceなど)。これまでのところ、かなり良いです。

ここで少し奇妙なことが起こります。次のような「CoreService」というクラスがあります。

// ICoreService interface implements the interfaces of 
// all services in the system 100+ 
public class CoreService : ICoreService 
{
    // I don't like these lazy instance variables. I think they are pointless
    private readonly Lazy<CustomerService> _customerService; 
    private readonly Lazy<UserService> _userService;

    public CoreService()
    {
        // These violate the Dependency inversion principle. 
        // It also news up its dependencies, which is bad.
        _customerService = new CustomerService();
        _userService = new UserService();

        // And so forth
    }

    #region ICustomerService
    public long? GetCustomerCount()
    {
        return _customerService.GetCustomerCount();
    }
    #endregion

    #region IUserService
    public User GetUser(int userId, int customerId)
    {
        return _userService.GetUser(userId, customerId);
    }
    #endregion

    // ...
    // And 100 other regions for all services
}

チームの推論では、使用するアプリケーションのコントローラーはCoreServiceを簡単にインスタンス化してそのサービスを使用でき、すべてが「レイジー」であるため、パフォーマンスの問題は発生しません。

私はこれが悪いデザインであることを説明しようとしました:

  1. すべての依存関係とその依存関係を遅延してインスタンス化することにより、依存関係の逆転の原則に違反しています。
  2. #1の結果として、私たちはサービスのテスト容易性を排除しています。サービスの依存関係のモックを作成し、ユニットテストのために挿入することはできなくなりました。
  3. CoreServiceは、「神オブジェクト」のアンチパターンのように思えます。
  4. コントローラーでは何もインスタンス化しないでください。コントローラーに必要な依存関係を挿入するだけです。 (たとえば、CustomerControllerに5つの異なるサービスが必要な場合は、コンストラクタを介してそれらを挿入するだけです!)

私の議論は有効ですか?ここで見逃しているベストプラクティスの他の違反はありますか?ここでどんな入力も高く評価されます。

編集:この質問は重複としてマークされているため、タイトルを更新しました。このサービスは必ずしも神のオブジェクトではなく、実際には「パススルー」またはFacadeサービスです。間違いをお詫びします。

45
Vin Shahrdar

これは 神のオブジェクト ではありません。

ここにはたくさんあるからなのかもしれませんが、ある意味では何もしていません。ここには動作コードはありません。これは全能の全能の神ではありません。すべてを見つけるだけです。それは真のオブジェクトではなく、データ構造です。

このパターンには、より適切な名前があります: Service LocatorDependency Injection と非常に対照的ですファウラー パターン。

あなたの証言不満は有効ですが、問題はそれよりも大きいです。ここで違反している主な原則は Interface Segregation 原則です。

その理由は次のとおりです。 Dependency Inversion の問題をリファクタリングする目的でこの問題に遭遇した場合、CoreServiceのハードコーディングを停止します。渡さなければならないパラメーターに置き換えます。なぜそれが本当に役に立たないのか分かりますか?

CoreServiceが何でもすべてにアクセスできるため、オブジェクトにCoreServiceが必要であることを知ると、オブジェクトにCoreServiceがアクセスできるため、依存関係の問題は実際には解決されません。依存関係を外部化して、ニーズを明確にします。

インターフェース分離の原則に従うと、現時点でCoreServiceを必要とするオブジェクトは、提供する100の情報のうち5つを実際に必要とすることがわかります。これら5つのものが必要とするのは、わかりやすい名前です。

その名前を見ると、これが何を必要としているかがわかります。私はそれらの5つのものを提供する方法を見つけることができます。これらの5つのものを提供するには、2つまたは42の方法があります。それらの方法の1つはテストを介したものかもしれませんが、このアイデアはテスト以上のものです。テストするだけで、この問題は非常に明白になります。

その名前を提供し、5つのパラメーターを持つコンストラクターを回避するには、 パラメーターオブジェクトを導入refactoring.comc2.com これらの5つの事柄が、良い名前で1つの一貫したアイデアを表すことを条件とします。 (2つの名前を必要とする2つのアイデアがこれらの5つのものに隠されている可能性があります。問題がない場合は、分割して名前を付けます)。

このパラメータオブジェクトを必要とする別のオブジェクトを見つけた場合、このパラメータオブジェクトを再利用したくなるかもしれません。しかし、オブジェクトには他にも何かが必要だとしましょう。したがって、その何かをパラメーターオブジェクトに追加します。最初のオブジェクトはこれを他に必要としませんが。さて、再びサービスロケータを作成する準備が整いました。必要のないものを受け入れないことでこれを止めます。それが、インターフェース分離の原則です。

ここに隠れている別のパターンは Singleton のようですc2.com。 Dependency Injectionには、これに代わる素晴らしい選択肢があります。 mainで必要なものを一度ビルドし、ビルドしたものへの参照を、それを必要とするすべてのものに渡すだけです。長期間有効なオブジェクトのグラフを作成したら、そのうちの1つで1つのメソッドを呼び出して、全体の動作を開始します。ただ おかしくならないようにしてください作成コードと動作コードを分離する を維持する限り、これをファクトリーやその他の 作成パターン で分割しても問題ありません。

他の人を説得するには、この問題を修正する方法を示す必要があります。彼らのニーズを表現するものをいくつか作り、それらのニーズを満たすコードを書いてください。適切な名前の単純なファクトリーとそれが住む独自のファイルは、多くの場合これを実現します。あなたが彼らのものの残りを見つける必要性で立ち往生しているならば、工場がその問題に対処するようにしましょう。これで、オブジェクト(クラスファイルを含む)は無意味にそのナンセンスを認識できなくなり、残りの部分を修正するときに気になりません。

また、その依存関係を知らせますが、これは悪いことです。

それは少し単純化しています。それらをオーバーライドする方法を提供しない場合、クライアントオブジェクト内で「依存関係を新しくする」ことは悪いことです。 C#を使用しているようです。 C#には名前付き引数があります !つまり、オプションの引数で依存関係を満たすことができます。そのための適切なデフォルト値があることを確認してください。適切なデフォルト値を慎重に選択する必要があります。人を驚かせるものは使わないでください。

また、@ JimmyJamesが指摘しているように、すべてを頭を使わずに遅延ロードすることは理想からほど遠いものです。実際にはスピードアップしません。あなたがそれを支払うときだけ、それは変わります。それはあなたがそれの代金を払うときに予測不可能になり、構成の問題を診断するのを難しくする可能性があります。それは本質的にキャッシュの問題です。 (物事に良い名前を付けた後)コンピュータサイエンスで最も困難な問題の1つとして広く認識されています。むやみに使わないでください。

49
candied_orange

私たちは、実際の神の奉仕のような何か他のものを持っています。

バックエンド全体は、15個程度のメソッドの単一のインターフェースによってフロントエンドからアクセスされます。これらのメソッドはすべて、Streamを引数として取ります(実際には、別のプロセスにリモート処理しているため)。現在、ストリームAPIはコードを作成するのがばかげているので、1回だけ行いました。サービスオブジェクトは、同じ15のメソッドと、引数を変換して他の1つを呼び出すためにいくつかの追加のメソッドを持つこの単一のオブジェクトです。サービスオブジェクトは、バックエンドインターフェースと、ログインしたユーザーを表すオブジェクトのインターフェースから自由に構築されます。

神の奉仕はすべてを行うように見えます。実際には、約500行で、要求のシリアライズと応答のデシリアライズを行います。メソッドの設計哲学は、1つのメソッド呼び出しが1つのトランザクションであり、クライアントは、保存するオブジェクトのIEnumerableを渡すことにより、任意の大きな保存トランザクションを構成できます。

APIには、実際には次のようないくつかの実装があります。  単体テスト用の3つのモック。 God Serviceは1つの実装のみを持ち、インターフェースを実装していません。

これらの個別のサービスをすべて持つという考えはばかげていると思います。それらすべてを作成して注入することは、さらにばかげています。少なくともこの神の奉仕は彼らをすべて怠惰にするので、対処するのははるかに簡単です。

設定レベルで一度に1つずつサービスを交換するのが良いアイデアである良いデザインを見たことがありません。非常に少数の有効な構成の1つにそれらを巨大なチャンク(または可能性としては一度にすべて)に交換したかったので、このGod Service設計にはほとんどマイナス面がありません。単一の構成エントリに基づいて、すべてのコンポーネントサービスのどの実装をロードするかを処理するように簡単に調整できます。不自然なキメラ構成はもう存在できないため、サポートする頭痛が少なくなります。

2
Joshua

簡単に言えば、私はあなたの問題を理解したように...あなたは多くの内部サービスを持っており、それらのすべての内部サービスとの対話/呼び出しに使用されている別の外部サービスがあります。

オニオンアーキテクチャの世界では、内部サービスドメインサービスと外部サービスをアプリケーションサービスと呼びます。下の写真に示すように、

enter image description here

単一の外部サービスからすべての内部サービスを呼び出すことは常に悪い習慣です。

取るべきコンセプトは、external\applicationサービスは、論理的に意味のある限り問題なく、すべてのダンプステーションだけではありません。

ドメインサービスが相互に依存している場合は、適切に名前を付けた3番目のサービスにそれらを抽出し、共有メソッドを配置できます。

0
Mathematics