DDD 项目的 Maven 模块布局

发布于 2024-10-24 12:49:33 字数 292 浏览 4 评论 0原文

在进行 DDD 项目时,您如何布局 Maven 模块?您是否将所有层(表示层、应用程序、域、基础设施)放入一个模块中,还是创建一个多模块布局,每个层都有一个单独的模块?或者完全是别的什么?

我注意到由 Domain Language 和 Citerus 公司开发的 DDD Sample App 使用单个 Maven 模块,每个层作为该模块内的一个单独的 Java 包。这是既定的最佳实践,还是我应该考虑更细粒度的模块布局?

How do you layout your Maven modules when doing DDD projects? Do you fit all layers (presentation, application, domain, infrastructure) in a single module, or do you create a multi-module layout with a separate module for each layer? Or something else entirely?

I notice that the DDD Sample App, developed by the companies Domain Language and Citerus, uses a single Maven module, with each layer as a separate Java package inside that module. Is this the established best practice, or should I consider a more granular module layout?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

匿名。 2024-10-31 12:49:33

一般来说,模块分离和打包是一个部署和开发实用性的问题。还有谁需要该代码?如果我想更改功能 Y,是否全部都在包 X 中?

警告:示例应用程序是
打包为单个应用程序以使其变得简单
作为学习工具来消费。但
这里有一些使用它的建议
作为一个例子并假装它是
真实为了说明的目的,我会在真空中对此做出一些假设,但是负责任的 DDD 实践者会找一位领域专家并采访开发团队,以验证有关领域、上下文的任何和所有假设边界以及这些上下文之间的关系。 你无法独自完成 DDD。

正确建模

第一步是专注于正确建模和定义上下文边界。我不会太担心基础设施层,而是会担心域内的各种上下文及其模型。示例应用程序中的关键区别在于这些不同上下文之间的区别,此应用程序中有三个上下文

  • 预订
  • 路由
  • 第 3 方供应商/港口/船舶等

如果您注意到这些上下文被根 java 包

  • se.citerus.dddsample
  • com 清楚地分隔开。 pathfinder
  • com.aggregator

这些层主要是为了促进这些上下文之间的通信,并将基础设施问题与域工作分开,使测试更容易,域职责更明确。基础设施很重要,但事实上示例应用程序在这里使用 XML,在那里使用 JMS,在那里使用 hibernate,这是正在进行的域建模的次要问题。

示例应用程序使这种分离非常清晰,很容易看出聚合根在哪里:

  • 货物
  • 处理事件
  • 位置
  • 航行

通过聚合根分解java包是可靠的实践。航段意味着货物聚合之外没有任何内容,时间表意味着航行聚合之外没有任何内容,处理历史意味着处理事件聚合之外没有任何内容。保持领域模型与基础设施隔离并可在没有基础设施的情况下进行测试是一个很好的实践。但您可能不会将这种解耦扩展到模块级别。将所有域对象放在一个 jar 中并将所有基础设施放在另一个 jar 中并不是一条规则。开发和版本负担可能会变得痛苦。

识别有界上下文

关键是各种上下文的模型如何分离/不共享。在预订上下文中,路线是具有一组航段的行程。在路由域中,它是真正的计算机科学意义上的图,因此该域可以使用大学算法课上经过充分研究的图遍历算法来解决路由问题。

Booking 和 Routing 这两个上下文处于紧密的合作伙伴关系,它们都在 Edges 和 Nodes 以及 Itineraries 和 Legs 两个模型之间维护共享接口。这种转换是在外部路由服务中管理的模型之间维护的,在外部路由服务中,TransitPath 成为行程。显然,这是一个非常关键的集成点,应该在测试中得到很好的覆盖,并通过持续集成进行管理。

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

第三方上下文和预订域之间的关系称为“Conformist”,他们按照我们定义的规范提交数据,我们不会改变我们的模型以使他们更容易做到这一点。他们有责任服从我们。也就是说,这是我对情况的猜测,可能有一个非常重要的供应商,而且他们实际上定义了 XML 模型,而不是我们。只有对虚构团队的采访才能真正描述这一点。

因此,总的来说,将与聚合密切相关的所有类分组(可能是同一包)。明确定义上下文边界并确保有一个明确的集成点,定义上下文之间的关系合作伙伴、共享内核、发布语言、开放主机服务、一致性等。

基于示例中的内容,我们可能可以将各种上下文打包在单独的 maven 中用于货物预订、路线路径查找和处理事件聚合的模块。考虑到开发方法和团队组织的实际情况,这是否有意义,而且只有情况确实如此。

在限界上下文中查找模块

获取您的上下文边界权限。将您的聚合正确定义为良好的垂直方向。减少耦合以明确定义良好的接口。

