vert.x java_使用Java 9模块和Vert.x微服务构建CI系统

vert.x java

重要要点

  • Java 9和Vert.x微服务与构建应用程序兼容
  • 许多Java库仍无法作为模块使用
  • “自动模块”(库尚未打包为模块)需要格外小心
  • Java的内置Nashorn Javascript环境对于编写Vert.x应用程序很有用

本文将引导您使用Eclipse Vert.x工具包设计和开发现代的,响应式,弹性和消息驱动的持续集成(CI)系统。 我们将利用Project Jigsaw下的Java平台模块系统(JPMS)来构建应用程序,该系统是通过在定义明确的接口上进行通信的大量松散耦合的模块构建的。

意图是JPMS应该使Java架构师和开发人员有信心,他们可以使用模块来处理大型遗留代码库以及创建新的应用程序。 但是,将现有的Java库与模块系统一起使用并不总是那么容易。 因此,在此过程中,我们将讨论在使用Java模块系统时遇到的各种挑战以及使系统运行所需的解决方法。

首先,定义此新CI系统的最低可行产品(MVP),并将其作为Docker本机系统构建。 该产品应提供以下核心功能作为REST API:

  1. 在存储库上支持CRUD。 存储库表示受版本控制的项目。 它指定到git存储库或perforce库的连接详细信息。
  2. 支持“管道即代码”哲学。 管道定义工作流程以构建代码工件。 管道在JavaScript中定义。 脚本文件可以与您的代码库一起存储在源代码存储库中。
  3. 提供用于启动和停止管道的API。 给定管道的一个实例就是构建。

现在我们定义了MVP,我们可以继续并开始构建系统。 第一步是创建项目框架。 为此,我们可以使用IntelliJ多模块gradle项目模板。 由于我们将使用JDK 9,因此使用最新最大的gradle版本(在撰写本文时为4.4.1)将使我们受益。 看到我们将要创建模块化jars,我们需要添加实验性的拼图插件,并确保源兼容性设置为Java9。该项目的主“ build.gradle”文件应类似于以下代码段:

vert.x java_使用Java 9模块和Vert.x微服务构建CI系统

与大多数系统一样,我们的系统将具有一个公共的核心库,用于承载实体对象,实用程序类,共享常量,查询解析器等。 让我们将此核心库定义为Java 9模块。

如前所述,Java 9模块是一个自描述的,命名的接口,类和资源的集合。 作为JPMS的一部分,添加了一个新结构“ module-info.java”,以使开发人员可以定义模块的公共合同及其对其他模块的依赖关系。 我们将使用上述文件来命名我们的模块,指定其对其他模块的依赖关系,以及将导出的包供应用程序中的其他模块使用。

以下代码片段显示了如何使用module-info文件描述核心模块:

vert.x java_使用Java 9模块和Vert.x微服务构建CI系统

让我们通过这里的定义。 每个“ module-info.java”文件均以关键字“ module”开头,后跟模块名称。 反向域名表示法(主要是软件包的命名约定)可以用来命名模块。

我们看到在代码块中使用了两个新关键字-“ exports”和“ requires”。 “ exports”关键字用于声明我们的模块公开的公共包-我们模块的公共API。 “ requires”关键字用于声明模块依赖性。

在这一点上,一个明显的问题是如何根据野外可用的各种第三方非模块化库来创建Java 9模块。 为此引入了一种称为自动模块的概念。

顾名思义,非模块化jar会根据jar文件的名称自动转换为已命名的模块化jar。 自动名称是根据以下算法得出的:从jar文件名开始,删除扩展名,用点替换连字符,最后删除版本号(如果存在)。 这就是为什么使用“ vertx.core”名称将非模块化“ vert-core-3.5.0.jar”库指定为依赖项的原因,但是,以此方式推断出的自动模块可能并不总是有效,正如我们稍后将在我们依赖Netty的本地传输库的实例。

与核心模块类似,我们需要定义其他几个模块来处理数据库调用,用户身份验证,运行引擎以及与CI系统中的各种系统插件进行通信。

