【问题标题】:DDD Aggregate Root PersistenceDDD 聚合根持久性
【发布时间】:2014-08-11 13:59:24
【问题描述】:

我有一个类,它是一个聚合根并代表一个人。一个人必须有一个 Title(Mr、Mrs、Ms 等),它是 Person 对象的一个​​属性。创建人员时,用户必须从下拉列表中选择一个标题,其内容通过另一个页面进行管理。

还有通过只读页面查看此信息的选项,因此通过使用下面的模型,我有“手头”的 ID 和名称,而不必离开并根据以下内容检索标题的 ValueName如果我要将 TitleId 添加为 Person 的属性而不是 Title,我必须做的就是标题 ID。

保存人员对象时,标题的 Id 作为人员的一部分被持久化并存储在 Person db 表中(包含标题的 db 表没有被触及)

在填充人员时,存储过程根据标题 ID 将人员连接到标题,并返回用于填充人员标题类的信息。

public class Person : IAggregateRoot, IPerson 
{
   public string Forename { get;set; }
   public string Surname{ get;set; }
   public ITitle Title { get;set; }
}

public class Title : IAggregateRoot , ITitle
{
    public Guid Id {get;set;}
    public string ValueName {get;set;}
}

我的问题是:从 DDD 的角度来看,是否可以使用此类结构并将聚合根对象嵌套在另一个聚合根对象中,因为它是“固定列表”或“查找值”并且还需要由管理员单独维护?

【问题讨论】:

    标签: c# domain-driven-design aggregateroot


    【解决方案1】:

    不,嵌套聚合根是不行的。聚合可以包含多个实体,或引用(通过 id)到另一个聚合,但它不能封装聚合。

    我还要说“标题”不是实体(聚合或其他)的良好候选者。它应该被认为是一个“值对象”,实际上 - 它应该只是一个字符串。您可能有一个单独的标题查找表,但这并不意味着您应该从您的 Person 对象中引用该表。

    将维护职称查找表的过程与将职称分配给人员分开。选择标题后,将字符串复制到 person 对象。非规范化是使用值对象的方式。

    【讨论】:

    • 只是一个想法:“标题”只有当 any 字符串可以是标题时,它才应该只是一个字符串。
    • 感谢马特,我现在认为 Person 类应该包含 titleId 而不是 title 对象。关于维护标题列表,标题对象需要以自己的权利持久化这一事实是否意味着标题应该是聚合根? (目前我的存储库只允许持久化实现 IAggregateRoot 的对象)
    • @MikeSW - 不一定。输入验证可以是分配标题过程的一部分。
    • @AnthonyWalters - 这会工作,但它可能无法提供最佳的可维护性。考虑到您可能希望在某些时候删除标题。如果您通过 TitleID 引用,那么您将永远无法删除标题。您可以通过在 Titles 类中标记一个标志来“软删除”,但是您必须记住检查它。
    • 另外,如果您通过 ID 引用,那么您还需要对标题进行唯一约束。否则,具有相同头衔的两个人可能具有不同的 TitleID。
    【解决方案2】:

    根据定义,聚合根是聚合的“根”,因此对您的问题的简短回答是否定的,聚合根不能嵌套在另一个聚合根中。

    请记住,聚合根完全取决于上下文。因此,在管理“Person”的上下文中,Title 只是一个实体或值对象。在管理“标题”的上下文中,标题可能是聚合根。如果是这种情况,应该有两个单独的“标题”类,每个上下文一个。这是一种变更管理策略,因此在一个上下文中所做的更改不会影响另一个上下文。

    这里有一个例子可能有助于阐明这个主题。请原谅这些名称,因为需要对您的域有更多的了解才能做出更有意义的名称,因此我将出于本示例的目的做出一些假设:

    招聘背景

    public class Person : IAggregateRoot, IPerson 
    {
       public string Forename { get;set; }
       public string Surname{ get;set; }
       public Title Title { get;set; }
       public DateTime? HireDate { get; set; }
       public void Hire(Title granted, IPersonRepository repository)
       {
           Title = granted;  //Grant the new hire this title that we have at our company
           HireDate = DateTime.Now;
           repository.Save(this);
       }
    }
    
    public struct Title /* I like to make my value objects structs */
    {
        public Title (Guid id, string value)
        {
            Id = id;
            Value = value;
        }
        public Guid Id { get; private set; }
        public string FullTitle{ get; private set; }  /* This would be the prefix + value + level when loaded from the repository because, in this context, we don't have any need for that level of separation. */
    }
    

    创建不同级别的新标题的上下文

    public class Title : IAggregateRoot, ITitle
    {
        public string Prefix { get; set; }
        public string Value { get; set; }
        public int Level { get; private set; }
        public void Create(int levelsAvailable, ITitleRepository repository)
        {
            for (int i = 1; i <= levelsAvailable; i++)
            {
                Title title = new Title(Prefix, Value) { Level = i };
                repository.Save(title);
            }
        }
    }
    
    public class Person : IEntityObject, IPerson 
    {
       public ITitle Title { get;set; }
       public string Name { get; set; }
    }
    

    要阐明变更管理策略的原因,请考虑企业是否回过头来表示每个新员工都将获得为期 90 天的试用职称,并且试用职称被定义为 1 级职称。使用上述策略,只需更改一个上下文即可消除其他上下文的任何可能的未知故障。

    希望这会有所帮助!

    【讨论】:

    • 值对象 - 根据定义 - 没有 ID。
    • @MattJohnson 你是对的。我主要把它放在那里,因为那是 OP 最初拥有它的方式。此外,如果您将对象的数据库 ID 与它一起存储,则很多时候会更容易,并且您会获得更好的性能,因此您最终不必在保存对人员所做的更改之前根据值查询 Title ID .就个人而言,当这些情况出现时,我使用“对象”类型来存储我的 ID,并将它们设为私有。这允许我的域代码保持与存储库无关,同时确保应用程序开发人员不会针对数据库 ID 编写代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-07
    • 2018-08-12
    • 2020-03-20
    • 1970-01-01
    • 2016-03-21
    • 1970-01-01
    • 2012-02-19
    相关资源
    最近更新 更多