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

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

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

例如,我需要创建代表汽车命名的简单模型:公司,模型和修改(例如,日产天籁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 Models { get { return new ImmutableSet (this._models); } } private readonly ISet _models = new HashedSet (); 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 Modifications { get { return new ImmutableSet (this._modifications); } } private readonly ISet _modifications = new HashedSet (); 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将正确地将所有内容写入数据库,下次我们加载该公司时,它的模型集合将正确保存我们的模型。 修改也是如此。 因此,这种方法使我们的父实体处于不一致状态,直到它从数据库重新加载。 不行。

第二种方法。

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

 public class CarsCompany { public virtual string Name { get; protected set; } public virtual IEnumerable Models { get { return new ImmutableSet (this._models); } } private readonly ISet _models = new HashedSet (); 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 Modifications { get { return new ImmutableSet (this._modifications); } } private readonly ISet _modifications = new HashedSet (); 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 (); } 

这次每个实体创建都使父实体和子实体保持一致状态。 但是子实体状态的validation泄漏到父实体( AddModelAddModification方法)。 由于我不是DDD的专家,我不确定它是否合适。 如果不能通过属性简单地设置子实体属性并且基于传递的参数设置某些状态将需要更复杂的工作来为属性分配参数值,那么将来可能会产生更多问题。 我的印象是,我们应该尽可能地将实体的逻辑集中在该实体中。 对我来说,这种方法将父对象转变为某种实体和工厂混合体。

第三种方法。

好的,我们将颠倒维持亲子关系的责任。

 public class CarsCompany { public virtual string Name { get; protected set; } public virtual IEnumerable Models { get { return new ImmutableSet (this._models); } } private readonly ISet _models = new HashedSet (); 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 Modifications { get { return new ImmutableSet (this._modifications); } } private readonly ISet _modifications = new HashedSet (); 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 (); } 

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

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

我在这里得到了可接受的答案: https : //groups.yahoo.com/neo/groups/domaindrivendesign/conversations/messages/23187

基本上,它是方法2和方法3的组合 – 将AddModel方法放入CarsCompany,并使用name参数调用受保护的CarsModel内部构造函数,该参数在CarsModel的构造函数中进行validation。

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

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

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

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

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

尝试在第2 /第3种方法中采用此业务逻辑更改。

有趣。 DDD vs Repository / ORM导航属性。 我认为答案取决于你是在处理一个或两个聚合。 CarsModel应该是CarsCompany聚合的一部分,还是它自己的聚合?

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

方法二是以与处理聚合相同的方式处理添加到关系中 – 使Application Services从存储库调用方法,这是解决ORM特定问题的正确位置。 这种方法可以填充关系的两端。

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

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

Nhibernate或db模式在这里没有位置,您只需要纯C#代码并清楚地了解域。