现在,我们已经介绍了模块化Java应用程序的一些基本概念,让我们退后一步讨论vert.x。 这是一个工具包,提供了永不阻塞调用线程的非阻塞API。 这种非阻塞性意味着vert.x应用程序可以使用一小部分线程池处理大量并发。 Vert.x可以使用多React器模式来执行此操作。

熟悉JavaScript的开发人员可能会想起单线程事件循环,该循环会在事件到达注册处理程序时传递事件。 多React器模式是一种相关方法,但是为了克服单线程事件循环的固有局限性,vert.x根据给定服务器上的可用内核采用了多个事件循环。

Vert.x还附带一个松散的基于参与者模型的自发并发模型,其中“参与者”接收和响应消息,并使用消息与其他人进行通信。 在vert.x世界中,参与者称为“顶点”,它们通常使用通过事件总线发送的JSON消息相互通信。 我们甚至可以指定vert.x应当部署的每个顶点的实例数。

事件总线的设计使其可以使用多种即插即用群集管理器(如Hazelcast和Zookeeper)形成群集。 可以将每个顶点或在vert.x实例上运行的顶点的组合视为微服务。 多React器模型,类似参与者的顶点以及事件总线的分布式特性的结合使vert.x应用程序具有高度响应性,弹性和弹性,从而坚持了React性宣言。

考虑到这一点,让我们看一下CI系统的整体流程:

vert.x java_使用Java 9模块和Vert.x微服务构建CI系统vert.x java_使用Java 9模块和Vert.x微服务构建CI系统

如上所示,有许多顶点通过vert.x事件总线相互通信。 请注意,上面显示的插件也是verticle。 CI系统的入口点是通过服务器端点。 它是公开的版本,因为它公开了REST API。 CLI和GUI客户端可以使用这些API端点来指定存储库的连接详细信息,创建管道并运行这些管道。

以下代码摘录将使我们了解如何在vert.x中定义API和路由:

vert.x java_使用Java 9模块和Vert.x微服务构建CI系统

我们使用vert.x的网络库来定义REST API,并将所有路由安装在“ / api / v1 /”基本路径下。 Vert.x提供了大量其他库和功能,使我们能够快速开发响应式应用程序。

例如,可以使用Web API合约库使用OpenAPI 3规范设计应用程序的API,并让该库自动处理请求验证和安全验证。 Vert.x的OAuth库可用于使用您选择的OAuth提供程序(例如Google或Facebook)或您自己的自定义提供程序来保护您的应用程序和API。

回到前面的图,引擎垂直轴负责协调管道实例或构建的执行。 当客户端调用服务器垂直版本提供的REST API以启动管道时,服务器垂直版本将消息发送到引擎垂直版本。 收到此消息以开始管道执行时,引擎将实例化一个新的流对象。

流对象是一个简单的状态机,用于跟踪管道实例的进度。 在任何给定的点,流对象可以处于以下三种状态之一:设置,运行和拆卸。 它也可以根据传入消息转换到新状态。 在这些状态的每种状态下,流对象都会触发事件,并将这些事件作为消息通过事件总线发送。

已注册的插件处理这些消息,并通过事件总线异步发送回处理结果。 这是一段代码摘录,展示了我们如何注册消息处理程序以启动管道,创建流对象以及处理来自插件的传入消息:

vert.x java_使用Java 9模块和Vert.x微服务构建CI系统

引擎垂直引擎还负责定位和部署插件或顶点。 我们使用Java的服务加载程序机制(在Java 6中引入并在Java 9中进行了修订)来在服务器启动期间定位和部署插件。 要了解服务加载,我们需要讨论服务和服务提供商。

服务是众所周知的接口或类(通常是抽象的)。 服务提供者是服务的具体实现。 ServiceLoader类是用于加载实现给定服务的服务提供程序的工具。 现在,一个模块可以声明它使用了特定的服务。 然后,模块可以使用ServiceLoader来查找和加载在运行时环境中部署的服务提供者。

例如,服务器模块可以声明它使用插件接口,而工作区模块可以声明它提供两个服务来实现插件协定,如它们相应的“ module-info.java”文件中所述:

vert.x java_使用Java 9模块和Vert.x微服务构建CI系统

因此,当服务器模块启动时,它将调用ServiceLoader并接收以下两个插件,然后将它们作为vert部署:

