34

Jeffrey Palermo から有名な Onion Architecture を学んでいます。このパターンに固有のものではありませんが、リポジトリとドメイン サービスの分離が明確にわかりません。リポジトリはデータ アクセスに関係し、サービスはビジネス レイヤーに関するものであると (誤解して) 理解しています (1 つ以上のリポジトリを参照します)。

多くの例では、リポジトリには や などの背後にある種のビジネス ロジックがあるようGetAllProductsByCategoryIdですGetAllXXXBySomeCriteriaYYY

リストの場合、サービスはロジックのないリポジトリのラッパーにすぎないようです。階層 (親/子/子) の場合、ほぼ同じ問題です: 完全な階層をロードするのはリポジトリの役割ですか?

4

7 に答える 7

37

リポジトリは、データベースにアクセスするためのゲートウェイではありません。これは、何らかの形式の永続ストアからドメイン オブジェクトを格納およびロードできるようにするための抽象化です。(データベース、キャッシュ、またはプレーン コレクション)。内部フィールドの代わりにドメイン オブジェクトを取得または返すため、オブジェクト指向のインターフェイスです。

GetAllProductsByCategoryIdまたはGetProductByNameのようなメソッドをリポジトリに追加することはお勧めしません。ユース ケース/オブジェクト フィールドの数が増えるにつれて、リポジトリにメソッドを追加することになるからです。代わりに、仕様を受け取るリポジトリにクエリ メソッドを用意することをお勧めします。製品を取得するために、仕様のさまざまな実装を渡すことができます。

全体として、リポジトリ パターンの目標は、ユース ケースが変わっても変更を必要としないストレージの抽象化を作成することです。この記事では、ドメイン モデリングにおけるリポジトリ パターンについて詳しく説明します。興味があるかもしれません。

2 番目の質問:ProductRepositoryコードに a が表示された場合、Product のリストが返されると思います。また、各 Product インスタンスが完成していることも期待しています。たとえば、Product にオブジェクトへの参照がある場合、null ではなくインスタンスが返さProductDetailれると思います。おそらく、製品と一緒にリポジトリ ロードの実装を実行したり、その場でメソッドを呼び出したりします。リポジトリのユーザーとしてはあまり気にしません。を呼び出したときに、製品が id のみを返す可能性もあります。リポジトリのコントラクトの観点からは、まったく問題ありません。ただし、クライアント コードが複雑になり、自分自身を呼び出す必要があります。Product.getDetail()ProductDetailProductDetailgetDetail()ProductDetailRepositoryProductDetailgetDetail()ProductDetailRepository

ところで、私は過去にリポジトリ クラスのみをラップするサービス クラスを数多く見てきました。アンチパターンだと思います。サービスの呼び出し元がリポジトリを直接使用できるようにすることをお勧めします。

于 2012-09-07T23:33:03.923 に答える
16

リポジトリ パターンは、ドメイン オブジェクトにアクセスするためのコレクションのようなインターフェイスを使用して、ドメインとデータ マッピング レイヤーの間を仲介します。

したがって、リポジトリは、ドメイン エンティティに対する CRUD 操作のインターフェイスを提供します。リポジトリは集合体全体を扱うことに注意してください。

集合体は、一緒に属するもののグループです。集約ルートは、それらをすべてまとめたものです。

OrderOrderLines:

OrderLines は、親 Order なしで存在する理由はなく、他の Order に属することもできません。この場合、Order と OrderLines はおそらく Aggregate になり、Order は Aggregate Root になります。

ビジネス ロジックは Domain Entities ではなく Repository layer にある必要があります。アプリケーション ロジックはサービス レイヤにある必要があります。ここでのサービスは、リポジトリ間のコーディネーターとしての役割を果たします。

于 2012-09-07T12:59:56.063 に答える
7

私はまだこれに苦労していますが、回答として投稿したいだけでなく、これに関するフィードバックを受け入れます (そして望んでいます)。

例ではGetProductsByCategory(int id)

まずは最初の必要性から考えてみましょう。コントローラー、おそらく CategoryController をヒットしたので、次のようになります。

public CategoryController(ICategoryService service) {
    // here we inject our service and keep a private variable.
}

public IHttpActionResult Category(int id) {
    CategoryViewModel model = something.GetCategoryViewModel(id); 
    return View()
} 

ここまでは順調ですね。ビューモデルを作成する「何か」を宣言する必要があります。簡単にして言いましょう:

public IHttpActionResult Category(int id) {
    var dependencies = service.GetDependenciesForCategory(id);
    CategoryViewModel model = new CategoryViewModel(dependencies); 
    return View()
} 

わかりました、依存関係とは何ですか? カテゴリツリー、製品、ページ、合計製品数などが必要になる場合があります。

したがって、これをリポジトリの方法で実装すると、多かれ少なかれ次のようになります。

