16

私は DDD の世界にかなり慣れていませんが、それについての本 (Evans DDD など) を何冊か読んだ後、インターネットで私の質問に対する答えを見つけることができませんでした: DDD で子エンティティを作成する適切な方法は何ですか? ご覧のとおり、インターネット上の多くの情報は、単純なレベルで機能しています。しかし、細部の悪魔であり、単純化のために、何十もの DDD サンプルでは常に省略されています。

私は、スタックオーバーフローの類似の質問に関する私自身の回答から来ています。この問題に関する私自身のビジョンに完全に満足しているわけではないので、この問題について詳しく説明する必要があると考えました。

たとえば、車の名前を表す単純なモデルを作成する必要があります: 会社、モデル、およびモディフィケーション (たとえば、Nissan Teana 2012 - これは "Nissan" company、"Teana" モデル、"2012" モディフィケーションになります)。

作成したいモデルのスケッチは次のようになります。

CarsCompany
{
    Name
    (child entities) Models
}

CarsModel
{
    (parent entity) Company
    Name
    (child entities) Modifications
}


CarsModification
{
    (parent entity) Model
    Name
}

そのため、コードを作成する必要があります。言語として C# を使用し、ORM として NHibernate を使用します。これは重要なことであり、通常、インターネット上の膨大な DDD サンプルには示されていません。

最初のアプローチ。

ファクトリ メソッドを介して典型的なオブジェクトを作成する単純なアプローチから始めます。

public class CarsCompany
{
    public virtual string Name { get; protected set; }
    public virtual IEnumerable<CarsModel> Models { get { return new ImmutableSet<CarsModel> (this._models); } }


    private readonly ISet<CarsModel> _models = new HashedSet<CarsModel> ();


    protected CarsCompany ()
    {
    }


    public static CarsCompany Create (string name)
    {
        if (string.IsNullOrEmpty (name))
            throw new ArgumentException ("Invalid name specified.");

        return new CarsCompany
        {
            Name = name
        };
    }


    public void AddModel (CarsModel model)
    {
        if (model == null)
            throw new ArgumentException ("Model is not specified.");

        this._models.Add (model);
    }
}


public class CarsModel
{
    public virtual CarsCompany Company { get; protected set; }
    public virtual string Name { get; protected set; }
    public virtual IEnumerable<CarsModification> Modifications { get { return new ImmutableSet<CarsModification> (this._modifications); } }


    private readonly ISet<CarsModification> _modifications = new HashedSet<CarsModification> ();


    protected CarsModel ()
    {
    }


    public static CarsModel Create (CarsCompany company, string name)
    {
        if (company == null)
            throw new ArgumentException ("Company is not specified.");

        if (string.IsNullOrEmpty (name))
            throw new ArgumentException ("Invalid name specified.");

        return new CarsModel
        {
            Company = company,
            Name = name
        };
    }


    public void AddModification (CarsModification modification)
    {
        if (modification == null)
            throw new ArgumentException ("Modification is not specified.");

        this._modifications.Add (modification);
    }
}


public class CarsModification
{
    public virtual CarsModel Model { get; protected set; }
    public virtual string Name { get; protected set; }


    protected CarsModification ()
    {
    }


    public static CarsModification Create (CarsModel model, string name)
    {
        if (model == null)
            throw new ArgumentException ("Model is not specified.");

        if (string.IsNullOrEmpty (name))
            throw new ArgumentException ("Invalid name specified.");

        return new CarsModification
        {
            Model = model,
            Name = name
        };
    }
}

このアプローチの悪い点は、モデルを作成しても親モデル コレクションに追加されないことです。

using (var tx = session.BeginTransaction ())
{
    var company = CarsCompany.Create ("Nissan");

    var model = CarsModel.Create (company, "Tiana");
    company.AddModel (model);
    // (model.Company == company) is true
    // but (company.Models.Contains (model)) is false

    var modification = CarsModification.Create (model, "2012");
    model.AddModification (modification);
    // (modification.Model == model) is true
    // but (model.Modifications.Contains (modification)) is false

    session.Persist (company);
    tx.Commit ();
}

トランザクションがコミットされ、セッションがフラッシュされた後、ORM はすべてをデータベースに正しく書き込み、次にその会社をロードすると、そのモデル コレクションはモデルを正しく保持します。改造も同様です。したがって、このアプローチでは、親エンティティがデータベースからリロードされるまで一貫性のない状態のままになります。立ち入り禁止。

2番目のアプローチ。

