vlambda博客
学习文章列表

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Thornail构建微服务

Building Microservices Using Thorntail

在本章中,在看到架构向云的演变之后,我将详细介绍如何创建使用 Jakarta EE 和 MicroProfile 规范和 Thorntail 的分布式微服务架构。您可以选择其他实现,例如 Payara 或 Open Liberty。

下面将详细分析以下主题:

  • Thorntail: The MicroProfile implementation that enables you to package your Java EE applications with just enough of the server runtime.
  • Building a fantasy football application: I will create a set of microservice implementations based on different technologies in order to demonstrate the power of this new way to design enterprise applications.
  • User interface microservices: I will analyze the micro frontend architecture and how to design the user experience with the microservices approach, and I will implement it with an example, based on Angular 6.

Thorntail

Thorntail,以前称为 WildFly Swarm,是 MicroProfile 规范的开源实现。它建立在成熟技术之上,并利用了源自 Jakarta EE 规范、模式和实现的现有专业知识。

Thorntail 项目与 WildFly/Jakarta EE 一起诞生,但它专注于现代云应用程序,遵循 MicroProfile.io 的路径(在某些方面)。 它最初实现了 MicroProfile 的 1.3 版。您可以在 https://github .com/eclipse/microprofile-bom/releases/download/1.3/microprofile-spec-1.3.pdf.

在撰写本文时,Thorntail 版本为 2.0.0,其中包含 WildFly Swarm 2018.5.0 中实现的所有功能。

您可以将 Thorntail 视为成熟 Java EE/Jakarta EE 规范的所有优势以及 MicroProfile.io 的灵活性和面向云的概念的组合,如下图所示:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Thornail构建微服务

实际上,当您构建传统的 Jakarta EE 应用程序(使用标准 EAR 或 WAR 分发)时,您需要安装整个应用程序服务器(例如 WildFly),然后在其上部署您的应用程序。

确实,随着 Web 配置文件的引入(从 Java EE 6 开始),可以减少应用程序服务器初始配置中存在的 API 和组件的数量。

应用程序服务器进行了进一步优化,以仅加载我们的应用程序真正需要的规范(以及相应的类)。例如,WildFly 有四个基本配置文件,它们仅构成应用程序服务器启动期间可用功能的一个子集。 WildFly 也以惰性方式加载其子系统,因此它的占用空间非常低,并且与加载的类相关的内存使用情况与应用程序真正使用的内容严格相关。

然而,在微服务架构中,这还不够,因为使用传统的 Jakarta EE 应用程序服务器,您可能拥有比应用程序所需的更多的功能。

Thorntail 使您能够使用两种不同的策略创建一个包含部署所需内容的最终包,如下所示:

  • Uber JAR: A self-contained, executable Java archive that contains your application, the fractions of Thorntail required to support it, a Maven repository of dependencies, and a small library that's needed to bootstrap it all.
  • Hollow Uber JAR: A self-contained, executable Java archive that contains no application code, but only what you need to run it. Usually, it contains a subset of APIs, Jakarta EE, and MicroProfile, that your application will use. In this way, you exclude your EAR or WAR file (with the application code) from the executable Hollow JAR. Then, you can deploy your application artifact in the same style as traditional Jakarta EE app servers.

Thorntail 的关键元素之一是分数,这将在下一节中解释。

Fractions

正如我们之前解释的,您可以将 Thorntail 视为可以以可插拔方式构建的运行时环境。在 Thorntail 中代表组成方面的核心单位是 fraction。这是一个定义明确的运行时功能,可以添加到您的环境中。在某些情况下,分数直接映射到 WildFly 的子系统,但在其他情况下,它可能涉及 MicroProfile.io 规范中描述的或云环境中需要的不同功能。

分数可以做以下事情:

  • Directly enable a WildFly subsystem as Infinispan, which is needed to implement a data caching layer
  • Integrate additional frameworks or services (for example, topology using Consul, JGroups, and OpenShift)
  • Provide deployments of features like API documentation (for example, Swagger) or JMX metrics access (for example, Jolokia)
  • Add API dependencies as Jakarta EE (for example, JAX-RS) or MicroProfile.io (for example, config, and health check)
  • Alter deployment, introducing features as single-sign on for the authentication and authorization settings of your application (for example, Keycloak).

在撰写本文时,大约有 184 个可用的部分是稳定的和实验性的,还有更多的部分正在准备中。大约 80% 封装了 WildFly 相关组件,因此您可以使用一些 Jakarta EE 规范,并且您可以轻松地将应用程序从单体架构演变为微服务架构。

分数支持显式和隐式配置;在许多情况下,您不需要配置任何东西。默认值会让您成功运行您的应用程序。

可以检测或显式声明分数。最简单的情况是 WAR 项目,只有 Maven 插件。在您的应用程序中启用 thorntail-maven-plugin 后,Thorntail 将检测您使用了哪些 API,并将包括在构建时实现它们的部分。 thorntail-maven-plugin 的默认行为是仅在您未明确指定任何内容时自动检测分数。您可以通过 fractionDetectMode 属性更改此设置。

要实现自动检测模式,您应该执行以下步骤:

  1. Add the thorntail-maven-plugin to your pom.xml in a <plugin> block, with an <execution> specifying the package goal:
<plugins>
<plugin>
<groupId>io.thorntail</groupId>
<artifactId>thorntail-maven-plugin</artifactId>
<version>${version.thorntail}</version>
<executions>
<execution>
<id>package</id>
<goals>
<goal>package</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>

  1. Specify the API that you want to use in your pom.xml file; for example, JAX-RS, to implement a RESTful web service, as follows:
<dependencies>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>${version.jaxrs-api}</version>
<scope>provided</scope>
</dependency>
</dependencies>
  1. Build your project using the following command:
$ mvn package   
  1. Run the final, atomic Uber JAR, using the following command:
$ java -jar ./target/myapp-thorntail.jar   

要使用显式分数,设置特定版本,您应该执行以下步骤:

  1. Set the correct Bill Of Material (BOM) of the Thorntail version, as follows:
# Set the property relate to the Thorntail version
<properties>
<version.thorntail>2.0.0.Final</version.thorntail>
</properties>
...
  1. Set the BOM dependency in order to have the correct version of the fraction:
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.thorntail</groupId>
<artifactId>bom</artifactId>
<version>${version.thorntail}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
  1. Add the thorntail-maven-plugin to your pom.xml in a <plugin> block, with an <execution> specifying the package goal:
