使用Quarkus在Openshift上构建微服务的快速指南

在个人博客上,您有机会阅读了许多关于使用Spring Boot或Micronaut之类框架构建微服务的文章。这里将介绍另外一个很是有趣的框架专门用于微服务体系结构,它愈来愈受到你们的关注– Quarkus。它是做为下一代Kubernetes/Openshift原生Java框架引入的。它构建在著名的Java标准之上,如CDI、JAX-RS和Eclipse MicroProfile,这些标准将它与Spring Boot区别开来。java

其余一些可能说服您使用Quarkus的特性包括很是快的启动时间、为在容器中运行而优化的最小内存占用,以及较短的首次请求时间。此外,尽管它是一个相对较新的框架(当前版本是0.21),但它有不少扩展,包括Hibernate、Kafka、RabbitMQ、Openapi和Vert.x等等。git

在本文中,我将指导您使用Quarkus构建微服务,并在OpenShift(经过Minishift)上运行它们。咱们将讨论如下主题:github

  • 构建基于rest的且包含输入校验的应用程序
  • 微服务与RestClient之间的通讯
  • 开放健康检查(liveness, readiness)
  • 开放OpenAPI /Swagger 文档
  • 使用Quarkus Maven插件在本地机器上运行应用程序
  • 使用JUnit和RestAssured进行测试
  • 使用source-2镜像在Minishift上部署和运行Quarkus应用程序

1. 建立应用程序 - 依赖项

在建立新应用程序时,你能够执行一个Maven命令,该命令使用quarkus-maven-plugin。依赖项应该在参数-Dextensions中声明。web

mvn io.quarkus:quarkus-maven-plugin:0.21.1:create \
    -DprojectGroupId=pl.piomin.services \
    -DprojectArtifactId=employee-service \
    -DclassName="pl.piomin.services.employee.controller.EmployeeController" \
    -Dpath="/employees" \
    -Dextensions="resteasy-jackson, hibernate-validator"复制代码

下面是咱们pom.xml的结构:json

<properties>
    <quarkus.version>0.21.1</quarkus.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-bom</artifactId>
            <version>${quarkus.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<build>
    <plugins>
        <plugin>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-maven-plugin</artifactId>
            <version>${quarkus.version}</version>
            <executions>
                <execution>
                    <goals>
                        <goal>build</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>复制代码

对于使用输入验证构建简单的REST应用程序,咱们不须要太多模块。您可能已经注意到,我只声明了两个扩展,这与下面pom.xml中的依赖项列表相同:api

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-hibernate-validator</artifactId>
</dependency>复制代码

2. 建立应用程序 - 代码

对于Spring Boot或Micronaut用户来讲,可能有点奇怪的是,没有使用静态代码main方法的主运行类。resource/controller类实际上就是主类。Quarkus的resource/controller类和方法应该使用javax.ws.rs库中的注解进行标记。服务器

下面是employee-service的REST controller 的实现:架构

@Path("/employees")
@Produces(MediaType.APPLICATION_JSON)
public class EmployeeController {

    private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeController.class);

    @Inject
    EmployeeRepository repository;

    @POST
    public Employee add(@Valid Employee employee) {
        LOGGER.info("Employee add: {}", employee);
        return repository.add(employee);
    }

    @Path("/{id}")
    @GET
    public Employee findById(@PathParam("id") Long id) {
        LOGGER.info("Employee find: id={}", id);
        return repository.findById(id);
    }

    @GET
    public Set<Employee> findAll() {
        LOGGER.info("Employee find");
        return repository.findAll();
    }

    @Path("/department/{departmentId}")
    @GET
    public Set<Employee> findByDepartment(@PathParam("departmentId") Long departmentId) {
        LOGGER.info("Employee find: departmentId={}", departmentId);
        return repository.findByDepartment(departmentId);
    }

    @Path("/organization/{organizationId}")
    @GET
    public Set<Employee> findByOrganization(@PathParam("organizationId") Long organizationId) {
        LOGGER.info("Employee find: organizationId={}", organizationId);
        return repository.findByOrganization(organizationId);
    }

}复制代码

咱们使用CDI进行依赖注入,使用SLF4J进行日志记录。 Controller类使用内存存储库bean存储和检索数据。Repository bean使用CDI @ApplicationScoped注解,并注入controller:app

@ApplicationScoped
public class EmployeeRepository {

    private Set<Employee> employees = new HashSet<>();

    public EmployeeRepository() {
        add(new Employee(1L, 1L, "John Smith", 30, "Developer"));
        add(new Employee(1L, 1L, "Paul Walker", 40, "Architect"));
    }

