Domain driven design 聚合和根聚合创建的DDD有效建模

Domain driven design 聚合和根聚合创建的DDD有效建模,domain-driven-design,aggregate,Domain Driven Design,Aggregate,我们正在启动一个新项目,我们渴望应用DDD原则。该项目使用dotnetcore,efcore为sqlserver提供持久性 域的初始视图 我将使用一个任务跟踪器的示例来说明我们的问题和挑战,因为这将遵循类似的结构 一开始,我们理解如下:- 我们有一个项目 用户可以关联到项目 项目具有工作流 工作流具有任务 用户可以针对任务发布评论 用户可以更改任务的状态(正在进行、完成等) 项目,以及相关的工作组和任务最初是从模板创建的 最初的设计是一个大型集群聚合,项目是包含项目用户和工作流集合的根聚合,

我们正在启动一个新项目,我们渴望应用DDD原则。该项目使用dotnetcore,efcore为sqlserver提供持久性

域的初始视图 我将使用一个任务跟踪器的示例来说明我们的问题和挑战,因为这将遵循类似的结构

一开始,我们理解如下:-

  • 我们有一个项目
  • 用户可以关联到项目
  • 项目具有工作流
  • 工作流具有任务
  • 用户可以针对任务发布评论
  • 用户可以更改任务的状态(正在进行、完成等)
  • 项目,以及相关的工作组任务最初是从模板创建的
最初的设计是一个大型集群聚合,项目是包含项目用户工作流集合的根聚合,工作流包含任务等集合

这种方法显然会导致许多争用和性能问题,因为必须加载整个项目聚合以获取该聚合中的任何更改

无论正确与否,我们的下一次修订是将评论从聚合中分离出来,并使用评论作为根来形成一个新的聚合。这样做的动机是,业务部门设想针对每个任务提出大量评论

由于每个注释都与任务相关因此注释需要将外键保留回任务
。但是,按照只能通过其根引用另一个聚合的原则,这是不可能的。为了克服这个问题,我们将任务分解为另一个集合。这似乎也满足了任务可以由不同的人完成的需求,并且同样可以减少争用

然后,我们在从任务工作流的引用中遇到了同样的问题,任务所属的属性导致我们创建一个新的工作流聚合,任务中的外键返回到工作流

结果是:-

  • 一个项目聚合,它只包含分配给该项目的用户列表
  • 包含项目外键的工作流聚合
  • 包含项目外键的任务聚合
  • 包含返回到任务的外键的注释聚合
项目有一种方法可以创建一个新的工作流实例,允许我们设置外键。即略为简化的版本

public class Project()
{
    string _name { get; private set;}
    public Project(Name)
    {
         _name = Name;
    }
    public Workstream CreateWorkstream(string name)
    {
        return new Workstream(name, Id);
    }

    ....+ Methods for managing user assignment to the project
}
以类似的方式,Workstream有一个创建任务的方法

public class Workstream()
{
    string _name { get; private set;}
    public int ProjectId { get; private set; }

    public Workstream(Name, Id)
    {
         _name = Name;
         _projectId = Id;
    }
    public Task CreateTask(string name)
    {
         return new Task(name, Id);
    }

    private readonly List<Task> _activities = new List<Task>();
    public IEnumerable<Task> Activities => _activities.AsReadOnly();
}
公共类工作流()
{
字符串_name{get;private set;}
public int ProjectId{get;private set;}
公共工作流(名称、Id)
{
_名称=名称;
_projectd=Id;
}
公共任务CreateTask(字符串名称)
{
返回新任务(名称、Id);
}
私有只读列表_活动=新列表();
public IEnumerable Activities=>_Activities.AsReadOnly();
}
  • 添加Activities属性纯粹是为了在使用实体构建读取模型时支持导航
团队对这种方法感到不舒服,有些地方感觉不对劲。主要关注点是:-

  • 人们认为,在逻辑上创建项目应该是创建项目,向项目中添加一个或多个工作流,向工作流中添加任务,然后让EF处理持久化该对象结构的问题
  • 令人不安的是,必须首先创建项目,并且开发人员需要确保项目被持久化,以便获得一个Id,以便在调用创建模板的方法时准备就绪,该方法依赖于外键的Id。将这方面的责任推给域服务CreateProjectFromTemplate()中的一个方法来协调各个存储库中单独对象的创建和持久化,可以吗
  • 创建新工作流的方法是否在正确的位置
  • 实体用于形成用于创建读取模型的查询(导航属性支持)。可能需要考虑的是,对象结构正受到以只读方式显示数据的方式的影响

我们现在正处在一个循环的阶段,我们真的需要一些建议来给我们一些方向。

这里有一个不同的视角,可能会推动你走出僵局

我觉得你在做数据建模,而不是真正的领域建模。您关心的是将使用ORM(EF)直接持久化的关系模型,而不是实际问题域。这就是为什么您担心项目将加载太多的内容,或者哪些对象将保存哪些内容的外键

另一种方法是暂时忘记坚持,专注于事情可能需要什么样的责任。关于责任,我指的不是像保存/加载/搜索这样的技术性东西,而是领域定义的东西。如创建任务、完成任务、添加注释、,
{
    taskId: 67890,
    projectId: 12345
}