企业级软件开发人员通常会面对如下需求组合(至少我经常遇到的是):设计优良的数据存储结构(有时候是已有的旧数据库模型),大量的数据录入表单,非常复杂的业务逻辑、报表功能、与许多公司其他系统(财会、CRM等)集成,数千并发量。对此,你首先考虑的是什么?
__ “首先,采用主流的关系数据数据库管理(RDBMS)、Hibernate / JPA + Spring Boot、REST API,并且使用我最喜欢的或者最新的JS框架来实现UI。”__
__ “嗯~,还需配置Spring Security ,也许还需要写一部分代码来实现行级别的数据保护功能。如何实现呢?也许会用到数据库视图或虚拟专用数据库。“__
__ “所有这些DAO代码都非常相似且枯燥,但我还是需要一一实现。”__
__ “可以使用类似__ModelMapper的东西将JPA实体转换为REST的DTO。”
__ “别忘了跟实习生强调下懒加载和JPA关联查询。”__
__ “唉,其实都是雷同登录界面、千篇一律的实体到DTO的转换,有没有办法能让我摆脱所有这些乏味的东西 ,只需要专注于关键的业务逻辑实现呢?”__
本文适合使用Spring框架(包括Spring Boot)从头开始做过几个项目、并且正在考虑怎么提高自己工作效率的开发人员。在文章中,我将向您展示如何使用CUBA平台摆脱常见的耗时枯燥任务。
又一个新框架?
开发人员在听到新框架时提出的第一个问题往往是:“为什么我需要这个?Spring Boot就能很好地帮助我从头开始实现所有内容”。 没错,
新平台意味着新的规则、新的限制,之前积累的其他框架的经验便失去了意义。就算是目前使用的框架不完美,但是对它足够了解,
知道有哪些坑,和哪些变通的方法。
但是,从Spring切换到CUBA,并不需要重头学习规则,甚至没有什么变化,
只要稍微前进一步就可以摆脱枯燥的开发任务,摆脱数百行DTO架子代码和转换工具的困扰,
摆脱实现数据分页或数据过滤组件、创建Spring Security配置文件(JPA,Cache,...)等等些基础功能的麻烦。
本文中,我们将从头开始展示CUBA如何遵循几乎所有基于Spring的开发模式,
开发人员在Spring中积累的经验能被充分利用,
同样的研发投入却能交付更多。为了使情景更清晰简洁,本文侧重于后端代码。
Spring应用程序架构
Spring应用程序的典型架构可以很容易搜索到,很多情况下,它可以表示为具有一些交叉区域的典型三层应用程序。让我们来看看“经典”的Spring应用程序:
领域模型 Domain Model - 通常需要手动创建。但是,有一些工具可基于数据存储结构创建领域模型。
数据存储层 Repository Layer - 与数据存储一起使用的类。也称为“DAO”,“存储库(Repositories)”等。这块是ORM框架(及其兄弟姐妹们)的领地。它通常包含功能域模型中的一个实体执行CRUD操作的类。
服务层 Service Layer - 有时开发人员会创建一个额外的层来分离业务逻辑和数据CRUD操作。如果项目业务逻辑复杂,涉及不同类型的数据源,或者涉及外部服务集成,这一层会很有用。
Web /控制器层 Web/Controllers Layer (REST/MVC) - 用来处理REST API(将由基于浏览器的应用程序使用)或使用JSP实现的视图,或是模板框架(thymeleaf, velocity)、JVM前端框架(GWT, Vaadin,Wicket等)。由于API结构或前端界面展示不同,通常控制器是操作DTO而不是实体对象。因此,开发人员通常必须在实体模型和DTO模型之间实现双向转换。
_ 如果您对以上描述很熟悉(甚至觉得是显而易见) - 那么恭喜,您可以毫无障碍地开始使用CUBA。
参考应用 - 宠物诊所
话不多说,接下来看code怎么写。在GitHub上,有一个众所周知的Spring的“参考”应用程序 - Pet Clinic, GitHub 。下面我们将展示如何使用你的Spring技能加上CUBA框架开发一个新的宠物诊所程序。Antoine Rey的参考程序有非常好的、详细的说明; 我们将在本文中重复一些内容。
数据模型
图中显示了数据库的ER图。应用程序代码中实际的对象域模型有点复杂并且包含一些继承,您可以在上面提到的演示文稿中找到UML模型。
存储层
有四个Repository来处理主要实体:Owner(主人),Pet(宠物),Visit(拜访)和Vet(兽医)。这些Repository基于Spring JPA框架,由于Spring JPA的特性,这些Repository几乎不包含什么代码,但在Owner Repository里面有一个自定义的查询,用来获取宠物主人和他们各自的宠物。
UI界面
该应用程序包含九个界面,我们可以通过这些界面查看数据,并进行一些编辑:宠物主人,
宠物和拜访。本文不讨论它们,但需要指出的是,这些界面都是面向数据的应用程序里常见的CRUD表单。
附加功能
除了简单的CRUD功能外,该应用程序还包括一些能展示Spring Framework之强大的功能:
- 缓存(Caching) - 兽医列表已缓存,因此在刷新兽医列表时不会有DB读取。
- 验证器(Validator) - 检查在创建有关宠物的新记录时是否填写了所有字段。
- 格式化程序(Formatter) - 用于正确显示宠物类型。
- 多语言支持(i18n) - 该应用程序提供英语和德语版本。
- 事务管理 - 某些数据库查询是只读的。
感想
我非常喜欢这张图片,因为它精准表达了我的看法:要有效地使用任何框架,都需要了解它内部的工作原理。例如,Spring Boot隐藏了很多东西,如果你知道一个简单的JPA接口初始化背后有多少个类,你会吓一跳。以下是关于Spring Boot Pet Clinic应用程序中“神奇”的部分:
- @Caсheable - 除了这个注解之外没有任何缓存配置代码,但Spring Boot“知道”如何设置缓存实现(在我们的例子中将是EhCache)。
- @Transactional - Repository没有这个注解(它们的父类org.springframework.data.repository.Repository也没有),但所有save()方法都可以正常工作。
虽然存在这么多隐晦的地方,但是由于Spring Boot的透明性和可预测性,Spring Boot还是一个非常流行的框架。Spring Boot还有非常详细的文档,而且是开源的,所以你可以深入研究它的原理和任何方法的实现,研究里面的逻辑。我想每个人都喜欢透明且易于管理的框架,因为它们可以使您的应用程序更加易于维护。
宠物诊所与CUBA
现在,让我们看看使用CUBA平台的Pet Clinic,根据我们的Spring知识来审视它,并找出我们都能在哪些地方节约开发时间。
在GitHub上可以找到Pet Clinic实现的源代码。CUBA平台有非常好的文档,其中可以找到几乎有关CUBA的所有东西(大多数情况都有代码片段或者代码实例,都能在GitHub上找到)。在本文中,我们将更多的引用CUBA文档,以避免重复描述。
CUBA应用程序架构
CUBA应用程序包含以下模块(请参见图表)。
Global - 包含可以在其他模块中使用的数据库实体、CUBA视图和服务接口。
Core - 所有与数据库一起工作并实现业务逻辑的服务实现都应该放在这里。需要注意的是,Core模块里面的类在其他模块中并不可用,这样设计的目的是,可以独立部署Core和GUI模块到不同的服务器,给系统提供了很好的可扩展性。而想要将Core模块的服务注入其他模块,您应该使用Global模块中声明的接口。
GUI, Web, Desktop, Portal - 这些模块包含负责UI事件处理的GUI相关类(控制器,监听器等)。您可以在此处创建自定义REST控制器,以补全CUBA为您生成开箱即用的REST API。
为了提高开发人员效率,CUBA提供了 Studio - 一个漂亮小巧的图形化工具,用于创建和注册实体,并且可以为您更新所有配置,帮助创建服务的架子代码,并具有功能界面的可视化编辑器。
因此,基于CUBA平台的应用程序包含两个(或更多)单独的模块:可以单独部署的Core和GUI,以及一个跨功能域的Global模块。下面我们详细介绍一下CUBA的Global和Core模块及其内容。
Global模块
实体模型(Entity Model)
任何使用过与JPA标准兼容的ORM框架或者Spring的开发人员都应该熟悉CUBA应用程序中的实体模型。同样是用@Table,@Entity等注释的类,并在persistence.xml文件中注册。
在Pet Clinic应用程序的实体模型中,您可以重用Spring版本中的代码,但您需要记住以下几点:
-
CUBA 为使用此平台创建的每个组件(Component)引入了一个“命名空间”,以防止不同组件之间的名称冲突。这就是为什么每个实体名称都有一个“petclinic$”前缀。
-
建议对实体使用@NamePattern注释,这样在界面可以得到实体更有意义命名。
问题是,除了前缀和声明实体的“字符串式”表示之外,CUBA还提供了什么其他功能?其他功能包括:
-
支持ID生成功能的基类:从整数ID到UUID
-
一组有用(但可选)的接口:
- Versioned - 支持实体版本。
- SoftDelete - 支持实体的“软”删除,或者说是“逻辑”删除。
- Updatable - 添加实体更新日志记录的字段。
- Creatable - 添加实体创建日志记录的字段。
您可以在文档中阅读有关这些接口的更多信息。
-
CUBA Studio可以自动生成数据库创建和更新脚本。
在应用程序开发期间,我只是从Spring版本复制了现有实体模型,并添加了上面提到的CUBA特定功能,
从程序的参考版本中删除了BaseEntity类。
视图(Views)
CUBA的“视图”概念可能令人困惑,但很容易解释。视图是一种声明性方法,用于指定应提取哪些数据(属性和嵌套实例/集合)。
让我们假设您需要获取Owner及其宠物或兽医的属性 - 用于在同一个界面上显示依赖的子实体和父实体。
在纯Spring实现的情况下,您需要定义JPA关联查询:
@Query("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:id")
public Owner findById(@Param("id") int id);
...或定义正确的EAGER / LAZY FETCH类型以获取事务上下文中的实体的依赖集合。
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"),
inverseJoinColumns = @JoinColumn(name = "specialty_id"))
private Set specialties;
在CUBA版本中,您可以使用EntityManager和JPQL或视图和DataManager:
1. 定义一个指定我们想要提取的内容的视图:
<view class="com.haulmont.petclinic.entity.Vet"
extends="_minimal"
name="vet-specialities-view">
<property name="specialities"
view="_minimal">
</property>
</view>
2. 使用DataManager bean获取此数据
public Collection findAll() {
return dataManager.load(Vet.class)
.query("select v from cubapetclinic$Vet v")
.view("vet-specialities-view")
.list();
}
您可以为不同的任务创建不同的视图,选择要获取的属性,是否获取集合以及定义对象树的深度。在Mario David的博客中有一篇关于视图的精彩文章。
在宠物诊所程序中,我们为不同的场景定义了六个视图。这些视图主要用于UI表单,
其中之一是在服务中用来获取数据,上面显示了这个代码片段。
服务接口
于Global模块在CUBA的应用程序架构中属于交叉支撑的模块,因此您应该把服由务的接口定义在Global模块中,以便能够通过使用Spring的注入在其他模块中使用服务。您需要做的就是在Web模块的“web-spring.xml”文件中注册服务。CUBA平台在应用程序模块中创建代理,采用Spring配置XML文件的方法进行完全透明的实体序列化和反序列化。此功能使我们可以从其他模块调用Core模块中实现的服务,即使在分布式部署的情况下也只需要很少的额外工作。
因此,在使用CUBA进行实体模型开发方面,它与纯Spring完全相同,但是在插入数据后你不需要关心实体和关联实体的ID是怎么生成的,也不需要写额外的代码去实现实体版本化,软删除和实体变更日志。相比较于JPA关联查询,您还可以利用创建CUBA视图来节省一些时间。
Core模块
在Global模块中声明的服务接口都在Core模块实现。一般来说,CUBA应用程序中的每个服务都使用@Service注解,但您可以使用所有Spring里面可用的注解来处理bean。不过,由于CUBA的特殊架构,存在一些限制:
-
如果希望在Web模块中使用服务,则需要使用@Service做服务注解。
-
建议为服务创建服务名,以避免不同加载项中的Bean命名冲突。
除此之外,您的Core模块代码库是一个“纯粹的”基于Spring的后端应用程序。
您可以使用与以前相同的方式从数据存储中获取数据,调用第三方Web service等。唯一重要的区别是与数据库的交互:
Entity Manager and DataManager
CUBA平台使用自己的EntityManager,将其部分功能委托给实际的javax.persistence.EntityManager实例。CUBA的EntityManager主要提供低级别实体操作,不支持安全功能。所以在大多数情况下,还是推荐使用提供额外功能的DataManager:
- 行级和属性级安全性支持。
- 可以采用CUBA的实体视图获取数据。
- 支持动态属性。.
有关DataManager和EntityManager的更多信息,请参阅文档。值得注意的是,您不需要在GUI中直接使用这些bean,而可以使用数据源。
顺便谈谈PetClinic - 我(几乎)没有在Core模块中编写很多代码,因为没有特别复杂的业务逻辑。
CUBA实现的Spring宠物诊所的功能
在上一节中,基于Spring的Pet Clinic应用程序中我们总结了一个额外功能列表,CUBA中也提供了相同的功能:
缓存(Caching)
CUBA原生支持实体和查询缓存。这些缓存在文档中有详细描述,在使用时应首先考虑,因为它们支持分布式部署等所有平台功能。除此之外,您还可以使用Spring的@Cacheable启用缓存,并启用Spring 文档中所述的cach provider。
验证器(Validator)
CUBA使用BeanValidation作为标准验证引擎。如果内置验证不够,您可以定义自定义验证代码。而且还可以通过描述定义Validator类来提供验证UI数据的选项在这里。
格式化(Formatter)
CUBA平台为GUI组件提供了几种格式化程序,但除标准格式化程序外,您还可以定义自己的格式化程序。对于默认实体命名的格式化,使用@NamePattern注释。
国际化(I18n)
CUBA平台支持国际化的方式与其他java应用程序相同:使用message.properties文件,所以这里没什么新东西。
事务管理
CUBA平台提供以下事务管理选项:
- 熟悉的Spring @Transactional注释
- 如果在某些复杂情况下需要细粒度的事务管理,那么可以采用CUBA的持久层接口。
当我开发宠物诊所时,我只考虑过一次事务:那就是在开发允许编辑Owner、Pet以及在同一屏幕上添加Visit的表单的时候。此时我需要了解何时提交事务并刷新界面然后统一的显示数据。
真的几个小时就完成了宠物诊所
我能够在不到一天的时间内使用“标准”CUBA UI创建一个与Spring的Pet Clinic具有相同功能的应用程序。我不会说我是CUBA的专家(自我开始以来仅仅几周),但我有很长的时间在使用Spring。脑海中回想一下Spring程序的架构,然后我们看一下基于CUBA的架构:
-
**领域模型 **- Global模块中的实体。创建实体模型是每个程序中都需要的过程。由于使用BaseIntegerIdEntity类,在ID生成方面可以节省时间。
-
**数据存储层 **- 我不需要Repository,甚至连一个接口也不需要。我只是使用CUBA Studio 创建了一些视图。通过使用这个工具,我也不需要在配置文件中编写XML。
-
服务层 - 在我们的应用程序中,我们只有两个服务用来导出JSON和XML格式的Vets,这些Vets是可缓存的。我根据文档将接口放到Global模块,将接口实现放到Core模块。除了阅读有关DataManager以熟悉其API之外,这基本只是一个“正常”的开发过程。
-
**控制器层 **- CUBA Pet Clinic在Web模块中仅包含一个用于JSON和XML feed的自定义的REST控制器。这里没有特别要说的,只是一个采用了Spring注解的控制器。
-
**应用程序GUI **- 使用CUBA Studio创建“标准”CRUD表单非常简单。
在实现的过程中,我完全没有考虑如何将实体传递给Web UI,也没有考虑表单提交,也没有相关的控制器和Repository。CUBA为我提供了界面上一个比较好用的表格组件以及一个数据过滤组件,因此我不再需要解析查询语句,也不需要特意做分页。我花了大部分时间来实现界面的流程流转,以及调整页面样式。
我的个人体验如下表所示:
CUBA中易于理解的部分 | CUBA中需要阅读文档的部分 | |
实体 | 实体建模
数据库创建脚本 标准基类 |
软删除等的附加功能 |
库 | EntityManager 视图 | DataManager |
服务 | Bean管理 事务管理 安全性和用户管理 | 持久层接口 |
控制器 | 自定义REST控制器
请求URL映射 |
服务方法发布 |
UI | 标准表格 | 自定义UI |
显然Pet Clinic程序没有使用所有CUBA提供的特性,可以在站点上找到CUBA特性的完整列表,这里可以看到该平台可以怎样解决其他的一些常见任务。
我的个人观点 - 如果您使用企业软件的标准界面,CUBA可以简化后端实施并做得很好。如果你需要一个精美的界面,那么CUBA也会帮你节省在后端开发上的时间。
这么多优点!有没有什么缺点?
好吧,我想在本节中提到一些问题,这些问题的存在不影响使用CUBA,但是我发现这些问题会给第一次接触CUBA的人带来不好印象:
- 在介绍部分,CUBA平台说自己带有IDE,能简化项目的创建和管理。但有时在Studio和IDE之间切换可能会有点烦人。CUBA平台现在正在重新开发它,因此Studio很快就会转变为IDEA的插件。
- 在CUBA中,我们使用了比典型的Spring Boot应用程序更多的XML配置文件,因为平台提供的服务更多,各个模块之间需要一些XML来作为连接。
- 应用程序的单个页面都没有“友好”的URL。您可以使用屏幕链接直接访问屏幕,但这也非常不直观。
- 您必须处理CUBA的DataManager和EntityManager并学习它们的API而不是Spring JPA或JDBC(但如果需要,仍然可以使用它们)。
- 使用关系数据库时,使用CUBA将达到开发的最佳效率。而如果要使用NoSQL,CUBA的表现和Spring一样,都有很多需要自己写代码的地方。
结论
如果您有一个需要以关系型数据库作为数据储存,并且以数据为中心的应用程序的需求,您可能需要尝试使用CUBA平台作为基础来开发,因为:
-
CUBA是透明的。源代码可用,您可以调试所有内容。
-
CUBA很灵活(但是也有一定限度)。您可以继承并注入自己的bean而不采用标准CUBA bean,也可以发布自定义REST API并使用您自己的前端框架实现与用户交互。
-
CUBA就是Spring。80%的后端代码都是纯Spring应用程序。
-
使用CUBA可以加快项目启动。在第一个实体和UI屏幕创建之后,应用程序就可以使用了。
-
CUBA为您完成了很多日常枯燥的工作。
因此,通过使用CUBA,您将在枯燥的工作上节省不少时间,以便真正投入地处理复杂的业务相关逻辑,也可以实现与其他系统的无缝集成。