web-dev-qa-db-ja.com

サービス/ビジネス層を上にしてリポジトリパターンを適切に使用する方法について混乱

学習目的でASP.NET Web Api 2ソリューションを構築していますが、問題が発生しました。私が行方不明になっていることを正確に誰かに教えてもらえないかと思っていました。

私のWeb APIソリューションには4つのレイヤーがあります。

  • データモデル(POCO)、Fluent APIマッピング、EFコンテキスト、移行、シードを含むデータ
  • すべてのデータモデルのCRUDを実装し、ビジネスモデルを返すリポジトリ
  • IRepositoryインターフェースを提供し、アプリケーションのビジネスロジックを実装するサービスを保持するドメイン/サービス/ビジネス。ビジネスモデルも含まれています。
  • Web、Web Apiコントローラー、DI/IoCですべてを結び付けます。

参照は次のとおりです。データ<リポジトリ>ドメイン

Webは、DIの3つのレイヤーすべてを参照します。

個別のドメインレイヤーの背後にある考え方は、データレイヤーまたはリポジトリレイヤーで発生することにはまったく気づかないということです。リポジトリ層のIRepositoryと、その結果として取得したいビジネスモデルを提供することで、要件の概要を示します。ドメイン層の要求を満たすのは、リポジトリ層次第です。

ここに私の難問があります

  • ドメインはデータと関係がないため、データモデルの構造については何も知りません。これは、リポジトリレイヤーがすべてをビジネスモデルに変換してから返す必要があることを意味します。 -汎用オブジェクトとして返して、ドメインレイヤー内のビジネスモデルに汎用オブジェクトをマッピングできると思います。それはより適切でしょうか?
  • 繰り返しますが、ドメインはデータモデルについて何も知らないため、データセットに適用する式をリポジトリに渡すことができません。つまり、WHEREしたいすべてのプロパティに対して、要件の概要を示すIBussinessModelRepositoryに一致するPOCO固有のリポジ​​トリを作成し、その要件を実装する必要があります。しかし、これは「GetByProp1」、「GetByProp2」の多くを必要とするので、すぐに退屈になります。
  • 水和するために複雑なクエリを必要とする複合ビジネスモデルを作成する場合はどうなりますか?一致するリポジトリを使用してさらに別のIBusinessRepositoryを作成し、リポジトリレイヤーに複雑なクエリを書き込む唯一のオプションはありますか?その場合、サービスレイヤーは、ほとんどの場合、特定のリポジトリーに非クッドリクエストを渡すもう1つのステップであると感じられます。
  • Web APIコントローラーがリクエストをリポジトリに直接送信することは適切ですか?すべてのCRUDリクエストをサービスレイヤーにルーティングするのはばかげているように思えます。サービスレイヤーはとにかくリクエストをリポジトリレイヤーに渡すだけです。 いいえ、それはサービス層がすべての操作のエントリポイントであるべきではないためです。 (Webは、提供されるサービスの消費者としてのみ機能します。)
  • UoW + EFに関しては多くの意見が混在しているため、現在は作業単位を使用していません。まだフェンスの両側にいるほどの経験はありません。 UoWはこのシナリオで役に立ちますか?

このパターン/レイヤーアプローチを機能させるために重要な部分が欠けているように感じます。

私はごまかすことで簡単に続行できることを知っています(つまり、コンテキストのようなデータ固有のものへのサービスアクセスを提供します)。しかし、私はそれを適切な方法で行う方法を学ぶことにもっと興味があります。

これまでに行った調査では、大きく異なる結果が得られました。

  • リポジトリからAutoMapperを使用して、実際にクエリを実行せずにタイプをビジネスモデルに変更し、サービスレイヤーがビジネスモデル式を渡して、それを適用してクエリを実行できるようにして、マップされた結果を返す-素晴らしい音AutoMapperが再帰的なナビゲーションプロパティを持つモデルを処理できないため、アプローチが機能しないようです。
  • POCOと直接インターフェースするサービス層に。 (したがって、DALに直接アクセスできます)

何が欠けていますか?

私のコードについてさらに説明するために編集してください:

Github上のMy Generic Repositoryには、次のようなCRUDメソッドが含まれています。

public abstract class RepositoryBase<TData, TDomain, TId> : IRepository<TDomain, TId> where TDomain : EntityBase<TId>, new() where TData : class, IDataEntity<TId>, new()
{
    private readonly ShortStuffContext _context;

    public RepositoryBase(ShortStuffContext context)
    {
        _context = context;
    }

    public IEnumerable<TDomain> GetAll()
    {
        return _context.Set<TData>().BuildQuery<TData, TDomain>();
    }
}

BuildQueryは、すべてのエンティティを取得するか、式を取得して単一のエンティティをフェッチし、ValueInjecterを使用してデータモデルをドメインモデルにマッピングするLINQ拡張機能です。

リポジトリメソッドは、インターフェースを介してドメインレイヤーによって要求されます。

public interface IRepository<TDomain, TId> where TDomain : EntityBase<TId>
{
    IEnumerable<TDomain> GetAll();
}

RepositoryBaseは、現在空になっている個々のPOCOのリポジトリによって拡張され、TData、TDomain、およびTIdのみをリポジトリベースに提供します。

public class UserRepository : RepositoryBase<Data.Entities.User, User, decimal>
{
    public UserRepository(ShortStuffContext context)
        : base(context)
    {
    }
}

これは、コンストラクターインジェクションを介してドメインレイヤーから要求できますIRepository<User, decimal> UserRepository ninject InRequestScopeバインディングを介して。

6
Bio2hazard

アプリケーションの大きさはどれくらいですか?これを考えすぎている可能性は十分にあります。アプリケーションが大規模なエンタープライズグレードのアプリケーションでない限り、推奨している高度な疎結合はおそらく不要です。

このレベルの疎結合が依然として必要であると判断した場合は、「サービス」オブジェクトを返すサービス層を作成します。これは、MVCのモデルの表示と同様の機能を果たします。次に、ドメインモデルからサービスモデルにオブジェクトをマップするコードをサービスレイヤーに記述します。

Data RepositoryがCRUDオブジェクトを返している間に、サービスレイヤーがアクションの結果を返す必要があるという事実が原因である可能性があることに注意してください 例えば:

public InvoiceView GetInvoice(int invoiceID);

ドメイン内のいくつかの異なるテーブル/オブジェクトから、名前、住所、ラインアイテムなど、公開したいあらゆるデータを含むInvoiceViewオブジェクトを返します。

public class InvoiceView
{
    public int InvoiceID;
    public Address ShippingAddress;
    public Address BillingAddress;
    public List<LineItem> LineItems;
    ...
}

同様に、単純にアクションを実行し、アクションの結果を示すオブジェクトを返すService Layerメソッドがあります。

public TransactionResult Transfer(
    int sourceAccountID, int targetAccountID, Money amount, ValidationToken token);

重要:データから完全に分離することはできません。たとえオブジェクトIDまたはUUIDであっても、コンシューマーは常にデータについてある程度の知識を持っている必要があります。

1
Robert Harvey