设计理念

Jmix 旨在创建具有大量数据模型和复杂 UI 的 Web 应用系统,这些系统主要为组织的内部用户定制开发。

这种类型的应用程序主要有:增删改查、网站的管理端 UI、业务自动化工具、CRM 或者 ERP 类型的系统。这些应用通常需要成百上千的页面中处理几十或上百个互相关联的实体,而主要使用标准 UI 组件(如各种字段、表单和表格)。

为了高效地开发此类应用程序,与直接使用 Spring 或 Jakarta EE 等主流技术相比,开发人员需要一种能在更高抽象级别进行编码的工具。这个工具需要隐藏不必要的细节,且能避免编写重复代码,因为这类系统通常包含很多重复的元素:非常类似的实体、属性、UI 控件、页面等。但工具的抽象级别又不能太高,这样才能支持开发人员用熟悉的编程语言充分地表达业务逻辑,并能使用现代的强大开发工具,例如 IDE、VCS 以及测试框架和 CI/CD 等。

Jmix 的目标就是为开发上述种类的应用程序提供充分的抽象,并消除不必要的复杂性。

下面,我们介绍一下 Jmix 的基础设计理念。

全栈开发

Jmix 提供了全栈开发的方式,主要有两点考虑:

  1. 使用单一编程语言和开发范式。后端开发人员可以实现从数据库结构到 UI 的全栈业务功能,只需编写纯同步的 Java(或 Kotlin)代码。无需具备基于 JavaScript 前端技术方面的专业知识。

  2. API 是可选的。带 UI 的 Jmix Web 应用程序可以不暴露任何 API 端点而完美地运行,而不会引入更多的复杂性和安全问题。

这两点实际上是因为 Jmix 底层使用了 Vaadin 框架。Vaadin 提供了丰富的 UI 组件,每个组件都有一个客户端部分和一个服务端部分。客户端部分是用 JavaScript 编写并在浏览器中运行,而服务端的部分是用 Java 编写并在服务器上运行。用户与组件的客户端部分进行交互,而服务端部分则直接与应用程序的 Java 代码交互。

principles full stack 1
Figure 1. Vaadin 组件交互示例

因此,应用程序的开发者不需要为前端创建 API,而且也不需要编写前端代码。只需与 Vaadin 组件的服务端部分交互。

如果你对这个概念比较陌生,请阅读我们的文章: 服务端 Web UI 开发 - 更简单 更专注

单一的编程语言和开发范式可以让后端开发人员开发完整的业务功能,而不仅仅只是后端和 API 部分。这种方式可以显著提高团队的生产力,无需前端开发成本即可构建产品。

UI 的运行过程中不需要 API,这一点可以节省 API 这块大量的设计、开发、维护和文档的工作。这种方式本质上也更安全,因为应用只向浏览器发送真正需要显示在用户屏幕上的数据。因此,不会在浏览器端出现额外数据无意暴露给 JS 的风险,出现安全漏洞的可能性也小得多。

这种方式的一个不太友好的地方在于,前端的高抽象级别使得自定义 UI 或创建自己的 UI 组件变得更加困难。当然不是说不行,只是比直接使用前端框架要困难一些。此外,由于 Vaadin 是在服务端记录组件的状态模型,每个用户会话都需要消耗服务器上一定数量的内存,在一定程度上限制了应用的垂直扩展性(但是,应用程序可以通过添加更多集群服务器在水平方向扩展)。

因此,如果觉得提供的 UI 组件功能已经够用,并且只需要在 UI 的某些部分进行深度定制,那么 Jmix 和 Vaadin 的全栈开发方法无疑将能提供更多价值。还有一个条件是并发(同时使用系统的用户)用户数是可预测的,并且服务器的内存要能支持这些并发的用户。对于 Jmix 的目标应用领域来说,这两个条件通常都能满足。

在说到全栈开发时,有一个重要的问题是不能不讨论的,那就是单体应用和微服务架构。

基于微服务构建的解决方案通常具有一个与后端松耦合的前端应用。这个是与全栈开发方式相矛盾的,全栈开发中,用户界面直接与业务逻辑交互。

但这并不是说全栈开发只能用于单体应用。如果将大型应用拆分为多个协作的单体,每个单体都有自己的数据库和 UI,则可以一举两得:

principles full stack 2
Figure 2. 协作的单体

Jmix 非常适合构建任何规模的 模块化单体

模块化是通过开发自定义的 扩展组件(add-ons) 和在 Studio 通过 复合项目 进行开发实现的,这种方式使得代码库可以非常灵活地拆分。

将 Jmix 开发的功能完备系统与其他系统集成是通过 OpenID 连接REST API 扩展组件实现,并且 Jmix 能与任何异步通信解决方案完全兼容。