    public Employee add(Employee employee) {
        employee.setId((long) (employees.size()+1));
        employees.add(employee);
        return employee;
    }

    public Employee findById(Long id) {
        Optional<Employee> employee = employees.stream().filter(a -> a.getId().equals(id)).findFirst();
        if (employee.isPresent())
            return employee.get();
        else
            return null;
    }

    public Set<Employee> findAll() {
        return employees;
    }

    public Set<Employee> findByDepartment(Long departmentId) {
        return employees.stream().filter(a -> a.getDepartmentId().equals(departmentId)).collect(Collectors.toSet());
    }

    public Set<Employee> findByOrganization(Long organizationId) {
        return employees.stream().filter(a -> a.getOrganizationId().equals(organizationId)).collect(Collectors.toSet());
    }

}复制代码

最后一个组件是带验证的实体类:框架

public class Employee {

    private Long id;
    @NotNull
    private Long organizationId;
    @NotNull
    private Long departmentId;
    @NotBlank
    private String name;
    @Min(1)
    @Max(100)
    private int age;
    @NotBlank
    private String position;

    // ... GETTERS AND SETTERS

}复制代码

3. 单元测试

对于大多数流行的Java框架,使用Quarkus进行单元测试很是简单。若是您正在测试基于REST的web应用程序,您应该在pom.xml中包含如下依赖项:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>复制代码

让咱们分析一下来自organization-service(咱们的另外一个微服务,以及employee-service和department-service)的测试类。测试类应该用@QuarkusTest注释。咱们能够经过@Inject注解注入其余bean。其他部分是典型的JUnit和RestAssured—咱们正在测试controller公开的API方法。由于咱们使用内存存储库,因此除了服务间通讯以外,咱们不须要模拟任何东西(咱们将在本文后面讨论)。对于GET、POST方法,咱们有一些积极的场景,还有一个不经过输入验证的消极场景(testInvalidAdd)。

@QuarkusTest
public class OrganizationControllerTests {

    @Inject
    OrganizationRepository repository;

    @Test
    public void testFindAll() {
        given().when().get("/organizations").then().statusCode(200).body(notNullValue());
    }

    @Test
    public void testFindById() {
        Organization organization = new Organization("Test3", "Address3");
        organization = repository.add(organization);
        given().when().get("/organizations/{id}", organization.getId()).then().statusCode(200)
                .body("id", equalTo(organization.getId().intValue()))
                .body("name", equalTo(organization.getName()));
    }

    @Test
    public void testFindByIdWithDepartments() {
        given().when().get("/organizations/{id}/with-departments", 1L).then().statusCode(200)
                .body(notNullValue())
                .body("departments.size()", is(1));
    }

    @Test
    public void testAdd() {
        Organization organization = new Organization("Test5", "Address5");
        given().contentType("application/json").body(organization)
                .when().post("/organizations").then().statusCode(200)
                .body("id", notNullValue())
                .body("name", equalTo(organization.getName()));
    }

    @Test
    public void testInvalidAdd() {
        Organization organization = new Organization();
        given().contentType("application/json").body(organization).when().post("/organizations").then().statusCode(400);
    }

}复制代码

4. 服务间通讯

因为Quarkus的目标是在Kubernetes上运行,所以它不提供任何对第三方服务发现(例如经过Consul 或Netflix Eureka)和与此发现集成的HTTP客户机的内置支持。然而,Quarkus为REST通讯提供了专用的客户端支持。要使用它,咱们首先须要包括如下依赖性:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-client</artifactId>
</dependency>复制代码

Quarkus基于MicroProfile REST客户机提供声明性REST客户机。您须要建立一个带有所需方法的接口,并使用@RegisterRestClient对其进行注解。其余注解与服务器端很是类似。由于您使用@RegisterRestClient来标记Quarkus,因此应该知道这个接口做为REST客户机可用于CDI注入。

@Path("/departments")
@RegisterRestClient
public interface DepartmentClient {

    @GET
    @Path("/organization/{organizationId}")
    @Produces(MediaType.APPLICATION_JSON)
    List<Department> findByOrganization(@PathParam("organizationId") Long organizationId);

    @GET
    @Path("/organization/{organizationId}/with-employees")
    @Produces(MediaType.APPLICATION_JSON)
    List<Department> findByOrganizationWithEmployees(@PathParam("organizationId") Long organizationId);

}复制代码

