18

Jimmy Bogard( http://ndcoslo.oktaset.com/Agenda )からのNDC12プレゼンテーション「CraftingWicked Domain Models」を見た後、私はその種のドメインモデルを永続化する方法をさまよっていました。
これはプレゼンテーションのサンプルクラスです。

public class Member
{
    List<Offer> _offers;

    public Member(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
        _offers = new List<Offer>();
    }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public IEnumerable<Offer> AssignedOffers { 
        get { return _offers; }
    }

    public int NumberOfOffers { get; private set; }

    public Offer AssignOffer(OfferType offerType, IOfferValueCalc valueCalc)
    {
        var value = valueCalc.CalculateValue(this, offerType);
        var expiration = offerType.CalculateExpiration();
        var offer = new Offer(this, offerType, expiration, value);
        _offers.Add(offer);
        NumberOfOffers++;
        return offer;
    }
}

したがって、このドメインモデルにはいくつかのルールが含まれています。-
メンバーは姓名を持っている必要があり
ます-オファーの数は外部で変更できません
-メンバーは新しいオファーを作成し、その価値と割り当てを計算する責任があります

これをEntityFrameworkやNHibernateなどのORMにマップしようとすると、機能しません。では、この種のモデルをORMを使用してデータベースにマッピングするための最良のアプローチは何でしょうか。
たとえば、セッターがない場合、DBからAssignedOffersをロードするにはどうすればよいですか?

私にとって意味のあることは、コマンド/クエリアーキテクチャを使用することだけです。クエリは常にドメインエンティティではなくDTOを使用して実行され、コマンドはドメインモデルで実行されます。また、イベントソーシングは、ドメインモデルの動作に最適です。しかし、この種のCQSアーキテクチャは、すべてのプロジェクト、特にブラウンフィールドに適しているとは限りません。か否か?

私はここで同様の質問を知っていますが、具体的な例と解決策を見つけることができませんでした。

4

4 に答える 4

12

これは実際には非常に良い質問であり、私が熟考したものです. 完全にカプセル化された適切なドメイン オブジェクト (つまり、プロパティ セッターがない) を作成し、ORM を使用してドメイン オブジェクトを直接構築することは潜在的に困難です。

私の経験では、この問題を解決するには 3 つの方法があります。

  • Luka が既に述べたように、NHibernate はプロパティ セッターではなく、プライベート フィールドへのマッピングをサポートしています。
  • EF (上記をサポートしているとは思わない) を使用している場合は、memento パターンを使用してドメイン オブジェクトの状態を復元できます。たとえば、エンティティ フレームワークを使用して、ドメイン エンティティがプライベート フィールドを設定するために受け入れる「memento」オブジェクトを設定します。
  • ご指摘のとおり、イベント ソーシングで CQRS を使用すると、この問題が解消されます。これは、完全にカプセル化されたドメイン オブジェクトを作成するための私の好みの方法であり、イベント ソーシングの利点もすべて備えています。
于 2012-06-27T11:02:21.103 に答える
2

古いスレッド。しかし、Vaughn Vernon による最近の投稿(2014 年後半) では、特に Entity Framework を参照して、このシナリオだけに対処しています。そのような情報を見つけるのにどうにか苦労したことを考えると、ここに投稿することも役立つかもしれません.

基本的に、投稿では、 「データバッグ」側に関係するものについて、Productドメイン (集約) オブジェクトがProductStateEF POCO データオブジェクトをラップすることを提唱しています。もちろん、ドメイン オブジェクトは、ドメイン固有のメソッド/アクセサーを通じて豊富なドメイン動作をすべて追加しますが、プロパティを取得/設定する必要がある場合は内部データ オブジェクトに頼ります。

投稿から直接スニペットをコピーする:

public class Product
{
  public Product(
    TenantId tenantId,
    ProductId productId,
    ProductOwnerId productOwnerId,
    string name,
    string description)
  {
    State = new ProductState();
    State.ProductKey = tenantId.Id + ":" + productId.Id;
    State.ProductOwnerId = productOwnerId;
    State.Name = name;
    State.Description = description;
    State.BacklogItems = new List<ProductBacklogItem>();
  }

  internal Product(ProductState state)
  {
    State = state;
  }

  //...

  private readonly ProductState State;
}

public class ProductState
{
  [Key]
  public string ProductKey { get; set; }

  public ProductOwnerId ProductOwnerId { get; set; }

  public string Name { get; set; }

  public string Description { get; set; }

  public List<ProductBacklogItemState> BacklogItems { get; set; }
  ...
}

リポジトリは、DB 永続バージョンからエンティティ インスタンスをインスタンス化 (ロード) するために、内部コンストラクタを使用します。

私が自分で追加できる1つのビットは、おそらく、 EFを介した永続化の目的で、Productドメインオブジェクトをもう1つのアクセサーでダーティにする必要があるということです:ドメインエンティティをデータベースからロードできるようにするのと同じnew Product(productState)ように、逆の方法を許可する必要があります次のようなものを通して:

public class Product
{
   // ...
   internal ProductState State
   {
     get
     {
       // return this.State as is, if you trust the caller (repository),
       // or deep clone it and return it
     }
   }
}

// inside repository.Add(Product product):

dbContext.Add(product.State);
于 2016-07-20T13:00:08.033 に答える
1

AssignedOffers の場合: コードを見ると、AssignedOffers がフィールドから値を返すことがわかります。NHibernate は、Map(x => x.AssignedOffers).Access.Field() のようにそのフィールドに入力できます。

CQS の使用に同意します。

于 2012-06-27T09:09:53.960 に答える
0

最初に DDD を実行する場合、永続性の問題は無視されます。ORM は RDBMS と緊密に結合されているため、永続性が懸念されます。

ORM は、ドメインではなく永続構造をモデル化します。基本的に、リポジトリは、受信した集約ルートを 1 つまたは複数の永続エンティティに「変換」する必要があります。集約ルートは達成しようとしていることに応じて変化するため、境界付けられたコンテキストは非常に重要です。

割り当てられた新しいオファーのコンテキストでメンバーを保存するとします。次に、このようなものがあります(もちろん、これは可能なシナリオの1つにすぎません)

public interface IAssignOffer
{
    int OwnerId {get;}
    Offer AssignOffer(OfferType offerType, IOfferValueCalc valueCalc);
    IEnumerable<Offer> NewOffers {get; }
}

public class Member:IAssignOffer
{
    /* implementation */ 
 }

 public interface IDomainRepository
 {
    void Save(IAssignOffer member);    
 }

次に、レポは NH エンティティを変更するために必要なデータのみを取得します。それだけです。

EVent Sourcing については、ドメインに適合するかどうかを確認する必要があると思います。イベント ソーシングをドメインの集約ルートの格納にのみ使用し、残り (主にインフラストラクチャ) を通常の方法 (リレーショナル) で格納することに問題はないと思います。テーブル)。CQRS は、この問題に関して非常に柔軟に対応できると思います。

于 2012-06-27T09:57:57.333 に答える