统一的数据模型

使用 Jmix 可以将各种不同的数据以一个统一的模型表示,这个数据模型可以直接在持久层、UI 层和业务逻辑中使用。无需创建数据传输对象 (DTO) 并在持久化模型和展示模型之间做映射。

下面,我们将比较传统 Java 企业应用程序的架构和 Jmix 统一数据模型方式的不同,并定义 Jmix 可以提供最大价值的一个差异化边界。

现代企业应用程序开发中的常用方法是在持久层和展现层(包括业务逻辑)使用不同的数据模型。也就是说,一般会有一个 JPA(Jakarta Persistence API) 实体层(与数据库表映射)和一个单独的数据传输对象 (DTO) 层,通过 REST、GraphQL 或其他 API 技术与前端进行交互。在传统的 JavaScript SPA 前端中,还可以使用 JSON 表示相同的 DTO。

principles model 1
Figure 3. 传统方式

这种架构提供了极大的灵活性:可以独立修改持久层或展示层的模型,可以将数据转换为存储和 UI 最方便使用的结构,而如果需要考虑数据安全,还可以通过 API 隐藏某些数据。这种方式的一个弊端是,会有很多实体类和属性的重复代码,并且需要编写模型之间互相映射的 mapper。

如果编写的应用中只有很少的实体,却有花哨的 UI,并且展示层的数据结构与存储数据的 ER 模型差异很大,那么将持久层和展示层的模型分开将是很自然的一个选择。

但是在许多企业级应用中,情况恰恰相反:数据需要与数据库中相同的结构在 UI 中展示。还考虑到这种应用中数据模型的数量(通常是数十或数百个实体),分离的模型在这里会变得异常昂贵。

Jmix 应用程序中也不存在上面提到的分离模型的安全问题,因为不需要使用 API 构建用户界面。而且,可以很容易控制给用户展示的数据,只需要不给某些属性创建 UI 组件,那么这些属性的数据将永远不会离开后端。

Jmix 应用的基本方式是在所有层中都使用单一数据模型:持久层、业务逻辑层和 UI 层。在大多数情况下,都是使用 JPA 实体及其与数据库字段对应的属性字段。但在 Jmix 中并没有限制仅能使用持久化的数据模型,且支持下列场景:

  • 通过 JPA 实体中的 transient 属性实现实体的计算属性。

  • 使用非关系型数据库的数据源。此时,将无法使用 JPA,只能通过 POJO 的方式定义模型并映射至外部 API 或非关系数据库。

  • 对复杂的 UI 部分,这里也需要一些结构与持久化模型不同的 POJO,专门用于展示。

通过 Jmix,所有这些需求都可以在单个数据模型中实现。也就是说,可以通过在 JPA 持久化模型中添加非 JPA 属性来扩展模型,而无需堆叠多个模型。

principles model 2
Figure 4. 统一数据模型的方式

这种方式在应用程序展示的数据与数据库的结构差不多相同的项目中具有明显的优势,不需要在不同层复制模型,也不需要编写样板代码来维护这种重复的模型。只是在需要时添加必要的属性来扩展底层的持久化模型。

为了更好地理解 Jmix 如何提供包含不同元素的统一数据模型,以及如何使用数据模型,请参阅 数据模型和元数据 部分。

即用型解决方案

Jmix 针对企业级应用中的常见问题提供了即用型解决方案。包括处理数据的复杂 UI 以及一些全栈功能,例如生成报表,还有 BPM。

另外还包括一些在 UI 构建、数据访问和安全等功能中的高级别抽象和声明式的使用方案。可以在 下一节 看到这些功能的介绍。

Jmix 专注于一个特定领域的开发 - 企业级应用,与通用框架相比,例如 Spring 或 Django,Jmix 能为这类应用提供更多开箱即用的解决方案。

通过这些解决方案、最佳实践和默认配置,降低了使用 Jmix 的门槛并加快了应用程序的开发。

使用主流技术

Jmix 是基于主流技术(Java、Spring、JPA)构建,并且倾向于不去重新发明轮子。对底层技术进行了一些结构上的定制以及预配置,但仍然从根本上是开放的。

在需要时,可以绕过 Jmix 提供的抽象直接使用底层技术,这一点 Jmix 没有任何限制。

从工具和方法论的角度来看,开发者还可以使用业界的最佳实践,例如测试框架、静态代码分析、CI/CD 和版本控制系统等。

扩展性

在构建 Jmix 时,我们就考虑到了可扩展性。如果框架中的某些功能不符合需求,可以扩展或替换成自定义的解决方案。

另外,Jmix 框架中的可扩展性支持在不修改原始产品的基础上为特定行业或客户定制新的产品。

模块和扩展 部分详细介绍了 Jmix 可扩展性。