如今,让咱们看一下organization-service内的controller类。与@Inject一块儿,咱们须要使用@RestClient注解来正确地注入REST客户机bean。以后,您可使用接口方法来调用其余公开的服务

@Path("/organizations")
@Produces(MediaType.APPLICATION_JSON)
public class OrganizationController {

    private static final Logger LOGGER = LoggerFactory.getLogger(OrganizationController.class);

    @Inject
    OrganizationRepository repository;
    @Inject
    @RestClient
    DepartmentClient departmentClient;
    @Inject
    @RestClient
    EmployeeClient employeeClient;

    // ... OTHER FIND METHODS

    @Path("/{id}/with-departments")
    @GET
    public Organization findByIdWithDepartments(@PathParam("id") Long id) {
        LOGGER.info("Organization find: id={}", id);
        Organization organization = repository.findById(id);
        organization.setDepartments(departmentClient.findByOrganization(organization.getId()));
        return organization;
    }

    @Path("/{id}/with-departments-and-employees")
    @GET
    public Organization findByIdWithDepartmentsAndEmployees(@PathParam("id") Long id) {
        LOGGER.info("Organization find: id={}", id);
        Organization organization = repository.findById(id);
        organization.setDepartments(departmentClient.findByOrganizationWithEmployees(organization.getId()));
        return organization;
    }

    @Path("/{id}/with-employees")
    @GET
    public Organization findByIdWithEmployees(@PathParam("id") Long id) {
        LOGGER.info("Organization find: id={}", id);
        Organization organization = repository.findById(id);
        organization.setEmployees(employeeClient.findByOrganization(organization.getId()));
        return organization;
    }

}复制代码

通讯中缺乏的最后一个东西是目标服务的地址。咱们可使用@RegisterRestClient注解的字段baseUri 来提供它们。然而,更好的解决方案彷佛是将它们放在application.properties中。属性名须要包含客户端接口的彻底限定名和后缀mp-rest/url

pl.piomin.services.organization.client.DepartmentClient/mp-rest/url=http://localhost:8090
pl.piomin.services.organization.client.EmployeeClient/mp-rest/url=http://localhost:8080复制代码

在前一节中,我已经提到了单元测试和服务间通讯。要测试与其余应用程序通讯的API方法,咱们须要模拟REST客户机。下面是为模拟示例建立了DepartmentClient。它应该只在测试期间可见,因此咱们必须将它放在src/test/java中。若是咱们用@Mock@RestClient注释它,那么默认状况下将自动使用这个bean,而不是在src/main/java中定义的声明性REST客户机。

@Mock
@ApplicationScoped
@RestClient
public class MockDepartmentClient implements DepartmentClient {

    @Override
    public List<Department> findByOrganization(Long organizationId) {
        return Collections.singletonList(new Department("Test1"));
    }

    @Override
    public List<Department> findByOrganizationWithEmployees(Long organizationId) {
        return null;
    }

}复制代码

5. 监测和记录

咱们能够轻松地使用Quarkus公开健康检查或API文档。API文档是使用OpenAPI/Swagger构建的。Quarkus利用了 SmallRye项目中可用的库。咱们应该在pom.xml中包含如下依赖项:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-health</artifactId>
</dependency>复制代码

咱们能够定义两种类型的健康检查:readiness 和liveness。有/health/ready/health/live上下文路径。要将它们公开到应用程序以外,咱们须要定义一个实现MicroProfile HealthCheck 接口的bean。Readiness 端应该用@Readiness标注,而liveness 端应该用@Liveness标注。

@ApplicationScoped
@Readiness
public class ReadinessHealthcheck implements HealthCheck {

    @Override
    public HealthCheckResponse call() {
        return HealthCheckResponse.named("Employee Health Check").up().build();
    }

}复制代码

为了启用Swagger文档,咱们只须要添加一个依赖项便可。Quarkus还为Swagger提供了内置UI。默认状况下,它是在开发模式下启用的,因此若是您愿意在生产环境中使用它,您应该添加quarkus.swagger-ui.always-include=true到您的application.properties文件。如今,若是经过执行Maven命令mvn compile quarkus:dev在本地以开发模式运行应用程序employee-service,您能够在URLhttp://localhost:8080/swagger-ui下查看可用的API规范。

quarkus-swagger

这是我从应用程序启动时的日志。它打印监听端口和加载的扩展列表。

quarkus-startup

6. 在本地机器上运行微服务

由于咱们但愿在同一台机器上运行多个应用程序,因此须要覆盖它们的默认HTTP监听端口。虽然employee-service仍然在默认的8080 端口上运行,可是其余微服务使用不一样的端口,以下所示。

