这篇博文的灵感来自于我为我的上一个客户所做的工作,该客户想要模块化他的单体,但有数百个实体的复杂混乱。我们开始消除模块之间的循环依赖,这种依赖特别是由于 jpa 关系及其对服务层的影响而发生的。这篇博文试图解释为什么更简单的映射方法更好。
JPA 是一个很好的工具,可以将您的实体映射到表,但它通过注释的建模范例导致了复杂的意大利面条式混乱。一种更简单、更好的关系建模方法是只使用普通 ID 而不是引用另一个实体。
@Entity(name = "user") |
这种简化的建模方法最重要的好处之一是,如果您将您的实体及其所有字段视为其状态,那么现在很容易意识到组织的状态变化并不意味着用户的状态变化。这听起来很明显,但是如果您一直使用 jpa 关系,您必须知道哪些引用对象是用户实体的一部分,哪些不是。我们正在减少推理用户实体的认知负荷,这反过来又降低了代码库的整体复杂性。
现在可以轻松测试用户实体,而无需创建每个引用的实体。我们将操纵用户内部组织的意大利面条代码的风险降至最低。我们进一步提高了用户的加载性能,因为通常会急切地获取这些类型的多对一关系。
使用普通 ID 应该是您的默认设置。强引用其他实体应该是例外。
强引用参考案例
如果关系是一个实体独有的,您可以使用强引用。
public class Invoice { |
这种建模方式明确了发票项目
InvoiceItem |
必须通过发票实体创建、删除或更新发票项目,然后相应地更新其金额。这也意味着您不应为发票项目提供存储库。您应该通过加载发票来查询项目。
主要区别在于这些项目仅由发票强烈引用,并且此引用专用于发票。更改项目的状态会更改发票的状态。
明确一个实体对另一个实体的所有权。默认使用 id 的弱引用。
视图层的快速提示
有些人可能会建议您应该在视图需要时创建具有强引用的关系。此参数将视图问题强加到您的建模方法中,这会迅速导致N+1 查询问题。它还通过遍历加载数据的强引用来隐藏客户端需求,从而削弱您的服务 api。
// Do not model your relations only to implement these view requirements |
更好的方法如下:
public class UserController { |
现在我们在服务层明确地对查询需求建模,并将我们在领域模型中的耦合保持在最低限度。我们还为我们的客户提供更好的通用 api。随着前端不断变化的需求,我们可以自由地组合我们需要的所有数据,而不必担心因过度获取而导致性能下降。
在此示例中,可能只是删除了在用户列表视图的前端显示组织名称的要求。如果发生这些不断变化的前端需求,我们不必更改后端 api。我们减少了前端和后端需求之间的耦合。