vert.x java_使用Java 9模块和Vert.x微服务构建CI系统

这些插件完成了大部分工作。 插件注册消息处理程序以处理其感兴趣的管道流的特定事件。 例如,工作区插件负责git中的代码同步。 脚本解析器插件负责扫描工作区以找到用JavaScript编写的管道脚本文件,并执行描述如何运行管道的代码。 结果是由docker容器内的脚本运行程序插件执行的几个shell命令。 由于vert.x利用Java的内置JavaScript引擎Nashorn,因此可以运行JavaScript。 请注意,vert.x是一个多语言工具包,它也支持JavaScript,Kotlin和Groovy等语言。

这是管道脚本的片段:

vert.x java_使用Java 9模块和Vert.x微服务构建CI系统

脚本运行程序插件根据收到的消息下载docker映像,创建容器并在这些容器内执行shell命令。 显然,此插件必须与docker引擎进行交互。

Docker REST API通常在unix域套接字上的http上公开,而不是在tcp套接字上的传统http上公开。 这是vert.x发光的地方。 我们可以使用vert.x的异步客户端与docker交互,而不是使用执行阻塞代码与docker引擎对话的现成的jar。

当看到Netty提供的本机传输库时,Vert.x利用本机传输。 当我们在“ build.gradle”文件和“ module-info.java”文件中添加诸如“ netty-transport-native-kqueue”之类的本地传输依赖项时,它就可用。

vert.x缺乏对基于unix域套接字的http的支持,因此很快会解决这一问题,希望在下一发行版中得到解决。 暂时,我们可以对vert.x核心库进行一些小的代码修改,并自行构建它以解决此问题。 与docker引擎通信的插件代码如下所示:

vert.x java_使用Java 9模块和Vert.x微服务构建CI系统

添加诸如“ netty-transport-native-kqueue-4.1.15.Final-osx-x86_64.jar”之类的非模块化jar作为依赖项将导致创建自动模块名称。 但是,由于jar文件包含Java保留关键字“ native”,因此我们的模块化应用程序将无法编译。

当Netty的创建者在其下一个版本中解决此问题时,我们可以通过在jar的清单文件中添加“自动模块名称”条目来绕过此问题。 为此,我们必须先将罐子解压缩到一个文件夹中,然后将其放入CD中。 然后,我们必须修改“ MANIFEST.MF”文件以添加此项“ Automatic-Module-Name:io.netty.transport.kqueue”。 接下来,我们通过执行以下命令来创建jar:

vert.x java_使用Java 9模块和Vert.x微服务构建CI系统

通过运行以下命令,我们可以验证清单文件中指定的自动模块名称现在已被java识别:

vert.x java_使用Java 9模块和Vert.x微服务构建CI系统

我们必须使用类似的命令来修复其自动模块名称与Java的保留关键字冲突的其他非模块化jar。

现在,我们已经准备好构建和运行CI系统。 这是运行我们的应用程序的命令:

vert.x java_使用Java 9模块和Vert.x微服务构建CI系统

为了支持JPMS,已向现有命令工具(如“ javac”和“ java”)添加了新选项。 这些选项告诉Java编译器和运行时使用模块路径和模块化jar,而不是使用旧的classpath。 一些值得注意的选择:

“ -p”或“ --module-path”用于告诉Java查找包含Java模块的特定文件夹。

“ -m”或“ --module”用于指定模块和用于启动应用程序的主类。

在本文中,我们使用vert.x工具包设计了一个基于模块化微服务的应用程序。 根据我们的设计,我们已经使用JPMS和JDK 9构建了一个docker native CI系统。转到GitHub以获取代码,并详细了解vert.x和模块如何组合在一起以构建一个小型,独立的模块化Java。应用。

关于作者

vert.x java_使用Java 9模块和Vert.x微服务构建CI系统 Uday Tatiraju是Oracle的首席工程师,在电子商务平台,搜索引擎,后端系统以及Web和移动编程方面拥有近十年的经验。

vert.x java_使用Java 9模块和Vert.x微服务构建CI系统

翻译自: https://www.infoq.com/articles/CIWithJava9VertX/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

vert.x java