主体域建模
一种方法是成熟的对象关系映射。 实体之间相互直接引用,并且当您遍历对象图时,O / R映射器会自动为您加载数据。 要获得OrderLine
Product
,只需调用line.getProduct()
即可。 方便且看似透明,但是如果您不够谨慎的话,很容易损害性能。
另一种方式是该帖子可能称为面向文档的映射。 每个实体都有其ID和自己的数据。 如果它是聚合根(以域驱动的设计术语),则它可能具有一些嵌套实体。 在这种情况下, OrderLine
仅具有productId
,如果要获取产品,则必须调用ProductRepository.getProduct(line.getProductId())
。 它不太方便,需要更多的仪式,但是由于它的明确性,它也更容易优化或避免性能陷阱。
前面提到的帖子太多了。 我最近有机会在一个真实的例子中对这个问题进行更多的思考。
案子
当我着手为一个具有200多个Hibernate映射和大约300个表的相当大的系统创建一个边项目时,曙光初现。 我知道我只需要5个核心表,但是为了保持一致性并避免重复,我想重用大系统中的映射。
我知道对不需要的东西可能会有更多的依赖,并且我没有生成依赖图的工具。 我只是包括了第一个映射,观看了未映射实体的Hibernate错误,添加了映射,再次检查了错误日志……等等,直到Hibernate很高兴知道所有引用的类。
完成后,我的辅助项目中绝对最小且必要的“核心”具有110个映射。
当我添加它们时,我看到它们中的大多数都离核心和我的需求很远。 它们对应于边缘上某个地方的小子系统。
当我只需要两个钉子时,就像在一块杂乱无章的工作场所中强力吸引所有金属物品。
痛点
事实证明,这种面向对象的痛苦多于好处。 分拆重用核心中有不必要的依赖关系只是一个痛苦点,但是还有更多的痛苦。
这也使我的辅助项目变慢了,并且使用了太多的资源–我必须映射100多个实体,并在第二级缓存中支持它们。 当我加载一些核心实体时,我还拉出了许多我不需要的东西:在狭窄上下文中使用的许多字段,甚至是热切地加载的整个实体。 在任何时候,我周围都有太多的数据。
这样的模型也使开发速度大大降低。 构建和测试需要更长的时间,因为要生成的表更多,要扫描的映射等等。
由于另一个原因,它也会变慢:如果域类引用了其他20个类,那么开发人员如何知道哪些重要而哪些不重要? 在任何情况下,它都可能导致很长的课堂时间,有些令人不快。 应该是核心的东西变成了一个吞噬整个宇宙的巨大黑洞。 当一个不知道新手的人走近时,大多数时候他要么沉迷于尝试去理解一切,要么干脆破坏一些东西–不知道他上下文中的所有链接,无法理解班级中存在的所有链接。 实际上,即使是老年人,也可能会犯这样的错误。
该列表可能更长。
解?
这里有两个问题。
那是怎么发生的?
我正在编写一段与核心相距甚远的代码,但实际上可以在该核心实体上使用这两个新属性。 最快的方法是什么? 显而易见:将两个新字段添加到实体。 做完了
我需要为与核心实体密切相关的新用例添加一堆新实体。 最短的路径? 简单,只需从核心引用几个实体即可。 当我需要这些新对象并且已经有了旧的核心实体时,Hibernate会在我称为吸气剂的同时为我加载新实体。 做完了
听起来很自然,我可以看到几年前如何犯这样的错误,但是这种趋势可能已经停止甚至逆转了。 通过适当的代码审查和回顾,团队可能会更早找到更好的方法。 具有一些懈怠和良好的意愿,它甚至可以重构现有代码。
有更好的方法吗?
让我们回到开头部分,介绍两种映射域类的方法:“全面的ORM”与文档/聚合样式。
今天,我相信成熟的ORM对于具有几个紧密相关的用例的相当小的项目可能是一件好事。 一旦我们扩展出新的更大的功能块并引入更多的对象,它们就应该成为它们自己的集合。 即使它们本身可能绕着轨道运行并与核心直接链接,也不应从核心引用它们。 核心实体的属性也是如此:如果在遥远的用例中需要某些东西,请不要用新的字段破坏核心映射。 如有必要,甚至可以引入一个新实体。
换句话说,从领域驱动的设计中学习。 如果您还没有读过Eric Evans的书,请现在就去做。 这可能是我迄今为止读过的最有价值和最有影响力的软件书。
别忘了分享!
参考: 域建模:来自我们的JCG合作伙伴 Konrad Garus的Naive OO Hurts在Squirrel的博客上。
翻译自: https://www.javacodegeeks.com/2012/09/domain-modeling-naive-oo-hurts.html
主体域建模