我正在尝试使用ddd实践设计我的网络应用程序。 此应用程序处理存储位置的容器存储。容器包含物质。最有可能的是,用户将搜索某种物质并想知道在哪个位置找到容器。此外,他们希望清点存储位置,即获取该存储位置的所有容器。
这就是我将物质,容器和storageLocation识别为聚合的原因。我了解到,其他聚合不应该直接引用,而应该通过主键引用。现在,我想知道在我的域层中确保引用完整性的最佳方法是什么(即没有引用指向不存在/错误的容器),例如,删除容器时,因为physical和storageLocation引用了容器。我们假设所有引用都是双向的。 我大多害怕“忘记”向实体添加适当的方法,这些方法可能会在项目的后期添加。编程时,我不确定这是否是一个“有效”的问题。
这些是我的实体:
@Entity
public class Substance{
@Id
@GeneratedValue
private Long id;
@ElementCollection
List<Long> ContainerIds;
public void addContainer(Container c){containerIds.add(c.getId())}
public void removeContainer(Container c){// removes c.getId() from list}
}
@Entity
public class Container{
@Id
@GeneratedValue
private Long id; //+get
private Long substanceId; //+ get set
private Long storageLocationId; //+ get set
}
@Entity
public class StorageLocation{
@Id
@GeneratedValue
private Long id;
@ElementCollection
private List<Long> containerIds;
public void addContainer(Container c){containerIds.add(c.getId())}
public void removeContainer(Container c){// removes c.getId() from list}
}
现在,我是我的控制器,我必须从存储库中获取Substance和StorageLocation实体,从中删除容器ID引用,然后删除容器:
public class Acontroller{
private ContainerRepository containerRepository; // constructor injected
private SubstanceRepository substanceRepository; // constructor injected
private storageLocRepository storageLocRepository; // constructor injected
public void deleteContainer(Container c){
Substance sub = substanceRepository.getByID(c.getSubstanceId());
sub.removeContainer(c);
//The same for the storageLocation
containerRepository.removeContainer(c);
}
}
每当我向Container添加另一个entityReference时,我将不得不扩展控制器方法。
这种“手工”管理参考的方式是否可以接受。如果没有,我将如何保留id的引用呢?或者我应该忘记id并只使用对象引用?
ps:首先提出问题,所以请温柔地告诉我,让我知道如何改变这个问题。答案 0 :(得分:3)
Udi Dahan:Don't Delete -- Just Don't
此应用程序处理存储位置的容器存储。容器包含物质。最有可能的是,用户将搜索某种物质并想知道在哪个位置找到容器。此外,他们希望清点存储位置,即获取该存储位置的所有容器。
这就是我将物质,容器和storageLocation识别为聚合的原因。
这就是问题#2。聚合的动机是保护您的业务不变性。每个汇总负责验证任何建议的变更。分析查询并不会告诉您有关更改的任何信息,因此它不会告诉您有关可以放置边界的任何信息。
问题#1是你似乎在建模库存,现实世界而不是模型是“记录簿” - 你不能通过调用一个真正的储藏室来移除一个真实的容器“删除”方法。如果您将在线购物车(亚马逊?)的模型与购物应用程序的模型进行对比,您可以看到不同之处:点击删除任意多次,麦片盒不会跳出您的购物车。< / p>
但要解决你提出的问题;如果实体X必须满足需要存在实体Y的不变量,则X和Y必须是同一聚合的一部分(可以立即拒绝任何违反不变量的提议变更)或者你必须放松不变量和允许模型最终协调X的状态与不再可用的Y之间的冲突。
答案 1 :(得分:2)
假设所有引用都是双向的。
我认为这可能是您需要质疑的第一个假设。在对域实体进行建模时,最好考虑它们参与的操作以及在这些操作期间需要强制执行的不变量。如果这些操作和不变量不需要双向引用,请不要对它们进行维护。
e.g。在您的情况下 - 根据您的域和不变量,您可能能够摆脱单向接口 - 例如也许substance
拥有containerId
而container
拥有storageLocationId
第5章&#34;软件中表达的模型&#34; Eric Evan's book对这一主题进行了很好的讨论,包括明确揭穿通常的第一案例假设,即引用必须是双向的。
扩展@ VoiceOfUnreason的答案和Udi Dahan的博客,了解用户在要求删除内容时的意思非常重要。在你的情况下 - 要问几个问题:
有时看起来像不变量的东西,并不是绝对,积极的,必须始终强制执行的东西。例如在不太可能的情况下,如果您通过以上所有问题确实发现您确实需要删除容器,那么从物质的角度处理删除的后果会有轻微的延迟会发生什么?
您是否可以发布域事件ContainerDeleted
并在该事件的处理程序中识别所有相关物质并执行需要做什么?例如将它们标记为“未包含”&#39;或者你域中的任何有意义的东西。
这可以让你通过专注于那些真正不变量的东西来保持聚合很小--Vaughn Vernon的Effective Aggregate Design非常适合探索这个概念。
有时通过分析和知识处理&#39;您可以识别模型中隐藏的概念,当明确显示并明确建模时,可以简化您的模型和业务流程。例如在你的情况下,一些可能有用的东西:
ContainerPlacement
:
ContainerPlacement
的集合ContainerPlacement
可以只包含对containerId的引用,也可能包含强制执行storageLocation必须维护的不变量所需的任何属性 - 例如也许它拥有一个容器容量值对象的副本,以允许强制执行不变量,并且不会在我身上放置比我更适合的容器#34;在storageLocation聚合上,同时保留容器的大多数其他属性(例如颜色,服务日期等)作为容器聚合的责任。substance
呢?多个容器可以包含相同的物质吗?即如果物质是“水”。多个容器可以装水吗?一个容器中的水和另一个容器中的水有区别吗?
substance
作为一个实体存在差异 - 保持物质的名称和其他属性 - 粘度,密度等,substance
作为一个值对象 - 代表体积或容器内物质的数量。ContainedSubstance
- 定义了一个physicalId和一个卷。如果容器中可以包含多种物质,您可以将其建模为这些价值对象的集合。您的一些要求实际上是查询要求 - 域模型不存在以满足查询要求 - 仅用于在更改下强制执行不变量。
您可能会发现即使使用上述问题显示的关联建模,您也可以使用域模型的关系数据库持久性来满足您的查询 - 但如果没有,您还可以考虑维护单独的read model到促进查询,同时保留您的域模型用于维护其不变量。