public IHttpActionResult Category(int id) {
    var products = repository.GetCategoryProducts(id);
    var category = repository.GetCategory(id); // full details of the category
    var childs = repository.GetCategoriesSummary(category.childs);
    CategoryViewModel model = new CategoryViewModel(products, category, childs); // awouch! 
    return View()
} 

代わりに、サービスに戻ります:

public IHttpActionResult Category(int id) {
    var category = service.GetCategory(id);
    if (category == null) return NotFound(); //
    var model = new CategoryViewModel(category);
    return View(model);
}

はるかに優れていますが、正確には何が入っていservice.GetCategory(id)ますか?

public CategoryService(ICategoryRespository categoryRepository, IProductRepository productRepository) {
    // same dependency injection here

    public Category GetCategory(int id) {
        var category = categoryRepository.Get(id);
        var childs = categoryRepository.Get(category.childs) // int[] of ids
        var products = productRepository.GetByCategory(id) // this doesn't look that good...
        return category;
    }

}

別のアプローチである作業単位を試してみましょう。エンティティ フレームワークを UoW とリポジトリとして使用するので、それらを作成する必要はありません。

public CategoryService(DbContext db) {
    // same dependency injection here

    public Category GetCategory(int id) {
        var category = db.Category.Include(c=> c.Childs).Include(c=> c.Products).Find(id);
        return category;
    }
}

したがって、ここではメソッド構文の代わりに「クエリ」構文を使用していますが、独自の複合体を実装する代わりに、ORM を使用できます。また、すべてのリポジトリにアクセスできるため、サービス内で Unit of work を実行できます。

次に、必要なデータを選択する必要があります。おそらく、エンティティのすべてのフィールドは必要ありません。

これが実際に起こっていることがわかる最適な場所は ViewModel です。各 ViewModel はそれ自身のデータをマップする必要があるかもしれないので、サービスの実装をもう一度変更しましょう。

public CategoryService(DbContext db) {
    // same dependency injection here

    public Category GetCategory(int id) {
        var category = db.Category.Find(id);
        return category;
    }
}

すべての製品と内部カテゴリはどこにありますか?

ViewModel を見てみましょう。これはデータを値にのみマップすることを覚えておいてください。ここで何か他のことをしている場合は、ViewModel に責任を与えすぎている可能性があります。

public CategoryViewModel(Category category) {
    Name = category.Name;
    Id = category.Id;
    Products = category.Products.Select(p=> new CategoryProductViewModel(p));
    Childs = category.Childs.Select(c => c.Name); // only childs names.
}

CategoryProductViewModelあなたは今自分で想像することができます。

BUT (なぜ常に but があるのですか??)

3 db のヒットを実行しており、検索のためにすべてのカテゴリ フィールドを取得しています。また、遅延読み込みを有効にする必要があります。本当の解決策ではありませんか?

これを改善するために、find を where... で変更できますが、これはSingleorFindを ViewModel にデリゲートし、IQueryable<Category>正確に 1 であることがわかっている を返します。

「私はまだ苦労していますか?」と言ったのを覚えていますか?これが主な理由です。これを修正するには、サービスから正確に必要なデータを返す必要があります (また、ご存知のように ..... ご存知のとおり、ViewModel です)。

それでは、コントローラに戻りましょう:

public IHttpActionResult Category(int id) {
    var model = service.GetProductCategoryViewModel(id);
    if (category == null) return NotFound(); //
    return View(model);
}

メソッド内でGetProductCategoryViewModel、さまざまな部分を返すプライベート メソッドを呼び出して、それらを ViewModel として組み立てることができます。

これは悪いことです。これで、私のサービスはビューモデルについて認識しました...修正しましょう。

インターフェイスを作成します。このインターフェイスは、このメソッドが返すものの実際のコントラクトです。

ICategoryWithProductsAndChildsIds // quite verbose, i know.

これで、ViewModel を次のように宣言するだけで済みます。

public class CategoryViewModel : ICategoryWithProductsAndChildsIds 

そして、私たちが望む方法でそれを実装します。

ICategoryBasicインターフェースにはあまりにも多くのものがあるようIProductsに見えますIChilds

したがって、別のビューモデルを実装するときは、 のみを選択できますIProducts。これらのコントラクトを取得するためのメソッド (プライベートかどうかに関係なく) をサービスに持たせ、サービス レイヤーでピースを接着することができます。(言うは易く行うは難し)

コードが完全に機能するようになったら、ブログ投稿や github リポジトリを作成するかもしれませんが、現時点ではまだ用意していないので、ここまでです。

于 2015-04-04T21:11:29.993 に答える
4

ドメイン駆動設計では、リポジトリは集約全体を取得する責任があります。

于 2012-09-07T11:28:40.767 に答える