今回は、言語固有のオプションを使用して、他のクラスの保護されたプロパティを設定する問題を解決します。つまり、セッターとコンストラクターの両方で「保護された内部」修飾子を使用します。

public class CarsCompany
{
    public virtual string Name { get; protected set; }
    public virtual IEnumerable<CarsModel> Models { get { return new ImmutableSet<CarsModel> (this._models); } }


    private readonly ISet<CarsModel> _models = new HashedSet<CarsModel> ();


    protected CarsCompany ()
    {
    }


    public static CarsCompany Create (string name)
    {
        if (string.IsNullOrEmpty (name))
            throw new ArgumentException ("Invalid name specified.");

        return new CarsCompany
        {
            Name = name
        };
    }


    public CarsModel AddModel (string name)
    {
        if (string.IsNullOrEmpty (name))
            throw new ArgumentException ("Invalid name specified.");

        var model = new CarsModel
        {
            Company = this,
            Name = name
        };

        this._models.Add (model);

        return model;
    }
}


public class CarsModel
{
    public virtual CarsCompany Company { get; protected internal set; }
    public virtual string Name { get; protected internal set; }
    public virtual IEnumerable<CarsModification> Modifications { get { return new ImmutableSet<CarsModification> (this._modifications); } }


    private readonly ISet<CarsModification> _modifications = new HashedSet<CarsModification> ();


    protected internal CarsModel ()
    {
    }


    public CarsModification AddModification (string name)
    {
        if (string.IsNullOrEmpty (name))
            throw new ArgumentException ("Invalid name specified.");

        var modification = new CarsModification
        {
            Model = this,
            Name = name
        };

        this._modifications.Add (modification);

        return modification;
    }
}


public class CarsModification
{
    public virtual CarsModel Model { get; protected internal set; }
    public virtual string Name { get; protected internal set; }


    protected internal CarsModification ()
    {
    }
}

...

using (var tx = session.BeginTransaction ())
{
    var company = CarsCompany.Create ("Nissan");
    var model = company.AddModel ("Tiana");
    var modification = model.AddModification ("2011");

    session.Persist (company);
    tx.Commit ();
}

今回は、エンティティを作成するたびに、親エンティティと子エンティティの両方が一貫した状態のままになります。しかし、子エンティティの状態の検証は、親エンティティ (AddModelおよびAddModificationメソッド) に漏れました。私は DDD の専門家ではないので、大丈夫かどうかわかりません。子エンティティのプロパティをプロパティを介して単純に設定できず、渡されたパラメータに基づいて状態を設定するには、パラメータ値をプロパティに割り当てるというより複雑な作業が必要になるため、将来的にさらに多くの問題が発生する可能性があります。可能な限り、そのエンティティ内にエンティティに関するロジックを集中させる必要があるという印象を受けました。私にとって、このアプローチは親オブジェクトをある種のエンティティとファクトリーのハイブリッドに変えます。

3番目のアプローチ。

わかりました、親子関係を維持する責任を逆転させます。

public class CarsCompany
{
    public virtual string Name { get; protected set; }
    public virtual IEnumerable<CarsModel> Models { get { return new ImmutableSet<CarsModel> (this._models); } }


    private readonly ISet<CarsModel> _models = new HashedSet<CarsModel> ();


    protected CarsCompany ()
    {
    }


    public static CarsCompany Create (string name)
    {
        if (string.IsNullOrEmpty (name))
            throw new ArgumentException ("Invalid name specified.");

        return new CarsCompany
        {
            Name = name
        };
    }


    protected internal void AddModel (CarsModel model)
    {
        this._models.Add (model);
    }
}


public class CarsModel
{
    public virtual CarsCompany Company { get; protected set; }
    public virtual string Name { get; protected set; }
    public virtual IEnumerable<CarsModification> Modifications { get { return new ImmutableSet<CarsModification> (this._modifications); } }


    private readonly ISet<CarsModification> _modifications = new HashedSet<CarsModification> ();


    protected CarsModel ()
    {
    }


    public static CarsModel Create (CarsCompany company, string name)
    {
        if (company == null)
            throw new ArgumentException ("Company is not specified.");

        if (string.IsNullOrEmpty (name))
            throw new ArgumentException ("Invalid name specified.");

        var model = new CarsModel
        {
            Company = company,
            Name = name
        };

        model.Company.AddModel (model);

        return model;
    }


    protected internal void AddModification (CarsModification modification)
    {
        this._modifications.Add (modification);
    }
}


