web-dev-qa-db-ja.com

コントローラがサービスの代わりにリポジトリを呼び出すのは悪い習慣ですか?

コントローラがサービスの代わりにリポジトリを呼び出すのは悪い習慣ですか?

詳細を説明するには:

良い設計では、コントローラーはサービスとサービス使用リポジトリを呼び出します。

ただし、コントローラーでは、ロジックを必要としない場合があり、dbからフェッチしてそれを表示に渡す必要があるだけです。

そして、私はリポジトリを呼び出すだけでそれを行うことができます-サービスを呼び出す必要はありません-それは悪い習慣ですか?

53
mohsenJsh

いいえ、このように考えてください:リポジトリis aサービス(また)。

リポジトリを介して取得するエンティティがビジネスロジックのほとんどを処理する場合、他のサービスは必要ありません。リポジトリがあれば十分です。

エンティティを操作するために通過する必要があるサービスがある場合でも、最初にリポジトリからエンティティを取得してから、それを上記のサービスに渡します。試行する前にHTTP 404を放棄できることは非常に便利です。

また、読み取りシナリオでは、DTO/ViewModelに投影するエンティティが必要なのが一般的です。その間にサービスレイヤーがあると、多くの場合、パススルーメソッドが多くなり、見苦しくなります。

39
Joppe

コントローラがリポジトリを直接呼び出すことは悪い習慣ではありません。 「サービス」は単なる別のツールであるため、意味のある場所で使用してください。

NikolaiDanteがコメントしました:

...適切なアプリケーションに適したパターンを選択してください。私が言うことは、アプリケーションを一貫させる必要があるということです。

一貫性が最も重要な側面だとは思いません。 「サービス」クラスは、コントローラがそれを実装する必要がないように、いくつかの高レベルのロジックをカプセル化することを意図しています。特定の操作に「高レベルのロジック」が必要ない場合は、リポジトリに直接移動してください。

懸念の分離とテスト容易性を促進するには、リポジトリを、コンストラクタを介してサービスに注入する依存関係にする必要があります。

IFooRepository repository = new FooRepository();
FooService service = new FooService(repository);

service.DoSomething(...);

データベース内のレコードを検索するために何らかのパラメーター化されたクエリが必要な場合、サービスクラスはビューモデルを取得して、リポジトリによって実行されるクエリを構築するのに適した場所です。

同様に、フォームの複雑なビューモデルがある場合、サービスクラスは、ドメインモデル/エンティティのメソッドを呼び出してレコードを作成、更新、削除するロジックをカプセル化し、リポジトリを使用してそれらを永続化できます。

コントローラーがIDでレコードを取得する必要がある場合は、反対方向に進み、サービスオブジェクトに委任することは、大ハンマーで画鋲を打つようなものです-必要以上のことです。

コントローラーがトランザクションを処理するのに最適な位置にあること、または 作業単位オブジェクト であることがわかりました。次に、コントローラーまたは作業単位オブジェクトは、複雑な操作の場合はサービスオブジェクトに委任するか、単純な操作(IDによるレコードの検索など)の場合は直接リポジトリーに移動します。

public class ShoppingCartsController : Controller
{
    [HttpPost]
    public ActionResult Edit(int id, ShoppingCartForm model)
    {
        // Controller initiates a database session and transaction
        using (IStoreContext store = new StoreContext())
        {
            // Controller goes directly to a repository to find a record by Id
            ShoppingCart cart = store.ShoppingCarts.Find(id);

            // Controller creates the service, and passes the repository and/or
            // the current transaction
            ShoppingCartService service = new ShoppingCartService(store.ShoppingCarts);

            if (cart == null)
                return HttpNotFound();

            if (ModelState.IsValid)
            {
                // Controller delegates to a service object to manipulate the
                // Domain Model (ShoppingCart)
                service.UpdateShoppingCart(model, cart);

                // Controller decides to commit changes
                store.SaveChanges();

                return RedirectToAction("Index", "Home");
            }
            else
            {
                return View(model);
            }
        }
    }
}

サービスの組み合わせとリポジトリの直接操作は完全に許容できると思います。必要に応じて、トランザクションを作業単位オブジェクトにさらにカプセル化できます。

責任の内訳は次のようになります。

  • コントローラはアプリケーションのフローを制御します
    • ショッピングカートがデータベースにない場合、「404 Not Found」を返します
    • 検証が失敗した場合、検証メッセージを含むフォームを再レンダリングします
    • すべてがチェックアウトされた場合にショッピングカートを保存します
  • コントローラーはサービスクラスに委任し、ドメインモデル(またはエンティティ)でビジネスロジックを実行します。 サービスオブジェクトは実装されないビジネスロジックを実装しないビジネスロジック!それらはexecuteビジネスロジック。
  • コントローラは簡単な操作のためにリポジトリに直接委任することができます
  • サービスオブジェクトはビューモデルのデータを受け取り、ドメインモデルに委任してビジネスロジックを実行します(たとえば、サービスオブジェクトはリポジトリのメソッドを呼び出す前にドメインモデルのメソッドを呼び出します)。
  • データの永続化のためにサービスオブジェクトをリポジトリに委任
  • コントローラは次のいずれかである必要があります。
    1. トランザクションの有効期間を管理する、または
    2. トランザクションの存続期間を管理する作業単位オブジェクトを作成する
6
Greg Burghardt

それはあなたのアーキテクチャに依存します。私はSpringを使用しており、トランザクション性は常にサービスによって管理されています。

書き込み操作(または単にリポジトリに委任するロジックのない単純なサービス)のためにリポジトリを直接呼び出す場合は、1つの操作で複数のデータベーストランザクションを実行する必要があります。それはあなたのデータベースの矛盾したデータにつながります。原則として、データベース操作は機能するか、失敗するはずですが、半機能の操作は頭痛の原因です。

そのため、コントローラから直接リポジトリを呼び出すことや、単純な委任サービスを使用することは悪い習慣だと思います。あなたは読み取りのためだけにそれを始めます、そしてすぐにあなた、あるいはあなたの仲間は書き込み操作のためにそれを始めます。

2
Rober2D2

はい、それは悪い習慣です。コントローラは、アプリケーションのフローを制御し、クライアントアプリから入力を取得し、サービスを呼び出し、データをビュー(JSON/HTML/XML/etc)に渡すためにのみ使用する必要があります。リポジトリ/ DAOは、ビジネスロジックを知らなくてもデータを取得/保持するために使用する必要があります。

以下のシナリオを検討してください。

  • どういうわけか、データのロールアクセスを追加/変更する必要があります。リポジトリを呼び出すすべてのコントローラーにロジックを追加するか、それをリポジトリに配置する必要があります(このリポジトリを呼び出す他のサービスのロジックを壊します)。サービスを介して呼び出す場合は、サービスを変更するか、別のサービスを追加するだけでよく、そのレポを呼び出す他のサービスを壊す心配はありません。
  • アプリケーションの2つのクライアント(WebサイトとREST API)があるとします。将来、ロールアクセスまたはビジネスロジックを変更する必要がある場合は、WebサイトとREST APIコントローラーの両方を変更する必要があります。 。
0