在本文中,我们将重新讨论关键的领域驱动设计 (DDD)概念,并演示如何使用jMolecules将这些技术问题表达为元数据。
我们将探讨这种方法如何使我们受益,并讨论 jMolecules 与 Java 和 Spring 生态系统中流行的库和框架的集成。
最后,我们将重点关注ArchUnit集成并学习如何使用它来在构建过程中强制遵循 DDD 原则的代码结构。
jMolecules 的目标
jMolecules 是一个库,它允许我们明确表达架构概念,从而提高代码清晰度和可维护性。作者的研究论文详细解释了该项目的目标和主要功能。
总而言之,jMolecules 帮助我们使领域特定代码摆脱技术依赖,并通过注释和基于类型的接口表达这些技术概念。
根据我们选择的方法和设计,我们可以导入相关的 jMolecules 模块来表达特定于该风格的技术概念。例如,以下是一些受支持的设计风格以及我们可以使用的相关注释:
- 领域驱动设计 (DDD):使用@Entity、@ValueObject、@Repository和@AggregateRoot等注释
- CQRS 架构:利用@Command、@CommandHandler和@QueryModel等注释
- 分层架构:应用@DomainLayer、@ApplicationLayer和@InfrastructureLayer等注释
例如,我们可以导入Jackson和Byte-Buddy集成来生成样板代码,或者包含JPA和 Spring 特定模块来将 jMolecules 注释转换为其 Spring 等效项。
jMolecules 和 DDD
在本文中,我们将重点介绍 jMolecules 的 DDD 模块,并使用它来创建博客应用程序的域模型。首先,让我们将 jmolecumes -starter-ddd 和jmolecules-starter-test依赖项添加到我们的pom.xml中:
<dependency> |
在下面的代码示例中,我们会注意到 jMolecules 注释与其他框架的注释之间存在相似之处。这是因为Spring Boot或JPA等框架也遵循 DDD 原则。让我们简要回顾一些关键的 DDD 概念及其相关注释。
值对象
值对象是一个不可变的领域对象,它封装了属性和逻辑,而没有独特的标识。此外,值对象仅由其属性定义。
在文章和博客的上下文中,文章的 slug 是不可变的,并且可以在创建时自行处理验证。这使得它成为标记为 @ValueObject 的理想候选者:
@ValueObject |
Java 记录本质上是不可变的,这使它们成为实现值对象的绝佳选择。让我们使用记录创建另一个@ValueObject来表示帐户用户名:
@ValueObject |
实体
实体与值对象的区别在于,它们拥有唯一身份并封装可变状态。它们表示需要独特标识的领域概念,并且可以随时间推移进行修改,同时在不同状态下保持其身份。
例如,我们可以将文章评论想象成一个实体:每条评论都会有一个唯一的标识符、一个作者、一条消息和一个时间戳。此外,实体可以封装编辑评论消息所需的逻辑:
@Entity |
聚合根
在 DDD 中,聚合是一组相关对象,它们被视为数据更改的单个单元,并且有一个对象被指定为集群内的根。聚合根封装了逻辑,以确保对自身和所有相关实体的更改发生在单个原子事务中。
例如,文章 将成为我们模型的聚合根。文章可以使用其唯一的slug来识别,并负责管理其内容、喜欢和评论的状态:
@AggregateRoot |
我们可以看到,文章实体是包含评论实体和一些值对象的聚合的根。聚合不能直接引用其他聚合中的实体。因此,我们只能通过文章根与评论实体进行交互,而不能直接从其他聚合或实体进行交互。
此外,聚合根可以通过其标识符引用其他聚合。例如,Article引用了另一个聚合:Author。它通过Username值对象来实现这一点,该值对象是Author聚合根的自然键。
存储库
存储库是提供访问、存储和检索聚合根的方法的抽象。从外部看,它们显示为聚合的简单集合。
由于我们将Article定义为聚合根,因此我们可以创建Articles类并用@Repository对其进行注释。此类将封装与持久层的交互并提供类似 Collection 的接口:
@Repository |
执行 DDD 原则
使用 jMolecules 注释,我们可以将代码中的架构概念定义为元数据。如前所述,这使我们能够与其他库集成以生成样板代码和文档。但是,在本文的范围内,我们将重点介绍如何使用archunit 和jmolecules-archunit来执行 DDD 原则:
<dependency> |
让我们创建一个新的聚合根,并故意打破一些核心 DDD 规则。例如,我们可以创建一个没有标识符的Author类,它通过对象引用直接引用Article ,而不是使用文章的Slug。此外,我们可以有一个Email值对象,其中包含Author实体作为其字段之一,这也会违反 DDD 原则:
@AggregateRoot |
现在,让我们编写一个简单的ArchUnit测试来验证代码的结构。主要的 DDD 规则已经通过JMoleculesDddRules定义。因此,我们只需要指定要为此测试验证的包:
@AnalyzeClasses(packages = "com.baeldung.dddjmolecules") |
如果我们尝试构建项目并运行测试,我们将看到以下违规行为:
Author.java: Invalid aggregate root reference! Use identifier reference or Association instead! |
让我们修复错误并确保我们的代码符合最佳实践:
@AggregateRoot |
jMolecules 背后的想法
- 明确表达架构概念,以便于阅读和编写代码。
- 保持特定领域代码不受技术依赖。减少样板代码。
- 自动生成文档并验证实施结构和架构。
目标
- 让开发人员的生活更轻松。
- 表达一段代码(一个包,类或方法)实现一个架构概念。
- 让人类读者能够轻松判断给定的一段代码属于哪种架构概念。
- 允许工具集成:[list=1]
- 代码增强。(工具示例:ByteBuddy 与 Spring 和 JPA 集成)。
- 检查架构规则。(工具示例:jQAssistant、ArchUnit)。
用例:生成技术样板代码
jMolecules注释和接口可用于生成表达某一目标技术中概念所需的技术代码。
可用的库
- Spring、Data JPA、Data MongoDB、Data JDBC 和 Jackson 集成 ——使得使用 jMolecules 注释的代码在这些技术中开箱即用。
用例:验证并记录架构
以代码表达的 jMolecules 概念可用于验证源自概念定义的规则并生成文档。
可用的库
- jQAssistant 插件 — 用于验证适用于不同架构风格、DDD 构建块、CQRS 和事件的规则。还可以根据代码库中可用的信息创建 PlantUML 图。
- ArchUnit 规则 ——允许验证 DDD 构建块之间的关系。
- Spring Modulith—— 支持检测 jMolecules 组件、DDD 构建块和事件,以用于模块模型和文档目的(有关更多信息,请参阅https://docs.spring.io/spring-modulith/docs/current-SNAPSHOT/reference/html/#documentation/ [Spring Modulith 文档])。