web-dev-qa-db-ja.com

データエンティティ、ドメインエンティティ、およびリポジトリ

DDDに頭を悩ませようとしているのですが、行き詰まっています。これが私のプロジェクトのセットアップ方法です:

Data Access Layer
 -Entity models that map to the db
 -Db connection stuff
 -Repositories implementations

Domain Layer
 -Models that represent the DAL entity models
 -Repositories interfaces

Application Layer
 -MVC application that uses Domain models

ここで最初に発生する問題は、ドメインモデルがエンティティモデルとまったく同じであり、これに深刻な問題があることです。エンティティモデルには、「最大長」、「nullable」、 「必須」など。今、私が理解しているDDDに準拠するために、DAL以外の場所でこれらのモデルを直接使用することはできないため、ドメインレイヤーを作成しました。ドメインレイヤーでは、UI検証のためにこれらすべての検証ルールを複製していますが、さらに悪いことに、ルールを変更する必要がある場合、DALとドメインの2か所で変更する必要があります。

例:

User Entity in DAL
Name (required)
Last name (required)
Email (required, maxlen 120)
Username (required, maxlen 120)

User Domain Model
Name (required)
Last name (required)
Email (required, maxlen 120)
Username (required, maxlen 120)

私が非常に奇妙だと思うもう1つのことは、このアーキテクチャのリポジトリ構成です。私が読んだ内容に従って、GenericRepositoryインターフェースと、GenericRepositoryを継承するUserRepositoryインターフェースをすべてドメイン層で作成しました。 GenericRepositoryをDALに実装しました。この実装により、リポジトリの作成に使用されるエンティティのタイプに対応するDAOが作成されます。ここまでは順調ですね。

次に、UserRepositoryの実装に進みましたが、ここで別の問題があります。UserRepositoryインターフェイスはドメインユーザーモデルを想定しているため、DALでインターフェイスを実装しようとすると、ドメインユーザーモデルを使用して実装する必要があります。 DAOはDALモデルではなくドメインモデル用に作成されますが、これは意味がありません。これを修正する唯一の方法は、ドメイン層でDALを参照することですが、これは誤りです。

Domain Layer:

public interface IGenericRepository<TEntity>
{
    TEntity FindById(TKey id);
}

public interface IUserRepository : IGenericRepository<Domain.User>
{
    Task<User> FindByUserNameAsync(string userName);
}


DAL:

public abstract class GenericRepository<TEntity> : IGenericRepository<TEntity>
{
    protected DbContext ctx;
    protected DAO<Entity> dao;

    public GenericRepository(DbContext context)
    {
        ctx = context;
        dao= ctx.Dao<TEntity>();
    }

    public virtual TEntity FindById(TKey id)
    {
        return dao.Find(id);
    }
}

 public class UserRepository : GenericRepository<Domain.Models.User>, IUserRepository
{
    public UserRepository(DbContext context)
        : base(context)
    {
       // THIS WILL CREATE A DAO FOR A DOMAIN MODEL
    }

    // rest of code...
}

誰かがDDDに欠けているものに光を当てることはできますか?

5
victor

はい。ここで展開することはたくさんありますが、混乱のいくつかの根本的な原因は、最前線の物理モデルでこのプロセスを開始することに起因していると思います。これは通常、最初にDDDを実装しようとする個人にあらゆる種類の問題を引き起こします。 DDDの目標は、システムのbehaviorをモデル化して、コアドメインの機能要件を抽象化した結果が得られるようにすることです。とりあえず、DALを忘れてください。実装の詳細です。

まず、モデルをbehaviorで境界付きコンテキストに整理して、システムのモデリングを開始します。それらを真に関連付けるのは、モデルの動作です。エンティティに含まれるデータ/属性が、複雑なシステムの機能要件をモデル化するための良い出発点になることはめったにありません。いくつかの例を提供したいのですが、ドメインを指定しなかったので、いくつかのヒントを提供します。

Userドメインモデルから始めます。これが存在する唯一の理由は、あなたがUserエンティティを持っているためであると私は台所の流しに賭けるでしょう。これの問題は、Userがほとんどまたはまったく動作しないことを意味し(何を使用しますか?)、おそらくあまりに多くの知識を包含しているため、抽象的すぎます。ユーザーは何をしますか(認証は別として)?ショップ? Shopper。コメント? Commenter/Poster。売る? Seller。重要なのは、これらのではないは相互に排他的であることです。 1つのUserは、彼らが従事する行動に応じて、これらすべてのものになる可能性があります。それらがすべて同じデータベーステーブルにマップされるという事実は、実装の詳細です。

私がここに行くところがわかりますか? ShopperShoppingCart、およびCartItemを含むショッピングコンテキストと、BuyerPurchaseRequest、およびLineItem各モデルのモデルは、それぞれ[User][Order]、および[OrderItem]データベーステーブルにマップされます。モデルは、このプロセスの絶対的な焦点でなければなりません。それがどのように永続化されるかではありません。

あなたの質問への直接の答えについては。ドメインには共通のオブジェクトがいくつかあります。 DomainModelsValueObjectsはモデルの基礎ですが、RepositoriesFactoriesが補助的な役割を果たすことがよくあります。リポジトリとファクトリはほとんど常にドメインの実装の詳細を知る必要があるため、通常はドメインモデルの一部です(ただし、図の一部ではありません)。

5
king-side-slide