<plugins>
<plugin>
<groupId>io.thorntail</groupId>
<artifactId>thorntail-maven-plugin</artifactId>
<version>${version.thorntail}</version>
<executions>
<execution>
<id>package</id>
<goals>
<goal>package</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
  1. Add the dependency on the Thorntail fraction, for example, JAX-RS, to implement a RESTful web service, to the pom.xml file:
<dependencies>
<dependency>
<groupId>io.thorntail</groupId>
<artifactId>jaxrs</artifactId>
</dependency>
</dependencies>
  1. Build your project, using the following command:
$ mvn package  
  1. Run the final, atomic Uber JAR, using the following command:
$ java -jar   ./target/myapp-thorntail.jar  

Flexible configuration

Thorntail 让您有机会根据您的背景或内部政策,通过不同的策略轻松配置您的应用程序和环境。

您可以使用以下实现:

  • The thorntail-maven-plugin is used to set a specific fraction property (for example, the port for the default HTTP listener), as follows:
<swarm.http.port>8081</swarm.http.port>   
  • The traditional Java properties that you can set when you launch your application can be used as follows:
$ java -Dswarm.http.port=8081   myapp.jar  
  • The WildFly configuration file that you used in a previous implementation (or when you were migrating from a traditional Jakarta EE architecture to the microservice cloud native architecture) can be set with the following command:
$ java myapp.jar   -c standalone.xml  
  • You can also use a YML file (as implemented in most cloud environments and PaaS), by using the following command:
$ java myapp.jar   -s project-production.yml

Building a fantasy football application

介绍了 Thorntail 的特性及其与 Java EE/Jakarta EE 和 MicroProfile.io 的关系之后,是时候开始构建我们的微服务应用程序了。

我们将构建一个简单的梦幻足球应用程序,该应用程序分为四个 Maven 项目,如下所示:

  1. A microservice application that handles the football players domain; it will expose create, read, update, and delete (CRUD) API, and it will store and retrieve information using a PostgreSQL database.
  2. A microservice application that handles the fantasy football player domain and the presidents of the fantasy teams; it will expose a CRUD API, and it will store and retrieve information using a MySQL database.
  1. A microservice application that handles the fantasy team's domain; it will expose a CRUD API, and it will store and retrieve information using a MongoDB database.
  2. A microservice application that will be the web interface through which the various APIs call, in order to sign up to the fantasy league, create the fantasy team, and buy the football players.

我们决定使用不同的数据库,以向您展示微服务架构使用不同的语言工作,并确认微服务必须拥有特定数据域的概念。

