我对 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 ();
}
这次每个实体创建都使父实体和子实体处于一致状态。但是子实体状态的验证泄漏到父实体(AddModel
和AddModification
方法)中。由于我不是 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 添加子实体的最正确方法是什么?