我对域驱动设计有疑问。在我的应用程序的用户帐户/配置文件有界上下文中,有一个用户实体,其中包含帐户信息(ID,用户名,密码,电子邮件,盐等)和个人资料信息(全名,头像,生日,性别等)。我还有另一个有效的工作岗位/应用上下文,其中每个工作岗位都有雇主用户,每个工作申请都有申请人用户。
问题是,作业有限上下文中的雇主/申请人用户是否应该是我用于用户帐户有限上下文的相同用户实体?或者我应该为雇主和申请人设计不同的用户类型实体?
如您所见,只有来自帐户有限上下文的ID,全名,电子邮件和头像等信息才与作业有限的上下文相关。如果雇主/申请人是来自帐户/用户档案的相同用户实体,则它将加载更多无用的数据(不需要知道雇主/申请人的用户密码)。但是如果我为它们创建不同的实体类,它将使数据持久性更加棘手,因为在不同实体类中进行的更改可以更改同一数据库表中的相同数据。
你怎么看?我是否应该为不同的有界上下文/聚合使用一个用户实体用于所有或不同的用户实体?如果需要后者,我该如何处理数据/实体持久性?答案 0 :(得分:5)
这是一个古老的问题,你可能会以某种方式解决这个困境,但我会尝试回答它。
您写道:
“只有来自的ID,全名,电子邮件和头像等信息 帐户有界上下文与工作有限上下文相关“
他们真的与卑诗省工作有关吗?使用实体(甚至更多:使用聚合),您可以建模进程,而不是数据。在Job BC的哪个过程或用例中你需要全名吗?在域中是否要求不应使用具有特定名字或姓氏的人员?我假设没有。通过这么说,你可能想到,你必须显示一些显示一个人的全名的屏幕。但是屏幕不是流程,它们只是报告。您不应该通过reports / screens / UI驱动模型,而应该通过特定域中存在的进程来驱动模型。
好的,但该怎么做?显然你仍然需要生成一些报告/屏幕。不是吗?答案是:您需要CQRS(https://martinfowler.com/bliki/CQRS.html)。命令堆栈只是Aggregates等所达到的进程模型。因此,您的构建块将由某些ORM持久化。它们的数据模型将由它们(构建块)的外观驱动。在这里使用一些ORM,甚至可以轻松地持久化复杂的聚合,例如Hibernate。
然后你必须建立查询堆栈。我看到了两种实现它的方法,如果你在同一个数据库模式中同时存在两个BC,它们就会依赖它们。
当两个BC位于同一模式时,只需使用一些数据库视图来构建报告/屏幕的数据。使用一些非常快速和灵活的复杂查询ORM在这里像MyBatis或Spring JDBC(如果你想与JDBC愤怒斗争,甚至是简单的JDBC))。如果你不被迫,不要在这里使用Hibernate,因为你会发现在这个堆栈中尽可能接近SQL是件好事。数据查询抽象将迫使您努力使用已使用的框架/ ORM来实现由聚合及其进程而非屏幕驱动的数据模型的复杂连接。另一个原因是,在普通的业务应用程序中,读取数据比读取数据库要多几个数量级,在CQRS中,查询堆栈的使用比命令堆栈的使用更多,所以你需要快速的东西。
当BC位于不同的模式时,您需要构建将“合并”两个模式的报告数据库(https://martinfowler.com/bliki/ReportingDatabase.html)。然后,您可以对合并的架构进行查看,并将问题简化为上述问题。
请注意,报告数据库为您提供了另一种扩展可能性:此数据库是只读的,因此可以在多个服务器之间轻松复制,您的查询堆栈服务可以成倍增加并隐藏在某些Load Balancer后面。
好的,但是电子邮件地址属性是什么?您可能在您的域中有一个用例,应该通知雇主或申请人在域上执行的某些操作/流程。 我认为处理此用例的域服务(或域事件处理程序)应该向其他BC服务询问此特定用户的电子邮件(或将在某处处理的广播域事件)。甚至更好 - 它应该将这项工作委托给另一个BC的一些通知服务。此通知服务将询问帐户BC的服务电子邮件地址。
在我看来,有界上下文(以及无处不在的语言)是DDD最重要的概念。这是在建模过程中避免泥球大球的强大工具。在真实域中确定有界上下文并不容易:)