为了构建我们的应用程序,我们将使用以下工具,并且对于每个工具,我们将指定安装它们所需的信息:

  1. Apache Maven 3.5.4: Here is the link to install (https://maven.apache.org/install.html)
  2. JDK 1.8.0_171: You are free to use Oracle JDK or OpenJDK, but we recommend OpenJDK (http://openjdk.java.net/install/)
  3. Thorntail 2.0.0: Here is the link to install (https://thorntail.io/)
  4. Docker Community edition 18.03.1 : This is so that you can easily install and use the different databases required for our application (https://docs.docker.com/install/)
  5. AngularJS: This is to build the frontend layer; we will describe the instructions to install it later on.

The football player microservice

在本节中,我们将构建与足球运动员管理相关的微服务。为此,我们需要在系统上安装 PostgreSQL,除了上一节中描述的先决条件。正如我之前所说,我们将使用 Docker 来安装和处理 PostgreSQL。我在工作环境中使用 macOS High Sierra,我将描述如何在 Docker 容器中安装和运行 PostgreSQL。

Database installation and configuration

在你的机器上安装 Docker 之后,是时候运行 PostgreSQL 的容器化版本了,如下所示:

  1. Open a new Terminal window and launch the following command:
$ docker run --name postgres_thorntail \
-e POSTGRES_PASSWORD=postgresPwd -e POSTGRES_DB=football_players_registry \
-d -p 5532:5432 postgres

此命令将触发从 Docker 的公共注册表中提取标记为最新的 PostgreSQL 版本,下载运行容器所需的所有层,如下面的输出所示:

Unable to find image 'postgres:latest' locally
latest: Pulling from library/postgres
683abbb4ea60: Pull complete
c5856e38168a: Pull complete
c3e6f1ceebb0: Pull complete
3303bcd00128: Pull complete
ea95ff44bf6e: Pull complete
ea3f31f1e620: Pull complete
234873881fb2: Pull complete
f020aa822d21: Pull complete
27bad92d09a5: Pull complete
6849f0681f5a: Pull complete
a112faac8662: Pull complete
bc92d0ab9365: Pull complete
9e87959714b8: Pull complete
ac7c29b2bea7: Pull complete
Digest: sha256:d99f15cb8d0f47f0a66274afe30102b5bb7a95464d1e25acb66ccf7bd7bd8479
Status: Downloaded newer image for postgres:latest
83812c6e76656f6abab5bf1f00f07dca7105d5227df3b3b66382659fa55b5077

之后,PostgreSQL 镜像作为容器启动。

  1. To verify this, you can launch the $ docker ps -a command, which gives you a list of the containers created and their relative statuses:
CONTAINER ID  IMAGE    COMMAND                  CREATED        

1073daeefc52  postgres "docker-entrypoint.s..." Less than a second ago 

 

STATUS         PORTS                             NAMES 

Up 4 seconds   0.0.0.0:5532->5432/tcp     
postgres_thorntail

命令结果被分成两行以使其可读。

  1. You can also check the container logs in order to retrieve information about the PostgreSQL status. Launch the following command:
$ docker logs   -f 1073daeefc52  

1073daeefc52 是容器 ID。您应该得到以下信息:

PostgreSQL init process complete; ready for start up.

2018-07-13 22:53:36.465 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
2018-07-13 22:53:36.466 UTC [1] LOG: listening on IPv6 address "::", port 5432
2018-07-13 22:53:36.469 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
  1. Now, it's time to connect to the container, in order to manage it. Launch the following command:
$ docker exec   -it 1073daeefc52 bash   

1073daeefc52 是容器 ID。现在,使用以下命令登录 PostgreSQL:

$ psql -U   postgres  
  1. Now, you will be able to interact with the database server, as follows:
psql (10.4 (Debian 10.4-2.pgdg90+1))
Type "help" for help.
postgres=#

您应该能够看到我们在启动容器创建时创建的 football_players_registry 数据库。

  1. Run the \l command to verify the list of databases:
postgres=# \l   

                                        List of databases   

          Name            |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges      

---------------------------+----------+----------+------------+------------+-----------------------   

 football_players_registry   | postgres | UTF8       | en_US.utf8 | en_US.utf8 |   

 postgres                 | postgres | UTF8    | en_US.utf8 | en_US.utf8 |   

 template0                | postgres | UTF8    | en_US.utf8 | en_US.utf8 | =c/postgres       +   

                          |             |             |             |             |   postgres=CTc/postgres   

 template1                | postgres | UTF8    | en_US.utf8 | en_US.utf8 | =c/postgres       +   

                          |             |             |             |             |   postgres=CTc/postgres   

(4 rows) 

好的。现在是时候创建一个简单的表格来存放足球运动员的数据了。

  1. From the running Docker container instance, you should connect to the football_players database with the following command:
$ \connect   football_players_registry  
  1. Create the table with the following command:
CREATE TABLE FOOTBALL_PLAYER(
ID SERIAL PRIMARY KEY NOT NULL,
NAME VARCHAR(50) NOT NULL,
SURNAME VARCHAR(50) NOT NULL,
AGE INT NOT NULL,
TEAM VARCHAR(50) NOT NULL,
POSITION VARCHAR(50) NOT NULL,
PRICE NUMERIC
);
  1. Check the table's structure with the following command:
$  \d+   football_player   

您应该看到以下结果:

football_players_registry=#   \d+ football_player   

                           Table "public.football_player"   

  Column  | Type   | Collation | Nullable | Default | Storage  | Stats target | Description   

----------+-----------------------+-----------+----------+----------------------   

-----------------------+----------+--------------+-------------   

 id       |   integer |  | not null | nextval('football_player_id_seq'::regclass) | plain   |               |   

 name     |   character varying(50) | | not null | | extended |           |   

 surname  |   character varying(50) | | not null | | extended |           |   

 age      |   integer | | not null |   | plain | |   

 team     |   character varying(50) | | not null | | extended | |   

 position |   character varying(50) | | not null | | extended |    |   

 price    |   numeric | | | | main | |    

Indexes:   

   "football_player_pkey"   PRIMARY KEY, btree (id)   

Creating the source code

我们已经安装并配置了创建用于管理玩家注册表的微服务所需的一切。现在,是时候编写公开我们的微服务 API 所需的代码了。

我们将使用 Thorntail 项目生成器实用程序,https://thorntail.io/generator/,在为了得到一个项目框架来工作。如前所述,我们的微服务必须显示允许我们执行 CRUD 操作的 API。 Java EE 和 Jakarta EE 有实现这些功能的规范,如下所示:

  • JAX-RS
  • CDI
  • JPA
  • JTA

我们将使用 com.packtpub.thorntail 作为项目的 Maven Group ID,并使用 football-player-microservice 作为 工件 ID:

  1. Set the Group ID, Artifact ID, and Dependencies values in the project form generator, as shown in the following screenshot:
读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Thornail构建微服务
  1. Click on Generate Project to create and download the ZIP file with the project skeleton.
  1. Unzip the file in a directory of your choice, and open the Maven project with your favorite IDE (Eclipse, NetBeans, IntelliJ, and so on).

该项目的核心元素是 Maven pom.xml 文件,其中包含实现我们的微服务所需的所有依赖项。

项目使用BOM正确管理依赖管理,如下:

<properties>
...
<version.thorntail>2.0.0.Final</version.thorntail>
...
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.thorntail</groupId>
<artifactId>bom-all</artifactId>
<version>${version.thorntail}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
  1. The default value is set to bom-all, which includes all of the fractions—stable, unstable, experimental, and even deprecated. We need to change it to bom, which only includes the stable fractions; it's recommended for daily use:
<properties>
...
<version.thorntail>2.0.0.Final</version.thorntail>
...
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.thorntail</groupId>
<artifactId>bom</artifactId>
<version>${version.thorntail}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
  1. We will also change the value of the project name from Thorntail Example to Thorntail Football player microservice:
...
<modelVersion>4.0.0</modelVersion>
<groupId>com.packtpub.thorntail</groupId>
<artifactId>football-player-microservice</artifactId>
<name>Thorntail Football player microservice</name>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
...
  1. We will change the final name of the artifact, created in the build phase, from demo to football-player-microservice, as follows:
<build>
<finalName>football-player-microservice</finalName>
...
</build>
  1. Now, launch the first build with the following command:
$ mvn clean   package 

这样,我们将下载所有依赖项并创建名为 football-player-microservice.war 的 WAR 工件和超级可执行 JAR 文件 football-player-microservice-thorntail。罐子

要检查项目是否可以使用,我们可以使用以下命令运行它:

$ mvn thorntail:run  

首先,您会注意到所有与 JAX-RS、CDI 和 JPA 相关的部分都已安装并运行,如下所示:

2018-07-14 16:38:50,356 INFO [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction: JAX-RS - STABLE io.thorntail:jaxrs:2.0.0.Final
2018-07-14 16:38:50,363 INFO [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction: Logging - STABLE io.thorntail:logging:2.0.0.Final
2018-07-14 16:38:50,363 INFO [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction: Undertow - STABLE io.thorntail:undertow:2.0.0.Final
2018-07-14 16:38:50,364 INFO [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction: Elytron - STABLE io.thorntail:elytron:2.0.0.Final
2018-07-14 16:38:50,364 INFO [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction: CDI - STABLE io.thorntail:cdi:2.0.0.Final
2018-07-14 16:38:50,364 INFO [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction: CDI Configuration - STABLE io.thorntail:cdi-config:2.0.0.Final
2018-07-14 16:38:50,364 INFO [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction: Bean Validation - STABLE io.thorntail:bean-validation:2.0.0.Final
2018-07-14 16:38:50,365 INFO [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction: Transactions - STABLE io.thorntail:transactions:2.0.0.Final
2018-07-14 16:38:50,365 INFO [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction: JPA - STABLE io.thorntail:jpa:2.0.0.Final
2018-07-14 16:38:50,365 INFO [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction: Datasources - STABLE io.thorntail:datasources:2.0.0.Final
2018-07-14 16:38:50,365 INFO [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction: JCA - STABLE io.thorntail:jca:2.0.0.Final

然后,您可以看到 Thorntail 正在运行,并且应用程序已部署:

2018-07-14 16:38:55,555 INFO [org.wildfly.extension.undertow] (ServerService Thread Pool -- 10) WFLYUT0021: Registered web context: '/' for server 'default-server'
2018-07-14 16:38:55,598 INFO [org.jboss.as.server] (main) WFLYSRV0010: Deployed "football-player-microservice.war" (runtime-name : "football-player-microservice.war")
2018-07-14 16:38:55,607 INFO [org.wildfly.swarm] (main) WFSWARM99999: Thorntail is Ready

最后,我们可以在 http://localhost:8080/hello 调用应用程序,并看到消息 Hello from Thorntail!。现在,让我们使用 Ctrl + C 命令停止 Thorntail,并开始更新我们的项目。

Entity class – JPA

我们需要一个域模型对象来映射插入到我们数据库中的记录。为此,我们将使用 JPA 规范;因此,我们将为此创建一个实体类,如下所示:

  1. Let's create a new Java package named model, in order to store the data model class. The fully qualified package name will be com.packtpub.thorntail.footballplayermicroservice.model.
  2. Next, we will build the domain class, named FootballPlayer:
package   com.packtpub.thorntail.footballplayermicroservice.model;

import java.math.BigInteger;
...

/**
* Domain model class that maps the data stored into football_player table
* inside database.
*
* @author Mauro Vocale
* @version 1.0.0 15/08/2018
*/
@Entity
@Table(name = "football_player")
@XmlRootElement
@NamedQueries({
@NamedQuery(name = "FootballPlayer.findAll", query
= "SELECT f FROM FootballPlayer f")
})
public class FootballPlayer {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "id")
private Integer id;

@Basic(optional = false)
@NotNull
@Size(min = 1, max = 50)
@Column(name = "name")
private String name;

@Basic(optional = false)
@NotNull
@Size(min = 1, max = 50)
@Column(name = "surname")
private String surname;

@Basic(optional = false)
@NotNull
@Column(name = "age")
private int age;

@Basic(optional = false)
@NotNull
@Size(min = 1, max = 50)
@Column(name = "team")
private String team;

@Basic(optional = false)
@NotNull
@Size(min = 1, max = 50)
@Column(name = "position")
private String position;

@Column(name = "price")
private BigInteger price;

public FootballPlayer() {
}

public FootballPlayer(String name, String surname, int age,
String team, String position, BigInteger price) {
this.name = name;
this.surname = surname;
this.age = age;
this.team = team;
this.position = position;
this.price = price;
}

...

@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}

@Override
public boolean equals(Object object) {
if (!(object instanceof FootballPlayer)) {
return false;
}
FootballPlayer other = (FootballPlayer) object;
return !((this.id == null && other.getId() != null)

|| (this.id != null && !this.id.equals(other.getId())));
}

...

}
  1. To complete the management of the database access operations, we need to configure the persistence.xml file, inside src/main/resources/META-INF directory, where we will store all the configurations related to our persistence layer:
<?xml   version="1.0" encoding="UTF-8" standalone="no"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="FootballPlayerPU" transaction-type="JTA">
<jta-data-source>java:/jboss/datasources/FootballPlayerDS</jta-data-source>
<properties>
<property name="javax.persistence.schema-generation.database.action"
value="drop-and-create"/>
<property name="javax.persistence.schema-generation.drop-source"
value="metadata"/>
<property name="javax.persistence.schema-generation.create-script
source"
value="META-INF/create.sql" />
<property name="javax.persistence.sql-load-script-source"
value="META-INF/load.sql"/>
</properties>
</persistence-unit>
</persistence>

最重要的元素如下:

  • The name of the persistence unit (FootballPlayerPU)
  • The transaction type, JTA (which means that we demand to the container the management of the transactions)
  • The data source JNDI name to use (java:/jboss/datasources/FootballPlayerDS)
  • The load script source that will preload a subset of data into the database
  1. Finally, we will create the default project (the defaults.yml file) in the src/main/resources/ directory, where we will define the data source, FootballPlayerDS, which contains the configuration to connect to the database:
swarm:
datasources:
data-sources:
FootballPlayerDS:
driver-name: postgresql
connection-url:jdbc:postgresql://localhost:5532/football_players_registry
user-name: postgres
password: postgresPwd
valid-connection-checker-class-name: org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker
validate-on-match: true
background-validation: false
exception-sorter-class-name: org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter
  1. We will set the relative dependency in a Maven pom.xml file, as follows:
<properties>
...
<version.postgresql>9.4.1207</version.postgresql>
</properties>
<dependency>
<groupId>io.thorntail</groupId>
<artifactId>datasources</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${version.postgresql}</version>
</dependency>

声明供应商 JDBC 库依赖项,Thorntail 将自动检测并安装驱动程序。

RESTful web service – JAX-RS and CDI

现在,是时候构建我们​​的 RESTful Web 服务实现了,如下所示:

  1. Delete the default implementation class, HelloWorldEndpoint.java, created by the Thorntail generator.
  2. Define a new package, named service (the fully qualified name will be com.packtpub.thorntail.footballplayermicroservice.rest.service), where we will put the RESTful web service implementation.
  3. Finally, let's build the API implementations by using the CDI specifications (in order to inject the entity manager used to manage database connections) and the JAX-RS (in order to implement the RESTful operations). We will create an AbstractFacade class that defines the standard CRUD of our microservice:
package   com.packtpub.thorntail.footballplayermicroservice.rest.service;

import java.util.List;
import javax.persistence.EntityManager;

/**
* Abstract facade used to define the CRUD APIs of our micro service.
*
* @author Mauro Vocale
* @param <T> The type class of our entity domain.
* @version 1.0.0 17/08/2018
*/
public abstract class AbstractFacade<T> {

private final Class<T> entityClass;

public AbstractFacade(Class<T> entityClass) {
this.entityClass = entityClass;
}

protected abstract EntityManager getEntityManager();

public void create(T entity) {
getEntityManager().persist(entity);
}

public void edit(T entity) {
getEntityManager().merge(entity);
}

public void remove(T entity) {
getEntityManager().remove(getEntityManager().merge(entity));
}

public T find(Object id) {
return getEntityManager().find(entityClass, id);
}

public List<T> findAll() {
javax.persistence.criteria.CriteriaQuery<T> cq =
getEntityManager().getCriteriaBuilder().createQuery(entityClass);
cq.select(cq.from(entityClass));
return getEntityManager().createQuery(cq).getResultList();
}
}
  1. Finally, the real implementation is as follows:
package   com.packtpub.thorntail.footballplayermicroservice.rest.service;

import com.packtpub.thorntail.footballplayermicroservice.model.FootballPlayer;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
* Class that exposes the APIs implementations of our CRUD micro service.
*
* @author Mauro Vocale
* @version 1.0.0 17/08/2018
*/
@ApplicationScoped
@Path("/footballplayer")
public class FootballPlayerFacadeREST extends AbstractFacade<FootballPlayer> {

@PersistenceContext(unitName = "FootballPlayerPU")
private EntityManager em;

public FootballPlayerFacadeREST() {
super(FootballPlayer.class);
}

@POST
@Override
@Consumes({MediaType.APPLICATION_JSON})
@Transactional(Transactional.TxType.REQUIRES_NEW)
public void create(FootballPlayer entity) {
super.create(entity);
}

@PUT
@Path("{id}")
@Consumes({MediaType.APPLICATION_JSON})
@Transactional(Transactional.TxType.REQUIRES_NEW)
public void edit(@PathParam("id") Integer id, FootballPlayer entity) {
super.edit(entity);
}

@DELETE
@Path("{id}")
@Transactional(Transactional.TxType.REQUIRES_NEW)
public void remove(@PathParam("id") Integer id) {
super.remove(super.find(id));
}

@GET
@Path("{id}")
@Produces({MediaType.APPLICATION_JSON})
public FootballPlayer find(@PathParam("id") Integer id) {
return super.find(id);
}

@GET
@Override
@Produces({MediaType.APPLICATION_JSON})
public List<FootballPlayer> findAll() {
return super.findAll();
}

@Override
protected EntityManager getEntityManager() {
return em;
}

}
  1. Now, we will be able to create our Uber JAR, with the following command:
$ mvn clean package 
  1. Launch our microservice application by using the following command:
$ java -jar target/football-player-microservice-thorntail.jar 

让我们检查应用程序是否已部署以及 Thorntail 是否已启动并运行,如下所示:

2018-07-17 16:41:31,640 INFO [org.wildfly.extension.undertow] (ServerService Thread Pool -- 12) WFLYUT0021: Registered web context: '/' for server 'default-server'
2018-07-17 16:41:31,674 INFO [org.jboss.as.server] (main) WFLYSRV0010: Deployed "football-player-microservice.war" (runtime-name : "football-player-microservice.war")
2018-07-17 16:41:31,680 INFO [org.wildfly.swarm] (main) WFSWARM99999: Thorntail is Ready
  1. Now, invoke the API that retrieves the list of football players, as follows:
$ curl   http://localhost:8080/footballplayer | json_pp   

输出应该类似于以下内容(为方便起见,这里只截取了一部分):

[
{
"id":1,
"name":"Gianluigi",
"surname":"Buffon",
"age":40,
"team":"Paris Saint Germain",
"position":"goalkeeper",
"price":2
},
{
"id":2,
"name":"Manuel",
"surname":"Neuer",
"age":32,
"team":"Bayern Munchen",
"position":"goalkeeper",
"price":35
},
{
"id":3,
"name":"Keylor",
"surname":"Navas",
"age":31,
"team":"Real Madrid",
"position":"goalkeeper",
"price":18
},
...
]
  1. Finally, we will create the JUnit test, in order to ensure that our APIs are working properly. Let's add the Maven dependencies, as follows:
<properties>
...
<version.resteasy>3.0.19.Final</version.resteasy>
</properties>

<dependency>
<groupId>io.thorntail</groupId>
<artifactId>arquillian</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>${version.resteasy}</version>
<scope>test</scope>
</dependency>
  1. Then, we will create the test class, named FootballPlayerFacadeRESTTest, in the package com.packtpub.thorntail.footballplayermicroservice.rest.service, under the src/main/test directory:
package com.packtpub.thorntail.footballplayermicroservice.rest.service;

import com.packtpub.thorntail.footballplayermicroservice.model.FootballPlayer;
import com.packtpub.thorntail.footballplayermicroservice.rest.RestApplication;
...

/**
* Unit Test class needed to test the APIs.
*
* @author Mauro Vocale
* @version 1.0.0 18/07/2018
*/
@RunWith(Arquillian.class)
public class FootballPlayerFacadeRESTTest {
private static final String API_URL = "http://localhost:8080/footballplayer";

/**
*
* @return @throws Exception
*/
@Deployment
public static Archive createDeployment() throws Exception {
JAXRSArchive deployment = ShrinkWrap.create(JAXRSArchive.class);
deployment.addPackage(FootballPlayer.class.getPackage());
deployment.addPackage(AbstractFacade.class.getPackage());
deployment.addPackage(RestApplication.class.getPackage());

deployment.addAsWebInfResource(new ClassLoaderAsset(
"META-INF/create.sql", FootballPlayerFacadeREST.class.
getClassLoader()),"classes/META-INF/create.sql");

deployment.addAsWebInfResource(new ClassLoaderAsset(
"META-INF/load.sql", FootballPlayerFacadeREST.class.getClassLoader()),
"classes/META-INF/load.sql");

deployment.addAsWebInfResource(new ClassLoaderAsset(
"META-INF/persistence.xml", FootballPlayerFacadeREST.class.
getClassLoader()), "classes/META-INF/persistence.xml");

deployment.addAsWebInfResource(new ClassLoaderAsset(
"project-defaults.yml", FootballPlayerFacadeREST.class.
getClassLoader()), "classes/project-defaults.yml");

deployment.addAllDependencies();
System.out.println(deployment.toString(true));
return deployment;
}

private final Client client = ClientBuilder.newBuilder().build();
private WebTarget target;

...
@Before
public void setUp() {
target = client.target(API_URL);
}

/**
* Test of create method, of class FootballPlayerFacadeREST.
*/
@Test
@InSequence(2)
public void testCreate() {
System.out.println("create");
FootballPlayer player = new FootballPlayer("Mauro", "Vocale", 38,
"Juventus", "central midfielder", new BigInteger("100"));
Response response = target.request().post(Entity.entity(player,
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus(), is(Response.Status.NO_CONTENT.
getStatusCode()));
}

...
}

一些著名的球员失踪了吗?在本章的最后,我们将构建一个图形用户界面,帮助您插入您喜欢的用户界面。

The football manager microservice

在本节中,我们将构建与管理足球经理(管理梦幻球队和参与梦幻联赛的人)相关的微服务。为此,除了前面描述的先决条件外,您还需要在系统上安装 MySQL;而且,就像我们为 PostgreSQL 所做的那样,我们将使用 Docker 来安装和处理它。该项目的结构将与上一节中已经创建的非常相似;因此,我们将只关注不同的方面。

Database installation and configuration

安装 MySQL Docker 映像,请执行以下步骤:

  1. Open a new Terminal window and launch the following command, setting the name of the image, the port, and the credentials:
$ docker run --name mysql_thorntail -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql  

之后,MySQL 镜像将作为容器启动。

  1. To verify this, you can launch the $ docker ps -a command, which gives you a list of the containers created and their relative statuses. The status must be up.
  2. You can also check the container logs, in order to retrieve information about the MySQL status. Launch the following command:
$ docker logs   -f 6f1f54a5b932 

6f1f54a5b932 是容器 ID。您应该看到以下信息:

[System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.11' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL.
  1. Now, it's time to connect to the container, in order to manage it. Launch the following command:
$ docker exec   -it 6f1f54a5b932 bash 

6f1f54a5b932 是容器 ID。

  1. Now, log in to the MySQL with the following command:
$ mysql -u   root -p   
  1. Set the password that's required (in our case, root). Now, you will be able to interact with the database server:
Welcome to the   MySQL monitor.  Commands end with ; or \g.   

Your MySQL   connection id is 18   

Server   version: 8.0.11 MySQL Community Server - GPL
  1. Let's create the database that's needed for our microservice and select it for use, as follows:
$ CREATE DATABASE   football_managers_registry;   

$ use   football_managers_registry;   
  1. You can create the table in the container, as showing below, or delegate it to the source code implementation of your microservice. The first one is an easy but poor approach to use only in development environment:
CREATE TABLE FOOTBALL_MANAGER(
ID SERIAL PRIMARY KEY NOT NULL,
NAME VARCHAR(50) NOT NULL,
SURNAME VARCHAR(50) NOT NULL,
AGE INT NOT NULL,
NICKNAME VARCHAR(50) NOT NULL
);

Creating the source code

我们已经安装并配置了创建用于管理幻想管理器注册表的微服务所需的一切。现在,是时候编写公开我们的微服务 API 所需的代码了。

我们将遵循为足球运动员的微服务描述的相同步骤:

  1. We will connect to the Thorntail project generator utility, http://wildfly-swarm.io/generator/, in order to get our project skeleton to work on, and we will select the following dependencies:
    • JAX-RS
    • CDI
    • JPA
    • JTA
  2. We will use com.packtpub.thorntail as the Maven Group ID of the project, and fantasy-player-microservice as the Artifact ID.
  3. Click on Generate Project to create and download the ZIP file with the project skeleton.
  4. Unzip the file in a directory of your choice, and open the Maven project with your favorite IDE (Eclipse, NetBeans, IntelliJ, and so on).

请记住执行以下重要步骤:

  1. Change the value of the BOM from bom-all to bom, in order to only include the stable fractions that are recommended for daily use.
  2. Change the value of the project name from Thorntail Example to Thorntail Football manager microservice.
  3. Change the final name of the artifact, created in the build phase, from demo to football-manager-microservice.
  4. Now, launch the first build with the following command:
$ mvn clean package 

这样,我们将下载所有依赖项并创建名为 football-manager-microservice.war 的 WAR 工件和 Uber 可执行 JAR 文件 football- manager-microservice-thorntail.jar

Entity class – JPA

我们需要一个域模型对象来映射插入到我们数据库中的记录。为此,我们将使用 JPA 规范;因此,我们将为此创建一个实体类:

  1. Let's create a new Java package, to store it, and name it model. The fully qualified package name will be com.packtpub.thorntail.footballmanagermicroservice.model.
  2. After that, we will build the domain class, named FootballManager (only the fields section is shown here):
...

/**
* Domain model class that maps the data stored into football_manager table
* inside database.
*
* @author Mauro Vocale
* @version 1.0.0 19/08/2018
*/

@Entity
@Table(name = "FOOTBALL_MANAGER")
@XmlRootElement
@NamedQueries({
@NamedQuery(name = "FootballManager.findAll", query
= "SELECT f FROM FootballManager f")
})
public class FootballManager {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "ID")
private Long id;

@Basic(optional = false)
@NotNull
@Size(min = 1, max = 50)
@Column(name = "NAME")
private String name;

@Basic(optional = false)
@NotNull
@Size(min = 1, max = 50)
@Column(name = "SURNAME")
private String surname;

@Basic(optional = false)
@NotNull
@Column(name = "AGE")
private int age;

@Basic(optional = false)
@NotNull
@Size(min = 1, max = 50)
@Column(name = "NICKNAME")
private String nickname;

...

}
  1. To complete the management of database access operations, we need to configure the persistence.xml file, inside the src/main/resources/META-INF directory, where we will store all of the configurations related to our persistence layer. Remember to set:
    • The name of the persistence unit (FootballManagerPU)
    • The transaction type (JTA), which means that we demand to the container the management of the transactions
    • The data source JNDI name to use (java:/jboss/datasources/FootballManagerDS)
    • The load script source that will preload a subset of data into the database
  1. Finally, we will create the default project (the defaults.yml file) in the src/main/resources/ directory, where we will define the data source, FootballManagerDS, that contains the configuration to connect to the database:
swarm:
datasources:
data-sources:
FootballManagerDS:
driver-name: mysql
connection-url: jdbc:mysql://localhost:3306/football_managers_registry
user-name: root
password: root
valid-connection-checker-class-name: org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker
validate-on-match: true
background-validation: false
exception-sorter-class-name: org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter
  1. Let's complete the process by setting the relative dependency to io.thorntail.datasources and the mysql driver in the Maven pom.xml file.
  2. Declaring the vendor JDBC library dependency, Thorntail will automatically detect and install the driver.

RESTful web service – JAX-RS and CDI

现在,是时候构建我们​​的 RESTful Web 服务实现了,如下所示:

  1. Delete the default implementation class, HelloWorldEndpoint.java, created by the Thorntail generator.
  2. Define a new package, named service (the fully qualified name will be com.packtpub.thorntail.footballmanagermicroservice.rest.service), where we will put the RESTful web service implementation.
  3. Build the API implementations by using the CDI specifications (to inject the entity manager used to manage the database connections) and JAX-RS (to implement the CRUD RESTful operations). You can follow the same steps that were implemented in the The football player microservice section.
  4. Remember to set the @Path of your RESTful implementation to /footballmanager.
  5. Before we create our Uber JAR and run the application, we must change the port that the Thorntail instance will use. You must remember that there is the football player microservice that runs on the default 8080 port.
  6. To avoid port conflict, we will set the following property in the Maven pom.xml file:
...
<configuration>
<properties>
<swarm.port.offset>100</swarm.port.offset>
</properties>
</configuration>
...
  1. Create our Uber JAR using the following command:
$ mvn clean   package  
  1. Then, launch our microservice application with the following command:
$ java -jar target/football-manager-microservice-thorntail.jar
  1. Now, we can invoke the API that retrieves the list of football managers, using the following command:
$ curl http://localhost:8080/footballmanager | json_pp
  1. Finally, we will create the JUnit test, in order to ensure that our APIs work properly. Let's add the Maven dependencies, as follows:
<properties>
...
<version.resteasy>3.0.19.Final</version.resteasy>
</properties>

<dependency>
<groupId>io.thorntail</groupId>
<artifactId>arquillian</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>${version.resteasy}</version>
<scope>test</scope>
</dependency>
  1. Then, we will create the test class, named FootballManagerFacadeRESTTest, in the package com.packtpub.thorntail.footballmanagermicroservice.rest.service, under the src/main/test directory.

按照在Entity class – JPA部分中描述的相同步骤,并尝试自己做。如果不能,请不要担心;在本节的最后,我会给你一个指向 GitHub 存储库的链接以找到解决方案。

The football team microservice

在本节中,我们将构建与参与梦幻联赛的足球队的管理相关的微服务。是时候衡量您的技能并尝试根据本节中描述的规范构建微服务了。

在本章的最后,会有一个指向 GitHub 存储库的链接,您可以在其中找到最终的实现。

Database installation and configuration

第一步是安装数据库。对于这个微服务,我们将使用 MongoDB,并且与之前的微服务一样,我们将使用 Docker 来安装和处理它。

您应该安装和配置 Docker 映像,并满足以下要求:

  • Set the name of your image to mongo_thorntail.
  • Expose, via TCP, the default MongoDB—27017.
  • Run the container in the background and print the container ID.

之后,应该将 MongoDB 映像作为容器启动。启动将验证这一点的命令,并为您提供已创建容器及其相关状态的列表。

您应该主要关注以下属性:

  • Status: To verify that the container is up and running
  • Ports: To verify that the default port, 27017, is exposed

Creating the source code

我们已经安装并配置了创建用于管理团队注册表的微服务所需的一切。现在,是时候为我们的微服务 API 编写 Java 代码了。

您应该创建一个具有以下依赖项的 Maven 项目:

  • JAX-RS
  • CDI
  • MongoDB Java driver
  • Arquillian
  • RESTEasy client

在这种情况下,您不会使用 JPA,因为 Thorntail 2.0.0 版不支持使用 Hibernate OGM,它允许使用 NoSQL 数据存储和 JPA。

请参阅 https://docs.thorntail.io/ 上的文档4.0.0-SNAPSHOT/#component-ogm 了解更多详情。

请记住还要设置以下属性:

  • The Maven Group ID will be com.packtpub.thorntail.
  • The Maven Artifact ID will be football-team-microservice.
  • The BOM that will only include the stable fractions that are recommended for daily use.
  • The Project Name will be Thorntail Football team microservice.
  • The final name of the artifact, created in the build phase, will be football-team-microservice.
  • The HTTP port that is used by Thorntail; there are the other two microservices, which could be up and running at the same time.

构建项目,并确保包含名为 football-team-microservice.war 的 WAR 工件和 executable Uber JAR、football-team-microservice-thorntail .jar

在此阶段结束时,您将准备好实现实现目标所需的 Java 类。

设计和构建类来执行此操作,如下所示:

  • A model class that simulates a JPA entity, with the following attributes:
    • String ID: The ID primary key used by the MongoDB collection
    • String name: The name of your football team
    • String city: The name of the city that usually hosts the matches of your team
    • Set <Integer> footballManagerId: The primary key of the football manager of your team, managed by the football manager microservice
    • Set <Integer> footballPlayersId: The list of the primary keys of the football players of your team, managed by the football player microservice
  • A service class that behaves like a Data Access Object (DAO), needed to manage the MongoDB CRUD operation. Here, you should implement the following operations:
    • An initialize phase, where you do the following:
      • Connect to MongoDB
      • Create the database, if it doesn't exist
      • Create the collection, if it doesn't exist
      • Delete the previous collection's values
      • Load the collection's data
    • A destroy phase, where you close the connection to the database.
    • A getTeams method that retrieves all the values.
    • A find method that retrieves the record with the primary key.
    • An edit method that modifies the record.
    • A delete method, to remove the record.
  • A RESTful web service that implements the CRUD operation with the verbs GET, POST, PUT, and DELETE.
  • A beans.xml file, needed to activate the CDI in the container.
  • A JUnit test to verify the implementations of the RESTful APIs.

好的。现在,您应该可以使用以下命令创建 Uber JAR

$ mvn clean package

启动我们的微服务应用程序,如下:

$ java -jar target/football-team-microservice-thorntail.jar

尝试调用检索足球队列表的 API,如下所示:

$ curl http://localhost:8280/footballteam | json_pp

您将收到类似于以下内容的输出:

[
{
"footballPlayersId": [
1,
4,
6,
7,
10,
12,
14,
16,
18,
19,
20
],
"city": "Turin",
"name": "Mauryno_NewTeam",
"footballManagerId": 1,
"id": "5b548e7097007545c9904ce4"
},
{
"footballPlayersId": [
2,
5,
8,
9,
11,
13,
15,
17,
21,
22,
23
],
"name": "Foogaro_Great_Lazie",
"city": "Rome",
"id": "5b548e7097007545c9904ce5",
"footballManagerId": 2
}
]

您将在本章末尾找到微服务的完整实现。

The user interface microservice

现代企业应用程序的设计基本概念是前端与后端分离。

这两个层具有非常不同的特征,因此,它们需要不同的方法,具体如下:

  • Software development and life cycle
  • Resource management (CPU, thread, throughput)
  • Scalability and service levels

由于这些原因,所有新应用程序,从使用单体架构构建的应用程序到使用微服务设计的新应用程序,都发布了专门用于管理后端和前端的组件。

虽然对于后端组件,Jakarta EE 和 MicroProfile 为创建更多功能规范以实现面向云的应用程序做出了贡献,但对于前端组件却不能这么说。

目前,平台只提供了服务端的完整规范,而前端层只有一个基于组件模型概念的规范,一个灵活易用的用户界面元素——Java服务器端面 (JSF)。

Java EE/Jakarta EE 8 提供了 JSF 2.3 版,它代表了用于构建应用程序的标准用户界面。

尽管从 2.0 版开始对它们进行了全面修订,并以简洁的形式与 JEE 6 一起发布,但 JSF 在开发人员社区中逐渐失去了吸引力,尽管在最新版本中进行了所有改进。

许多对 JSF 技术的批评已经发布,其中最著名的是 Technology Radar 出版物,该出版物可在 https://www.thoughtworks.com/radar/languages-and-frameworks/jsf.

主要批评如下:

  • The use of a shared stateful state inside the components
  • The presence, in the same place, of the user interface and the business logic concepts

JSF 规范领导者试图回应这些批评(例如,在 https://www.primefaces.org/jsf-is-not-what-youve-been-told-anymore/),但没有取得任何巨大成功。跨度>

为了尝试克服 JSF 中发现的限制,建议创建一个新的 JSR,MVC 1.0/JSR 371,基于基于动作的模型,这应该是 Spring MVC 的答案。但是 MVC 在最后阶段被否决了,它仍然作为基于社区的规范存在。

尽管我确实考虑将 Jakarta EE 和极其成熟的 MicroProfiles 用于实现 Java Enterprise 和面向云的应用程序,但我不认为它是实现前端架构的正确选择。

从这个角度来看,Thorntail 只通过特定的部分提供对 JSF 的支持,如下所示:

<dependency>
<groupId>io.thorntail</groupId>
<artifactId>jsf</artifactId>
</dependency>

由于这些原因,建议采用微服务架构,即使是前端组件,以便能够修改用户体验的特定元素,而不必发布整个图形界面,但使用遵循 HTML 5 规范的框架( CSS 3 和 ECMAScript)。

这部分有许多框架,如下所示:

基本思想是将原始的单体前端划分为多个微服务,可能使用不同的语言和框架编写,并使用微前端的概念来协调它们。

该术语相当于微服务后端,但与前端世界相关。

基本思想是构建一个单页应用程序,该应用程序是由专用微服务实现的功能的组合。

正如后端微服务所描述的那样,微前端应该组织成垂直团队,负责实现用户体验的特定领域。

每个微前端都可以使用团队评估为在业务需求方面最佳的技术来实现

最后,您可以拥有一种调用微前端的聚合器单页应用程序,以构建最终的用户体验。

这种方法使您能够执行以下操作:

  • Be technologically agnostic: Make the team free to choose the technology.
  • Isolate the team code: The applications are independent and self-contained. In this way, every micro frontend can have a dedicated life cycle.
  • Favor native browser features over custom APIs: In this way, you can build a more effective and easily portable user interface.
  • Build a resilient site: Through the use of JavaScript that is asynchronous, and it's designed for native manages the failure.

构建用户体验超出了本章的范围,因为我们正在 Thorntail 中评估 Jakarta EE 的功能。出于这个原因,我将向您展示一种为您的特定微服务创建用户界面的快速方法,以便以 JUnit 测试以外的方式测试 API。

Building a user interface

要构建一个公开 API 的基本 HTML5 应用程序,您需要实施几个步骤。首先,你需要让你之前的微服务能够接受来自不同域的 HTTP 请求(这个特性叫做跨域资源共享)。

为了做到这一点,我使用了一个开源插件,它在 Java EE 7 环境中启用了对 JAX-RS 2.0 的 CORS 支持。

将这部分 XML 放入前面​​部分构建的微服务的 pom.xml 文件中,如下所示:

<!-- CORS Support For JAX-RS 2.0 / JavaEE 7  --> 

<dependency>
<groupId>com.airhacks</groupId>
<artifactId>jaxrs-cors</artifactId>
<version>0.0.2</version>
<scope>compile</scope>
</dependency>

该插件创建了在微服务中实现 CORS 功能所需的过滤器,而无需编写其他 Java 代码。

你必须决定是构建一个包含所有微服务用户体验的整体用户界面,还是将逻辑拆分为不同的微前端,然后创建一个聚合它们的单页应用程序。

让我们开始构建足球运动员微服务的用户界面。

我使用 Angular 6 和 Bootstrap 4 创建了一个新的 HTML 5 项目,名为 football-player-ui。为了加快开发速度,我使用了 Angular CLI,它使应用程序的创建变得轻松

要运行您将在 GitHub 存储库中找到的示例,或创建用户界面,您需要安装 Angular CLI 和相关依赖项。

启动以下命令来安装 NVM、NPM 和 NodeJS:

$ touch ~/.bash_profile
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash

关闭您的终端以使您的更改生效并打开一个新的。然后,启动以下命令:

$ nvm ls-remote 
$ nvm install 8.11.3
$ npm install -g @angular/cli
$ npm install --save bootstrap font-awesome
$ npm install rxjs@6 rxjs-compat@6 --save

现在,您已准备好创建实现表示视图(HTML 和 CSS)和业务逻辑(JavaScript,以调用 RESTful API)所需的文件。

由于应用程序仅使用 HTML 和 JavaScript 文件构建,您可以将它们放入 Apache Web 服务器目录(例如,/var/www/html),然后启动它:

$ /etc/init.d/httpd start

您应该获得类似于以下屏幕截图的结果:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Thornail构建微服务

Summary