在您的上下文中寻找模块。它们是单独模块的最自然候选者,并且分离可能有助于更严格地执行和记录上下文边界。然而,就像许多软件设计一样,这并不是一个硬性规定,并且实际上取决于具体情况。我可以设想并且已经看到/编写了具有不同写入模型和读取模型的应用程序(认为标准化和非标准化用于报告)以及每个模型的上下文,对于实际问题可能仍然打包在单个模块中。

另一点要警惕在上下文之间共享聚合根,这是 DDD 共享内核模式,应该非常谨慎地使用,因为它可以快速陷入一个大型混乱的领域模型,而无法满足任何上下文都很好。请注意,示例应用程序不会在 RoutingService 和 BookingService 之间共享模型。将所有域的聚合根放在单个模块中可能会无意中鼓励这种做法。

Generally Module separation and packaging is a question of deployment and development practicalities. Who else is going to require the code? If I want to change functionality Y, is it all in package X?

Caveat: the sample app is
packaged as single app to make it easy
to consume as a learning tool. But
here are some reccomendations using it
as a example and pretending it's
real. I will make some assumptions about it in a vacuum for illustrative purposes, but a responsible practitioner of DDD would grab a Domain Expert and interview the development team to validate any and all assumptions about the domain, context boundaries and the relationship between those contexts. You can't do DDD alone.

Get the Modeling Right

Step one would be to focus on getting the modeling right and the context boundaries defined. I would not worry about infrastructure layers so much as I would worry about the various Contexts and their models within the domain. The critical distinctions in the Sample App are those between these different contexts, there are three contexts in this app

  • Booking
  • Routing
  • 3rd Party Vendors/Ports/Ships etc

If you notice those are clearly separated by the root java packages

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

The Layers are mostly to facilitate communication between these contexts and to separate infrastructure concerns from the domain work making testing easier and domain responsibilities more plain. Infrastructure is important but the fact the sample app is using XML here and JMS there and hibernate over there are secondary concerns to the domain modeling going on.

The example app makes this separation very clear, it's easy to see where the Aggregates Roots are:

  • Cargo
  • HandlingEvent
  • Location
  • Voyage

Breaking up the java packages by Aggegrate Roots is solid practice. A Leg means nothing outside the Cargo aggregate, a schedule means nothing outside of the Voyage aggregate, a HandlingHistory means nothing outside the HandingEvent aggregate. Keeping the domain model isolated from and testable without infrastructure is a good practice. But you'd probably not extend that decoupling to the module level. It would not be a rule to say have all your domain objects in one jar and all your infrastructure in another. Development and version burdens might become painful.

Identify Bounded Contexts

The key is how the various context's models are separate/unshared. In the booking context a route is an Itinerary with a set of Legs. In the routing domain it's a Graph in the real Computer Science sense of the word, so that domain can solve routing problems using well studied graph traversal algorithms right out of your Algorithms class from college.

The two contexts Booking and Routing are in a close Partnership where they both maintain a shared interface between the two models of Edges and Nodes and Itineraries and Legs. This translation is maintained between model is managed in the ExternalRoutingService that's where a TransitPath becomes an Itinerary. Obviously that is a very critical integration point that should be well covered in tests and managed through Continuous Integration

The other context is 3rd party integration to report HandingEvents to the app about cargo status. This is achieved through a pattern called Published Language. In a nutshell we don't care what the cargo models of 3rd parties look like, as long as they report handling events to us following our Publish XML spec HandlingReport.

The relationship between the 3rd party context and the Booking Domain is called Conformist, they submit data following our defined specification we do not alter our models to make this easier for them. The burden is on them to conform to us. That said, that's my guess of the situation, it could be that there's a very important vendor out there and they are in fact defined the XML model not us. Only interviews with the fictional team could really characterize that.

So in summary group all classes related to an Aggregate closely (same package perhaps). Define Context boundaries clearly and ensure there's a clear integration point, define the relationship of the contexts Partnership, Shared Kernel, Published Language, Open Host Service, Conformist, etc.

Based on that in the example we could probably package the various contexts in separate maven modules for Cargo Booking, Route Path Finding and HandlingEvent Aggregation. If that made sense given the practicals of the development methodology and team organization, and only if that was the case.

Look for Modules in Bounded Contexts

Get your context boundaries rights. Define your Aggregates correctly into nice verticals. Reduce coupling to clear well defined interfaces.

Look for modules in your contexts. They are the most natural candidates for separate modules and the separation may help more strictly enforce and document context boundaries. However like so much of software design it's not a hard and fast rule and will really depend on the specific case. I can envision and have seen/wrote applications that have different write models and read models (think normalized and denormalized for say reporting) and contexts for each, that for practical matters might still be packaged in a single module.

Another point be wary of sharing Aggregate Roots between contexts, this is the DDD shared kernel pattern and should be used very sparingly as it can quickly spiral into a large messy domain model that doesn't serve the needs of any context well. Note that the example app does not share models between the RoutingService and the BookingService. Putting all of domain's aggregates roots in a single module might inadvertently encourage this practice.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文