department-service:quarkus-port-department

organization-service:quarkus-port-organization

让咱们测试一下Swagger UI中的服务间通讯。我调用了GET /organizations/{id}/with-departments,它调用由department-service公开的端点GET GET /departments/organization/{organizationId}。结果以下图所示。

quarkus-communication

7. 在OpenShift上运行微服务

咱们已经完成了示例微服务体系结构的实现,并在本地机器上运行它们。如今,咱们能够进行最后一步,并尝试在 Minishift上部署这些应用程序。在OpenShift上部署Quarkus应用程序时,咱们有一些不一样的方法。今天,我将向您展现如何利用S2I为此构建的机制。

咱们将使用Quarkus GraalVM Native S2I Builder。能够在 quai.io的 quarkus/ubi-quarkus-native-s2i找到。固然,在部署应用程序以前,咱们须要先启动Minishift。根据Quarkus的文档,基于GraalVM的本机构建占用了大量内存和CPU,因此我决定为Minishift设置6GB和4个内核。

$ minishift start --vm-driver=virtualbox --memor复制代码

此外,咱们还须要稍微修改一下应用程序的源代码。您可能还记得,咱们使用JDK 11在本地运行它们。Quarkus S2I builder只支持JDK 8,因此咱们须要在pom.xml中更改它。咱们还须要包括一个声明的本机配置文件以下:

<properties>
    <quarkus.version>0.21.1</quarkus.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
...
<profiles>
    <profile>
        <id>native</id>
        <activation>
            <property>
                <name>native</name>
            </property>
        </activation>
        <build>
            <plugins>
                <plugin>
                    <groupId>io.quarkus</groupId>
                    <artifactId>quarkus-maven-plugin</artifactId>
                    <version>${quarkus.version}</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>native-image</goal>
                            </goals>
                            <configuration>
                                <enableHttpUrlHandler>true</enableHttpUrlHandler>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <artifactId>maven-failsafe-plugin</artifactId>
                    <version>2.22.1</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>integration-test</goal>
                                <goal>verify</goal>
                            </goals>
                            <configuration>
                                <systemProperties>
                                    <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
                                </systemProperties>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>复制代码

另外在application.properties文件须要修改两处。咱们不须要覆盖端口号,由于 Minishift动态地为每一个pod分配虚拟IP。服务间的通讯是经过OpenShift发现实现的,因此咱们只须要设置服务的名称而不是localhost。

quarkus.swagger-ui.always-include=true
pl.piomin.services.organization.client.DepartmentClient/mp-rest/url=http://department:8080
pl.piomin.services.organization.client.EmployeeClient/mp-rest/url=http://employee:8080复制代码

最后,咱们能够将咱们的应用程序部署到Minishift上。为此,你应使用oc客户端执行如下命令:

$ oc new-app quay.io/quarkus/ubi-quarkus-native-s2i:19.1.1~https://github.com/piomin/sample-quarkus-microservices.git#openshift --context-dir=employee --name=employee
$ oc new-app quay.io/quarkus/ubi-quarkus-native-s2i:19.1.1~https://github.com/piomin/sample-quarkus-microservices.git#openshift --context-dir=department --name=department
$ oc new-app quay.io/quarkus/ubi-quarkus-native-s2i:19.1.1~https://github.com/piomin/sample-quarkus-microservices.git#openshift --context-dir=organization --name=organization复制代码

正如您所看到的,能够在个人GitHub账户上找到找到程序源代码,地址是https://github.com/piomin/sample-quarkus-microservices.git。在Minishift 上运行的版本已经在分支openshift中共享。在本地机器上运行的版本在主分支上可用。由于全部的应用程序都存储在一个库中,因此咱们须要为每一个部署定义一个参数context-dir

我很失望。虽然为minishift 设置更多的内存和CPU花费了我很长的时间——大约25分钟。

quarkus-builds

然而,通过长时间的等待,个人全部应用程序终于都部署好了。

quarkus-openshift-overview

我经过执行下面可见的命令将它们公开在Minishift 外。可使用DNS http://${APP_NAME}-myproject.192.168.99.100.nip.io下的OpenShift路由测试它们。

$ oc expose svc employee
$ oc expose svc department
$ oc expose svc organization复制代码

此外,您还能够在OpenShift上启用readiness 和liveness 健康检查,由于它们在默认状况下是禁用的。

quarkus-health

9月福利,关注公众号​后台回复:004,领取8月翻译集锦!​往期福利回复:001,002, 003便可领取!

img

相关文章
相关标签/搜索