16

我对 DDD 世界还很陌生,在阅读了几本关于它的书(其中包括 Evans DDD)后,我无法在互联网上找到我的问题的答案:用 DDD 创建子实体的正确方法是什么?你看,互联网上的很多信息都在一些简单的层面上运作。但是细节上的魔鬼,为了简单起见,在几十个 DDD 示例中总是省略它们。

我来自我自己在stackoverflow上对类似问题的回答。我对自己对这个问题的看法并不完全满意,所以我认为我需要详细说明这个问题。

例如,我需要创建代表汽车命名的简单模型:公司、型号和改装(例如,Nissan Teana 2012 - 这将是“Nissan”公司、“Teana”型号和“2012”改装)。

我要创建的模型草图如下所示:

CarsCompany
{
    Name
    (child entities) Models
}

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


CarsModification
{
    (parent entity) Model
    Name
}

所以,现在我需要创建代码。我将使用 C# 作为语言,使用 NHibernate 作为 ORM。这很重要,并且通常不会在 Internet 上的大量 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 将正确地将所有内容写入数据库,下次我们加载该公司时,它的模型集合将正确保存我们的模型。修改也是如此。所以这种方法让我们的父实体处于不一致的状态,直到它从数据库中重新加载。不去。

第二种方法。

这次我们将使用特定于语言的选项来解决设置其他类的受保护属性的问题 - 即我们将在设置器和构造器上使用“受保护的内部”修饰符。

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 ();
}

这次每个实体创建都使父实体和子实体处于一致状态。但是子实体状态的验证泄漏到父实体(AddModelAddModification方法)中。由于我不是 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
        };
    }


    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 添加子实体的最正确方法是什么?

第三种方法称为紧耦合CompanyCar并且Modification几乎了解彼此的一切。

第二种方法在 DDD 中被广泛提出。域对象负责创建嵌套域对象并将其注册到内部。

第一种方法是经典的 OOP 风格。对象的创建与将对象添加到某个集合中是分开的。这样,代码使用者可以用任何派生类(例如 TrailerCar)的对象替换具体类(例如 Car)的对象。

// 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 Repository / ORM 导航属性。我认为答案取决于您是在处理一个聚合还是两个聚合。CarsModel 应该是 CarsCompany 聚合的一部分,还是它自己的聚合?

方法一是让问题消失。MikeSW 暗示了这一点。如果 CarsCompany 和 CarsModel 不需要是同一个聚合的一部分,那么它们应该只通过身份相互引用,导航属性在域中不可见。

方法二是将添加到关系中的方式与我们处理获取聚合的方式相同 - 使应用程序服务从存储库中调用一个方法,这是解决您的 ORM 特定问题的正确位置。这种方法可以填充关系的两端。

于 2016-12-24T03:12:23.043 回答
-1

这是一个非常具体且非常诚实的答案:您所有的方法都是错误的,因为您违反了 DDD 的“第一条规则”,即数据库不存在。

您定义的是 ORM (nhibernate) 的 PERSISTENCE 模型。为了设计域对象,首先您必须识别有界上下文、它的模型、该模型的实体和值对象以及聚合根(它将在内部处理子和业务规则)。

Nhibernate 或 db 模式在这里没有位置,您只需要纯 C# 代码和对领域的清晰了解。

于 2012-06-15T10:10:19.980 回答