C# 使用DDD创建子实体的正确方法

C# 使用DDD创建子实体的正确方法,c#,nhibernate,domain-driven-design,parent-child,C#,Nhibernate,Domain Driven Design,Parent Child,我对DDD世界相当陌生,在读了几本关于DDD的书(Evans DDD就是其中之一)后,我在互联网上找不到我的问题的答案:用DDD创建子实体的正确方法是什么?你看,互联网上的很多信息都是在简单的层面上运行的。但是细节中有魔鬼,为了简单起见,他们总是在几十个DDD样本中被忽略 我是从stackoverflow的Similar问题来的。我对自己对这个问题的看法并不完全满意,所以我认为我需要详细阐述一下这个问题 例如,我需要创建表示汽车命名的简单模型:公司、模型和改装(例如,日产天籁2012,即“日产”

我对DDD世界相当陌生,在读了几本关于DDD的书(Evans DDD就是其中之一)后,我在互联网上找不到我的问题的答案:用DDD创建子实体的正确方法是什么?你看,互联网上的很多信息都是在简单的层面上运行的。但是细节中有魔鬼,为了简单起见,他们总是在几十个DDD样本中被忽略

我是从stackoverflow的Similar问题来的。我对自己对这个问题的看法并不完全满意,所以我认为我需要详细阐述一下这个问题

例如,我需要创建表示汽车命名的简单模型:公司、模型和改装(例如,日产天籁2012,即“日产”公司、“天籁”模型和“2012”改装)

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

CarsCompany
{
    Name
    (child entities) Models
}

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


CarsModification
{
    (parent entity) Model
    Name
}
所以,现在我需要创建代码。我将使用C作为语言,使用NHibernate作为ORM。这一点很重要,互联网上大量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
        };
    }
}
提交事务并刷新会话后,ORM将正确地将所有内容写入数据库,下次我们加载该公司时,它的模型集合将正确地保存我们的模型。修改也是如此。所以,这种方法使父实体处于不一致的状态,直到它从数据库重新加载。不行

第二种方法。

这一次,我们将使用特定于语言的选项来解决设置其他类的受保护属性的问题——也就是说,我们将在setter和构造函数上使用“protectedinternal”修饰符

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 ();
}
这种方法在相应的实体中获得了所有验证/创建逻辑,我不知道它是好是坏,但是通过使用工厂方法简单地创建对象,我们隐式地将其添加到父对象子集合中。在事务提交和会话刷新之后,将有3个插入到数据库中,即使我从未在代码中编写过一些“add”命令。我不知道也许只是我和我在DDD世界之外的丰富经验,但现在感觉有点不自然


那么,用DDD添加子实体的最正确方法是什么呢?

这里有一个非常具体和残酷诚实的答案:您所有的方法都是错误的,因为您打破了DDD的“第一条规则”,即DB不存在

您所定义的是ORM的持久性模型(nhibernate)。为了设计域对象,首先您必须确定域、其模型、该模型的实体和值对象以及(将在内部处理子项和业务规则)


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

我在这里得到了可接受的答案:

基本上,它是方法2和方法3的组合—将AddModel方法放入CarsCompany,并使其使用名称参数调用CarsModel的受保护内部构造函数,该参数在CarsModel的构造函数中进行验证

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 ();
}
那么,用DDD添加子实体的最正确方法是什么

第三种方法称为<代码>公司,
汽车
改装
几乎相互了解

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

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


尝试在第二/第三种方法中采用这种业务逻辑更改。

有趣。DDD与存储库/ORM导航属性。我认为答案取决于你处理的是一个集合还是两个集合。CarsModel应该是CarsCompany聚合的一部分,还是它自己的聚合

方法一是解决问题。MikeSW暗示了这一点。如果CarsCompany和CarsModel不需要是同一聚合的一部分,则它们应仅通过标识相互引用,导航属性不应在域中可见


方法二是像对待获取聚合一样对待添加到关系——让应用程序服务调用存储库中的方法,这是解决ORM特定问题的正确位置。这种方法可以填充关系的两端。

我可以删除所有代码示例,使整个问题更难理解,但我认为这对社区没有帮助。我不知道复杂的问题不是这样的。整个问题(带或不带代码)在这里是不合适的。正如我所说,它需要讨论。阅读我发布的链接。我可以提供另一个:。正如我之前所说的,这个网站提供的是清晰、简洁的问题,不需要讨论就可以回答。因为你的整个问题都是关于讨论“最正确的方式”和要求“听取意见[sp]”(这在这里也不合适),所以在这里并不合适。我问了一个非常具体的问题,并等待一个非常具体的答案。通过我的例子,我仅仅展示了我在这件事上所做的研究工作。我不需要任何讨论-我需要清楚的答案如何正确地做这件事。仅仅因为你们不知道这个问题的答案并不意味着它有讨论的性质。DDD世界使用了很多模式,我想指出一个我应该应用的模式来解决这个特殊的问题。是的,这很好,听起来就像任何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 ();
}
// var model = CarsModel.Create (company, "Tiana");

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

company.AddModel (model);