Apache Causeway 简介
1. 概述
Apache Causeway 是一个构建于 Spring Boot 之上的领域驱动应用开发框架。与从控制器和页面开始不同,我们描述领域对象、其状态、行为及业务规则。Causeway 在运行时将这些描述转化为元模型。
之后,Wicket 视图和 RESTful Objects 视图使用相同的元模型来提供 Web UI 和超媒体 API。这使得 Causeway 特别适用于内部管理软件,这类场景中领域覆盖的广度和快速反馈往往比定制界面更重要。
在本教程中,我们将使用 Apache Causeway 3.6.0、Java 21 和 Maven 构建一个小型资产管理应用。具体来说,我们将建模笔记本电脑、显示器、手机等资产,添加生命周期操作和业务规则,运行生成的 UI,并调用一个生成的 REST 端点。
2. 心智模型
领域是软件所处理的业务范围。
2.1. 领域
领域模型是表示该范围的一组类型、关系、操作和规则。
在本例中,领域是公司硬件管理:
- 存在哪些资产
- 谁拥有它们
- 哪些生命周期转换是有效的
因此,主要的领域对象是 Asset(资产)。由于实例存储在数据库中,Asset 也是一个领域实体。相比之下,视图模型可以表示临时或计算信息,而不具有持久标识。Causeway 将标量值(如 type、serialNumber、status 和 assignedTo)作为属性公开。与其他多个对象的关系则表现为集合。
行为以操作的形式呈现,例如 assignTo()、returnToInventory() 和 retire()。那些不自然属于单个资产的操作(如创建或查找资产)则位于 Assets 领域服务中。Causeway 将这些领域成员作为其 UI 和 REST 视图的通用词汇表。
2.2. Causeway 与传统 Spring MVC 应用对比
在传统 Spring MVC 应用中,请求通常会到达一个控制器,该控制器执行多项操作:
- 调用应用服务
- 准备模型或 DTO
- 选择视图或生成响应
即使业务逻辑被良好隔离,每个新用例通常也需要显式的 Web 层代码。
Causeway 改变了起点。它内省实体、领域服务、注解和方法签名,并将其记录在元模型中。视图渲染元模型描述的属性、集合、操作、参数和验证消息。因此,添加一个基本用例无需创建对应的控制器、表单和模板。
但这并不会移除应用架构的其余部分。我们仍然配置 Spring 模块、持久化、安全和菜单布局,并且在适当时可以将复杂编排放在应用服务中。Causeway 通过让领域模型驱动交互,减少了表现层的重复工作。
2.3. 何时适合使用 Causeway
该模型非常适合后台工具、管理系统、内部管理应用以及领域密集型原型。这类系统通常将结构化数据与大量业务操作相结合,因此一致的生成界面能让团队尽早验证术语、工作流和规则。
当主要需求是高度品牌化的消费体验、像素级交互设计或不符合领域对象自然映射的页面流程时,该模型则不太适合。在这种情况下,我们可以在 Causeway API 前放置自定义客户端,但这会牺牲泛型视图所带来的部分开发速度。
框架的常见用例文档也呈现了同样的光谱,从原型设计和通用业务 UI 到自定义客户端。关键决策在于:领域覆盖度与表现控制权,哪个是更强的需求。## 3. 构建内部资产管理应用程序
让我们使用几个特征来对资产进行建模:
- 类型
- 序列号
- 状态
- 负责人(可选)
正常的资产生命周期是 AVAILABLE -> ASSIGNED,然后回到 AVAILABLE,而可用的资产也可以变为 RETIRED。
一个可运行的 Causeway 应用除了核心领域代码外,还需要框架模块导入、持久化、安全性和菜单配置。为了跟随操作,请保持 GitHub 上的完整项目 打开。以下片段涵盖了与本教程相关的决策,而仓库包含支持性的引导和配置文件。
3.1. 项目设置
首先,我们需要 JDK 21 和 Maven 3.9.11。Causeway 应用程序启动父工程 管理兼容的 Spring Boot 和框架依赖版本。在 pom.xml 中,让我们添加 Web 应用包、两个查看器、Simple Security、JPA 以及 EclipseLink,还有 H2 内存数据库:
Web 应用包提供了通用的运行时依赖。查看器构件添加了生成的 Wicket 界面和 RESTful Objects API,而 Simple Security 和 H2 使本示例保持独立。
接下来,一个小的 Spring 配置类标识应用程序代码:
让我们分解一下:
@ComponentScan发现域服务@EnableJpaRepositories启用 Spring Data 仓库@EntityScan注册 JPA 实体
然后,我们在 AppManifest 中将此配置与所需的 Causeway 模块一起导入:
导入模块会使其 Spring Bean 和 Causeway 特性对应用程序可用。
仓库还配置了 H2、EclipseLink 模式创建、演示用户和菜单布局。在 application.yml 中,我们将 causeway.applib.annotation.action.explicit 设置为 true,这样只有显式注释了 @Action 的方法才会成为操作。
3.2. 创建资产领域实体
让我们定义持久化类型,并给它一个稳定的逻辑名称:
那么,让我们仔细看看代码的含义:
- JPA 注解映射实体并在数据库级别对
serial_number强制实施唯一性约束。 @Named提供 Causeway 独立于 Java 包名使用的逻辑标识符。@DomainObject显式将类标识为 Causeway 域对象。CausewayEntityListener将 JPA 生命周期事件连接到 Causeway,包括服务注入和生命周期通知。- 公共构造函数建立了第一个生命周期不变量:每个新资产都从
AVAILABLE状态开始。
JPA 映射存储的值,而 Causeway 将相应的 Getter 识别为属性。为了清晰起见,让我们看看身份属性:
有几个重要细节:
EnumType.STRING存储诸如LAPTOP的值,而不是脆弱的序数。@PropertyLayout在生成的 UI 中对成员进行分组和排序。@Title使序列号成为对象显示标题的一部分。
其余属性保存 AssetStatus 和一个可选的员工名称。
3.3. 通过操作添加领域行为
值得注意的是,我们不希望调用者通过 Setter 独立更改生命周期字段。相反,实体暴露的操作会一起更新相关状态。
首先,分配操作记录员工并更改状态:
Causeway 将 assignTo() 渲染为一个操作,并从参数元数据派生其提示。通过返回 this,它告知查看器继续使用更新后的资产。IDEMPOTENT 语义描述了预期的调用语义,但并不会自动使 Java 实现变为幂等。
其他生命周期操作将资产返回到库存或使其退役:
具体来说,returnToInventory() 在恢复 AVAILABLE 时清空负责人。退役是刻意明确的,IDEMPOTENT_ARE_YOU_SURE 会要求 Wicket 查看器进行确认。
此时,方法表达了状态变更,但我们仍然需要控制每个变更何时有效。
3.4. 添加业务规则
Causeway 通过命名约定将支持方法与域成员关联起来。例如,disableAssignTo() 控制 assignTo() 的可用性,而 validate0AssignTo() 验证参数零(即第一个参数):
这里,结果为 null 表示允许交互。消息则会禁用或拒绝交互,并向查看器或 API 客户端提供原因。因此,只有可用的资产才能被分配,只有已分配的资产才能被归还,而已分配的资产在退役前必须先归还。
@MemberSupport 还允许 Causeway 验证支持方法是否仍然与现有的域成员匹配。
字符串参数默认是必填的,因此框架会在调用操作之前拒绝空的“Employee”字段。验证器还会处理仅包含空白字符的输入。这些检查适用于 Causeway 管理的交互;直接对 assignTo() 的 Java 调用仍然是普通的方法调用,不会自动调用支持方法。
业务规则是域交互的一部分,因此两个生成的查看器都可以在不将条件复制到控制器或页面中的情况下强制执行它们。
3.5. 创建资产域服务
实体操作在一个已存在的资产上执行。创建和查询属于域服务的职责,该服务由 Spring Data JPA 仓库 支持:
@DomainService 将服务包含在元模型中,@Named 分配 REST 查看器使用的逻辑名称,@Priority 将其置于 Spring 排序的早期位置。
RepositoryService 通过 Causeway 抽象持久化新实体,而 AssetRepository 提供特定于应用程序的查询。
仓库本身使用 Spring Data 派生的查询,因此不需要实现类:
方法名称描述了 Spring Data 将生成的排序、部分匹配和不区分大小写的精确匹配。这使持久化查询与面向 Causeway 的服务操作保持分离。
支持方法名称 validate1Create() 指的是参数一,即 create() 的第二个参数。它在持久化之前拒绝空序列号和已存在的不区分大小写的重复项。数据库约束还额外防止并发的精确值重复。在数据库级别强制不区分大小写的唯一性需要规范化、合适的索引或不区分大小写的排序规则。
最后,menubars.layout.xml 显式地将三个服务操作放入“Assets”菜单:
objectType 值匹配服务的逻辑名称,每个 id 匹配一个操作方法。@DomainService 在元模型中注册服务,每个 @Action 暴露一个方法,而布局文件确定此菜单分组。
3.6. 入口点
应用程序入口点激活 Causeway 原型预设,然后启动 Spring Boot:
那么,让我们运行应用程序。## 4. 运行内部资产管理应用程序
从 apache-causeway 模块,我们可以构建并运行该应用程序:
接下来,打开 http://localhost:8080/wicket/ ,使用演示凭据 admin 和 pass 登录。
4.1. 演示交互
从 Assets 菜单中,选择 Create ,选中 Laptop ,然后输入 LT-001。Causeway 立即使用其标题、属性、字段集和操作渲染新对象: [LOADING...]
当我们打开 Assign To 并提交一个空员工时,强制参数阻止了调用,生成的提示显示验证错误: [LOADING...]
输入 Alice 后,该操作将资产状态更改为 ASSIGNED。同一页面现在启用了 Return To Inventory 并禁用了 Assign To 和 Retire,这反映了支持方法,无需页面特定的条件代码: [LOADING...]
这个通用 UI 不需要任何特定资产的控制器、表单或 HTML 模板。
原型预设、内存 H2 数据库、自动架构创建和 SimpleRealm 凭据仅适用于此本地示例。生产应用程序应使用持久化存储、架构迁移、生产安全以及生产就绪的 Causeway 配置。
4.2. 生成的 API
该服务将其列表操作标记为安全(safe):
RESTful Objects 查看器 将此操作映射到经过身份验证的 GET。在应用程序仍在运行时,我们可以使用演示账户调用它:
URL 包含逻辑服务名称 assets.Assets 和操作标识符 listAll。经过验证的调用返回 200 OK,并直接访问领域服务,而不是直接暴露 AssetRepository。
响应不是原始的 JSON 数组。它是一个 RESTful Objects DomainObjectList 表示,包含链接、关系类型和媒体类型配置文件,允许客户端导航领域。
这对于通用客户端和集成很有用,但 在将其视为公共的、版本化的 API 之前,我们应有意限制暴露的表面或定义客户端特定的表示。
5. 结论
在本文中,我们从 Causeway 的领域优先思维模型开始,构建了一个内部资产管理应用程序。具体来说,我们映射了一个 JPA 实体,将生命周期行为表达为操作,使用支持方法强制执行有效转换,并添加了一个用于创建和查询的领域服务。
之后,我们运行了生成的 Wicket UI 并调用了 RESTful Objects API。当行为丰富的领域和快速交付一致的管理界面比完全自定义界面更重要时,Causeway 是一个强大的选择。
完整的源代码可在 GitHub 上获取。
文章 Introduction to Apache Causeway 首发于 Baeldung。
[LOADING...]
[LOADING...] [LOADING...] [LOADING...] [LOADING...] [LOADING...] [LOADING...] [LOADING...]