我们首次尝试在项目中采用域驱动设计。我遇到的问题是实体之间的关联。你是怎么做到的?
说,我有实体Employee
和Contract
,这是一个简单的一对多关联。我该如何建模?
选项1:汇总。
问题:这里的问题是,如果我理解正确,则在创建聚合对象时必须加载聚合中的所有实体。我不能在需要时延迟加载实体,因为它需要从实体引用存储库,这显然是坏的。但每次从数据库中获取所有员工的合同都是一个很大的性能问题。
选项2:使用存储库(例如ContractRepository.GetContractsForEmployee()
)并将EmployeeId
属性添加到Contract
类来获取员工的合同。
问题:很难将任何业务逻辑放入实体中。我想有一个方法,比如Employee.Dismiss()
,但它还需要更新员工的合同。这意味着我需要将这个逻辑放在一个服务中。问题是,我想不到很多逻辑只在Employee
上运行,因此模型会变得有些贫乏,服务中的大部分逻辑都会出现。
你如何处理DDD中的这些问题?
答案 0 :(得分:3)
这只是我对它的看法......不知道你的域名。
首先,here是一个很好的阅读资源(部分关于聚合和根)。
在DDD术语中,Employee
和Contract
都是实体(因为它们都具有身份)。
“聚合围绕一个或多个实体绘制边界。以及:每个聚合都有一个根实体,这是聚合中唯一允许聚合外部任何对象的成员引用。“
问题是:Employee
和Contract
是否形成聚合,Employee
是根实体?显然不是,因为其他域实体也可以引用contract
,并且合同ID是全局唯一的,不仅仅在Customer
内。
因此,考虑到这些规则,Employee
和Contract
都是汇总根。
然后:“只有查询可以直接获得聚合根;所以这意味着每个聚合根应该有一个存储库。”
因此,在这种情况下,我们有一个EmployeeRepository
和一个ContractRepository
。
考虑到所有这些因素,我不会在域模型中添加员工与合同之间的关系;但要单独对待它们。毕竟,如果你需要一个Employee
,你也不一定需要他的contracts
,它们都是不同的方面。
选项2是我选择的:使用ContractRepository
获取您感兴趣的合同。如果需要,您可以添加一个域服务,负责在需要时聚合员工和合同。
如果您还定义了Company
实体,那么解雇员工可能是该实体的工作。
答案 1 :(得分:1)
我们最近也采用了DDD方法。如果我这样做,我会有以下(简化属性简化):
public class Employee() {
String name;
Set<ContractNumber> contracts;
public void addContract(ContractNumber contractNumber) {
this.contracts.add(contractNumber);
}
}
public class Contract() {
ContractNumber contractNumber;
Date effectiveDate;
}
public class ContractNumber() {
String contractNumber;
}
ContractNumber是一个从Employee中引用的值对象。在此示例中,Employee位于BoundedContext中,用于处理Employees及其各自的合同。在其他有限的上下文中可能还有其他Employee表示。
正如其他答案所讨论的那样,会有员工和合同的存储库。
答案 2 :(得分:0)
你需要找到真正的不变量。
在这里你可以有一个不变量,例如:你不能解雇已经被解雇的Employee
。
如果这是唯一真正的不变量,那么你可以制作一个Employee
聚合,它只有关联合同的ID。
Contract
将是另一个聚合(如果需要)。
如果dismiss()方法成功,您可以加载所需的合同并进行必要的更改。