DDD项目的Maven模块布局

7
当进行DDD项目时,您如何布置Maven模块?您是否将所有层(表示层、应用层、领域层、基础设施层)放在单个模块中,还是创建多模块布局,每个层都有一个单独的模块?或者完全不同的东西?
我注意到由公司Domain Language和Citerus开发的DDD Sample App使用单个Maven模块,其中每个层作为该模块内的单独Java包。这是已经确立的最佳实践,还是我应该考虑更细粒度的模块布局?
1个回答

10

通常模块分离和打包是部署和开发实践的问题。还有谁需要使用这段代码?如果我想改变功能Y,它是否都在X包中?

  

注意:示例应用程序被打包为单个应用程序,以便作为学习工具轻松消费。但是,以下是一些推荐使用它作为示例并假装它是“真正的”时的建议。我将在真空中对其进行一些假设,以说明的目的,但负责任的DDD实践者会与领域专家和开发团队进行访谈,以验证关于域、上下文边界和这些上下文之间关系的所有假设。你不能独自完成DDD。

正确建模

第一步是集中精力确保正确建模和定义上下文边界。我不会过多地担心基础架构层,而是会关注域内各种上下文及其模型。示例应用程序中关键的区别在于这些不同上下文之间,该应用程序中有三个上下文:

  • 预订
  • 路由
  • 第三方供应商/港口/船只等

如果你注意到,它们由根java包明确分隔开来:

  • se.citerus.dddsample
  • com.pathfinder
  • com.aggregator

层主要是为了促进这些上下文之间的通信,并将基础架构问题与域工作分开,从而使测试更容易进行,域责任更明确。基础架构很重要,但示例应用程序在此处使用XML,在那里使用JMS,在Hibernate中使用更多次,这些都是次要关注点,域建模才是重点。

示例应用程序非常清楚地表明了这种分离,很容易看出哪些是聚合根

  • Cargo
  • HandlingEvent
  • Location
  • Voyage

按照聚合根将Java包分组是一种可靠的最佳实践。在货物聚合范围之外,单个Leg没有任何意义;在航程聚合范围之外,计划没有任何意义;在HandingEvent聚合范围之外,HandlingHistory没有任何意义。将领域模型与基础设施隔离并使其可测试是一个好习惯。但是您可能不会将这种解耦延伸到模块级别。说所有领域对象都存在于一个JAR文件中,而所有基础架构都存在于另一个JAR文件中并不是一个规则。开发和版本负担可能会变得痛苦。

确定有界上下文

关键在于各个上下文模型如何单独/不共用。在预订上下文中,路线是一条行程线路,具有一系列Legs。在路由领域中,它是Graph,以计算机科学中的真正含义为基础,因此该领域可以使用大学算法课程中学过的遍历图算法解决路由问题。

两个上下文,预订和路由,处于紧密的合作关系,它们在两个模型之间维护一个共享接口,包括Edges和Nodes以及Itineraries和Legs。这种模型之间的翻译在ExternalRoutingService中进行管理,那里TransitPath变成了Itinerary。显然,这是一个非常关键的集成点,应该在测试中得到很好的覆盖,并通过持续集成进行管理。

另一个上下文是第三方集成,用于向应用程序报告有关货物状态的HandlingEvents。这通过一种称为发布语言的模式实现。简而言之,我们不关心第三方的货物模型长什么样子,只要他们按照我们定义的发布XML规范HandlingReport为我们报告处理事件即可。

第三方上下文与预订领域之间的关系被称为服从者,他们按照我们定义的规范提交数据,我们不会改变我们的模型使其更容易适应他们。他们需要遵守我们的规范。话虽如此,这只是我对情况的猜测,事实上可能存在一个非常重要的供应商,他们实际上定义了XML模型而不是我们。只有通过对虚构团队的采访才能真正表征这一点。

总的来说,将与聚合相关的所有类(例如在同一个包中)紧密分组。明确定义上下文边界,并确保有清晰的集成点,定义上下文伙伴关系、共享内核、发布语言、开放主机服务、遵从者等关系。

基于这一点,在示例中,我们可以将各种上下文封装到单独的maven模块中,例如货物预订、路径查找和事件处理聚合。如果在开发方法论和团队组织的实践情况下有意义,并且只有在这种情况下才这样做。

在有界上下文中查找模块

正确设置上下文边界。正确地将聚合定义为良好垂直性质。减少耦合以清晰定义接口。

在您的上下文中查找模块。它们是最自然的单独模块的候选项,而分离可能有助于更严格地执行和记录上下文边界。但是,像许多软件设计一样,这不是一个硬性规则,这真的取决于具体情况。我可以设想并见过/编写了应用程序,它们具有不同的写入模型和读取模型(考虑规范化和反规范化,例如报告),每个上下文都可能仍然打包在单个模块中。

另一个要点是小心共享聚合根,这是DDD共享核心模式,应该非常谨慎地使用,因为它可能很快演变成大型混乱的域模型,无法满足任何上下文的需求。请注意,示例应用程序不在RoutingService和BookingService之间共享模型。将领域的所有聚合根放在单个模块中可能会无意中鼓励这种实践。


1
感谢您提供了一份详尽且有趣的答案。您提到上下文和聚合根的重要性是很有道理的。然而,您并没有真正回答问题:是否有已经建立的最佳实践方法来将DDD应用程序分成Maven模块(项目)?从我的经验来看,之后改变模块布局并不容易。虽然这并不需要什么高深技术,但需要大量繁琐的文件和目录移动、pom.xml文件的重新结构化以及确保版本控制历史记录得到正确保存等工作。 - markusk
1
@markusk 抱歉,我使用 trivial 的意思并不是没有工作,而是指对模型没有更改。这个问题无法一般化回答。上下文是查找模块的最佳位置,没有两个应用程序会共享相同的上下文映射。因此,这方面没有最佳实践。绘制上下文映射,并查看它们是否作为单独的模块有意义,这取决于您的团队和开发过程。如果只有一个上下文,则一个 maven 模块就足够了,添加更多模块只会创建更多需要管理的依赖项。 - Kyri Sarantakos
谢谢您的澄清。您使用每个上下文一个单独模块的方法对我来说很有道理。现在要说服我的同事们了。 :-) - markusk
@markusk 我已经编辑了答案,更明确地推荐了那个。 - Kyri Sarantakos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接