あなたが質問で言及したアーキテクチャを使用してDDD設計を実装している誰かを見たようですが、それはDDDとは異なります。これは、可能な実装の1つにすぎません。 DDDに関するキングサイドスライドの回答をフォローアップすることをお勧めします。

あなたの質問については、Entity Frameworkとプロジェクト組織の一般的な質問と考えています。そしてここにいくつかのコメントがあります:

ドメインモデルはDALモデルを表していません。それは逆です。ドメインのいくつかの概念を表すようにドメインモデルを設計する必要があります。その場合、DALはこれらのモデルを永続化するユーティリティにすぎません。個人的には、DALエンティティをまったく使用しません。 EFはドメインモデルを直接マップできます。属性の代わりにEntityTypeConfigurationを使用してマッピングファイルを使用します。これは、モデルまたはFluent APIを汚染します。これは非常に冗長で面倒です。

一般的なリポジトリインターフェイスを作成しないでください。これは、そこにあるべきではないメソッドを持つリポジトリを持つことになります。すべてのリポジトリには、要件に応じて必要なメソッドのみが含まれている必要があります。

ビジネスレイヤーでリポジトリインターフェイスを定義します。リポジトリが実装する必要があることを指示するのはビジネスレイヤーであり、その逆ではありません。次に、ビジネスレイヤープロジェクトを参照してこれらのリポジトリを実装する1つまたは複数のプロジェクトを作成し、モデルをマップしてインターフェースを実装できるようにします。

UIレイヤーからドメインモデルを使用しないでください。ビジネス層は、入力DTOを受け入れる書き込み操作(コマンド)とDTOを返す読み取り操作(クエリ)を備えたインターフェースを提供する必要があります。

これらのインターフェイスとDTOをビジネスレイヤーが所有する別のプロジェクトで定義します。次に、UIレイヤーまたはWeb APIコントローラーがそのプロジェクトを参照し、インターフェイスを使用できます。

ビジネス層が外部サービスにアクセスする必要がある場合は、それらのサービスから必要な正確なメソッドを使用して、ビジネス層でそれらのインターフェースを定義します。次に、これらのインターフェースを実装するプロジェクトを作成できます。これは、リポジトリの場合とまったく同じ概念です。

1

(免責事項、私はこれをあなたの質問に対する1回限りの正しい答えとして強制したり提案したりしていません。これは、 オニオンアーキテクチャ に基づいて私のソリューションでそれを行う方法です。)

私の主な混乱は、DALレイヤーでのドメインオブジェクトまたはモデルの複製にあると思います。私は常にDomainオブジェクトをバックエンドデータベースのエンティティと考えています[* 1]。私にとってこれは、ソリューションが機能するために保持する必要がある中心的な情報です。したがって、DALレイヤーを複製する必要はありません。

これは、(入力に基づいて)ソリューションフォルダーを構造化する方法です。

  1. ドメイン層

    • を表すモデル DALエンティティモデル データベースオブジェクト(テーブルとビュー)。

    • リポジトリインターフェース

  2. データアクセス層

    • Db接続関連。

    • リポジトリ実装

    • モデル構成(Fluent構成)。

  3. インフラストラクチャ(ビジネス)レイヤー

    • 外部サービス。

    • ユーティリティ。

    • フロントエンドレイヤーで後で使用される特定のサービス。

  4. UI 応用

    • WebAPIプロジェクト、場合によっては明確な分離のために独自のフォルダーにある[* 2]

    • を使用するMVC/angular/react/etcアプリケーション ドメインモデル オニオンアーキテクチャによる上記のすべての層。

上から下の各レイヤーは、前のレイヤーのすべてを参照します。レベル3(インフラストラクチャ)では、ドメインプロジェクトとDALレイヤープロジェクトなどを参照します。

[* 1] 通常、ドメインオブジェクト間のデータベースビューも含めます。これにより、フロントエンドレイヤーの集約を作成するためだけに "UserService"を使用する必要がほとんどなくなります。

[* 2] ソリューションのサイズ/スコープに応じて、WebAPIプロジェクトを省略し、以前のすべてのレイヤーをMVCアプリで直接使用します。ただし、WebAPIを使用する場合、MVCアプリはこのWebAPIのみを使用し、まれにインフラストラクチャレイヤーの外部サービスなどを参照する場合があります。

コメントごとに編集:

インフラストラクチャレイヤーには通常、次の3つのプロジェクトがあります。

  1. 外部サービス-アプリケーションがパブリックDMVサービスからのデータを必要としているとしましょう。これは私がそれを実装する場所です。サービス参照を追加し、DMVサービスを呼び出すためのロジックを追加し、WebAPIと直接またはMVCアプリケーションと通信するために使用される要求および応答DTOオブジェクトを準備します。

  2. Utilities-これは、たとえば「StringToDateTime」や「IsValidTaxNumber」など、多くの静的な拡張やメソッドのかなり大きなプロジェクトの1つです。他のプロジェクトを参照する必要はありません。これが現在持っている唯一の依存関係は、シリアル化セクションで使用されているNewtonsoft Jsonだと思います。

  3. 特定のサービス-これは主に、コントローラー(APIまたはMVC)の肥大化を防ぐためのミドルウェアとして使用されます。コントローラーカスタマーがいて、ビジネスロジックがある場合は、このプロジェクトに入れます。このようにして、プレゼンテーション層とビジネス/ロジック層をきれいに揃えています。

0
Iztoksson