设计理念

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。Jmix Web 应用程序可以不暴露任何 API 端点而完美地运行,而不会引入更多的复杂性和安全问题。

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

principles full stack 1

如果你对这个概念比较陌生,请阅读我们的文章: 服务端 Web UI 开发 - 更简单 更专注。这里我们只是简要强调一下文中的结论。

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

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

但另一方面,Vaadin 的前端抽象级别使得自定义 UI 或创建自己的 UI 组件变得更加困难。当然不是说不行,只是比直接使用前端框架要困难一些。此外,由于 Vaadin 是在服务端记录组件的状态模型,每个用户会话都需要消耗服务器上一定数量的内存,在一定程度上限制了应用的垂直扩展性。

而且还有一个显而易见的团队方面的问题,如果组织内已经有前后端分离的开发团队,以及围绕这些团队建立的开发实践,那么 Jmix 可能不是最好的选择。

因此,如果觉得默认提供的 UI 组件功能已经够用,并且只需要在 UI 的某些部分进行深度定制,那么 Jmix 和 Vaadin 的全栈开发方法无疑将能提供更多价值。还有一个条件是并发用户数是可预测的,并且不会太高(例如,小于 10 万)。对于 Jmix 所擅长的 B2B 应用领域来说,这两个条件通常都能满足。

最后一点是关于单体架构与微服务架构的说明。当 UI 与业务逻辑紧耦合时,全栈开发方法与微服务架构是矛盾的。但这并不是说全栈开发只能用于单体。如果将大型应用拆分为多个协作的单体,每个单体都有自己的数据库和 UI,则可以一举两得:

principles full stack 2

Jmix 非常适合构建任何规模的 模块化单体扩展组件(add-ons) 的概念和 Studio 中对 复合项目 的支持使得代码库可以非常灵活地拆分。Jmix 具有 OpenID 连接REST API 扩展组件,以及能与任何异步通信解决方案完全兼容,这些特性能支持独立的 Jmix 应用服务与组织整体的信息系统进行集成。

统一的数据模型

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

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

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

principles model 1

这种架构提供了极大的灵活性:可以独立修改持久层或展示层的模型,可以将数据转换为存储和 UI 最方便使用的结构,而如果需要考虑数据安全,还可以通过 API 隐藏某些数据。

但是,这种灵活性需要付出什么代价呢?显然,会有很多实体类和属性的重复代码,并且需要编写模型之间互相映射的 mapper。这种代价是否值得?还需要视情况而定。如果编写的应用中只有很少的实体,却有花哨的 UI,并且展示层的数据结构与存储数据的 ER 模型差异很大,那么将持久层和展示层的模型分开将是很自然的一个选择。

在许多 B2B 应用中,情况恰恰相反:数据需要与数据库中相同的结构在 UI 中展示。还考虑到这种应用中数据模型的数量(通常是数十或数百个实体),分离的模型似乎不再是一个好的选择。只会引入更多的样板代码,而提供的价值又非常有限。这些代码第一次完成后,在后续系统的发展过程中如果出现需要为实体添加一个属性的情况时,可以想象从前后端需要修改多少代码!

正如我们在上一节中所介绍的,由于使用了 Vaadin,Jmix 应用不需要通过 API 来构建用户界面。而且,可以很容易控制给用户展示的数据,只需要不给某些属性创建 UI 组件,那么这些属性的数据将不会传递给前端。使用分离模型更安全的论点也不攻自破。

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

因为即使在最简单的 CRUD 程序中,持久化模型和展示模型之间的差异也可能是真实存在的。例如,可能需要根据存储的数据计算一些值,这些值用于在 UI 中展示或在业务逻辑中使用。那么,可以在实体中定义 transient 属性并在加载实体时计算这些属性的值。

另一种情况是,当需要使用不是关系型数据库的数据源时。此时,将无法使用 JPA,只能通过 POJO 的方式定义模型并映射至外部 API 或非关系数据库。

最后,也可能有复杂的 UI 部分,这里也需要一些结构与持久化模型不同的 POJO。

通过 Jmix,所有这些需求(transient属性、映射到外部数据的 POJO、仅限 UI 展示的 POJO)都可以在单个数据模型中实现。也就是说,可以通过在 JPA 持久化模型中添加非 JPA 属性来扩展模型,而无需堆叠多个模型。

principles model 2

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

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

即用型组件

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

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

Jmix 专注于企业应用的特定领域,因此能够提供许多可以说是最佳实践的默认选项。通过这些最佳实践和默认配置,降低了使用 Jmix 的门槛并加快了应用程序的开发。

使用主流技术

Jmix 是基于主流技术(Java、Spring、JPA)构建,并且倾向于不去重新发明轮子。在需要时,可以绕过 Jmix 提供的抽象直接使用底层技术,这一点 Jmix 没有任何限制。

从工具和开发实践的角度来看,还可以使用现代测试框架、静态代码分析、CI/CD 和版本控制系统。

扩展性

在构建 Jmix 时,我们就考虑到了可扩展性。如果框架中的某些功能不符合需求,可以扩展或替换成自己的解决方案。使用 Jmix 创建的扩展组件和应用程序也是一样的 - 可以在现有解决方案之上进行自定义。

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