域驱动设计:如何处理不同有界上下文中的用户实体?
我有一个关于域驱动设计的问题。在我的应用程序的用户帐户/配置文件有界的上下文中,存在具有帐户信息(用户名,用户名,密码,电子邮件,盐等)和配置文件信息(全名,化身,生日,性别等)的用户实体。我还有另一个工作岗位/申请的有限背景,其中每个岗位都有一个雇主用户,每个岗位申请都有一个申请人用户。域驱动设计:如何处理不同有界上下文中的用户实体?
问题是,如果雇主/申请人用户在工作有界的上下文是我用于用户帐户有界上下文的用户实体?还是应该为雇主和申请人设计不同的用户类型实体?
如您所见,只有来自帐户有界上下文的信息(如id,全名,电子邮件和头像)才能在作业界限上下文中相关。如果雇主/申请人与账户/用户档案中的用户实体相同,则会加载更多无用的数据(不需要知道雇主/申请人的用户密码)。但是,如果我为它们创建不同的实体类,它将使数据持久性更加棘手,因为在不同实体类中所做的更改可能会更改同一数据库表中的相同数据。
您认为如何?我应该为所有人使用一个用户实体还是针对不同的有界上下文/聚合使用不同的用户实体?如果后者是可取的,我该如何处理数据/实体持久性?
这是一个老问题,你可能以某种方式解决了这个困境,但我会尽力回答它。
您写道:
“如从 帐户界上下文ID,全名,电子邮件地址和头像信息仅仅是在作业限界上下文相关的”
它们真的有关BC工作?使用实体(甚至更多:使用Aggregates),您正在建模进程,而不是数据。 Job BC的哪个流程或用例需要全名?在域名中是否有要求具有特定姓氏或名字的人不应被雇用?我不假设。通过这样说,你可能已经记住了,你必须展示一些人物姓名显示的屏幕。但屏幕不是进程,它们只是报告。您不应该通过报告/屏幕/ UI来驱动您的模型,而是通过特定域中存在的进程来驱动您的模型。
好吧,但如何做到这一点?显然你仍然需要生成一些报告/屏幕。你不是吗?答案是:您需要CQRS(https://martinfowler.com/bliki/CQRS.html)。命令堆栈只是聚集等达到的进程模型,所以你的构建块会被一些ORM持久化。他们的数据模型将由他们(构建块)的外观如何驱动。在这里使用一些ORM,可以很容易地坚持复杂的聚合,例如Hibernate。
然后,你必须建立查询堆栈。我看到两种实现方法,它们取决于两个BC是否在同一个数据库模式中。
当两个BC位于同一个模式中时,只需使用一些数据库视图即可为您的报告/屏幕构建数据。使用一些非常快速和灵活的复杂查询ORM,例如MyBatis或Spring JDBC(如果您想要忍受JDBC愤怒,甚至可以使用普通JDBC))。如果你没有被迫去使用Hibernate,不要在这里使用Hibernate,因为你会发现在这个堆栈中尽可能接近SQL是件好事。数据查询抽象将迫使您与已使用的框架/ ORM进行斗争,以实现由Aggregates及其流程驱动的数据模型的复杂连接,而不是屏幕。另一个原因是,在普通的商业应用程序中,读取数据比写入数据库多几个数量级,在CQRS中讲,查询堆栈的用法比命令堆栈的用法多,因此您需要快速的操作。
当BC级在不同的模式,你需要生成报告数据库(https://martinfowler.com/bliki/ReportingDatabase.html),将“合并”两种模式。然后,您可以对合并的模式进行查看,并将问题简化为上述问题。
请注意,报告数据库为您提供了另一种可能性缩放:这个数据库是只读的,所以它可以在多个服务器之间很容易被复制,您的查询堆栈服务可以相乘,并且隐藏在一些负载均衡。
好,但与电子邮件地址属性是什么?您的域中可能有一个用例,应该通知雇主或申请人有关在域上完成的某些操作/过程。 我认为,域名服务(或域事件处理函数)处理这种使用情况应该询问其他BC的服务为这个特定用户的电子邮件(或广播域事件将被处理的地方)。或者更好 - 它应该将这项工作委托给另一个BC省的一些Notification Service。此通知服务将询问BC的帐户服务的电子邮件地址。
在我看来界上下文(与通用语言一起)是DDD的最重要的思想。这是在建模过程中避免泥球的真正强大工具。它是真的不容易确定实际域:)
你的限界上下文共享同一个数据库界上下文?理想情况下,他们不应该。如果他们这样做,那么你需要明确哪些BC拥有哪一部分数据,并且只允许拥有BC来更改该数据。 – plalx
我不明白你在说什么。它是相同的应用程序,只是不同的有界上下文。我总是使用相同的数据库,除非它有不同的应用程序。 –
BC通常被部署为微服务并拥有自己的DB。 – plalx