public class CarsModification
{
    public virtual CarsModel Model { get; protected set; }
    public virtual string Name { get; protected set; }


    protected CarsModification ()
    {
    }


    public static CarsModification Create (CarsModel model, string name)
    {
        if (model == null)
            throw new ArgumentException ("Model is not specified.");

        if (string.IsNullOrEmpty (name))
            throw new ArgumentException ("Invalid name specified.");

        var modification = new CarsModification
        {
            Model = model,
            Name = name
        };

        modification.Model.AddModification (modification);

        return modification;
    }
}

...

using (var tx = session.BeginTransaction ())
{
    var company = CarsCompany.Create ("Nissan");
    var model = CarsModel.Create (company, "Tiana");
    var modification = CarsModification.Create (model, "2011");

    session.Persist (company);
    tx.Commit ();
}

このアプローチは、対応するエンティティ内にすべての検証/作成ロジックを取得し、それが良いか悪いかはわかりませんが、ファクトリ メソッドを使用してオブジェクトを単純に作成することにより、親オブジェクトの子コレクションに暗黙的に追加します。トランザクションのコミットとセッションのフラッシュの後、コードに「追加」コマンドを記述したことがなくても、データベースに 3 つの挿入が行われます。それが私と、DDD の世界の外での私の広大な経験だけなのかどうかはわかりませんが、今のところ少し不自然に感じます。

では、DDD で子エンティティを追加する最も正しい方法は何ですか?

4

4 に答える 4

3

ここで受け入れられる答えがあります:https://groups.yahoo.com/neo/groups/domaindrivendesign/conversations/messages/23187

基本的に、これはメソッド 2 と 3 の組み合わせです。AddModel メソッドを CarsCompany に配置し、CarsModel のコンストラクター内で検証される名前パラメーターを使用して、CarsModel の保護された内部コンストラクターを呼び出すようにします。

于 2012-06-17T19:09:32.233 に答える
3

では、DDD で子エンティティを追加する最も正しい方法は何ですか?

3 番目のアプローチは、タイト カップリングと呼ばれます。CompanyCarそしてModificationお互いについてほとんどすべてを知っています。

2 番目のアプローチは、DDD で広く提案されています。ドメイン オブジェクトは、ネストされたドメイン オブジェクトを作成し、内部に登録する責任があります。

最初のアプローチは、古典的な OOP スタイルです。オブジェクトの作成は、オブジェクトを何らかのコレクションに追加することから分離されています。このようにして、コード コンシューマは、具象クラス (Car など) のオブジェクトを任意の派生クラス (TrailerCar など) のオブジェクトに置き換えることができます。

// var model = CarsModel.Create (company, "Tiana");

var model = TrailerCarsModel.Create (
    company, "Tiana", SimpleTrailer.Create(company));

company.AddModel (model);

このビジネス ロジックの変更を 2 番目または 3 番目のアプローチで採用してみてください。

于 2015-10-20T10:47:56.393 に答える
1

面白い。DDD vs リポジトリ / ORM ナビゲーション プロパティ。答えは、1 つまたは 2 つの集計を扱っているかによって異なると思います。CarsModel は CarsCompany 集計の一部であるべきですか、それとも独自の集計であるべきでしょうか?

アプローチの 1 つは、問題を解消することです。MikeSW はこれをほのめかしました。CarsCompany と CarsModel が同じ集合体の一部である必要がない場合は、ID によってのみ相互に参照する必要があり、ナビゲーション プロパティはドメインに表示されません。

アプローチ 2 は、リレーションシップへの追加を集計のフェッチと同じ方法で処理することです。つまり、アプリケーション サービスがリポジトリからメソッドを呼び出すようにします。これは、ORM 固有の問題に対処するための適切な場所です。このようなメソッドは、関係の両端にデータを取り込むことができます。

于 2016-12-24T03:12:23.043 に答える
-1

これは非常に具体的で非常に正直な答えです。DDD の「最初のルール」、つまり DB が存在しないことを破ったため、すべてのアプローチが間違っています。

あなたが定義しているのは、ORM (nhibernate) の PERSISTENCE モデルです。ドメイン オブジェクトを設計するには、まず境界コンテキスト、そのモデル、そのモデルのエンティティと値オブジェクト、および集約ルート(子とビジネス ルールを内部で処理します) を特定する必要があります。

Nhibernate や db スキーマはここでは使用できません。純粋な C# コードとドメインの明確な理解だけが必要です。

于 2012-06-15T10:10:19.980 に答える