您好,登錄后才能下訂單哦!
Openshift中怎么利用Quarkus構建一個微服務,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
在創建新應用程序時,你可以執行一個Maven命令,該命令使用quarkus-maven-plugin
。依賴項應該在參數-Dextensions
中聲明。
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
的結構:
<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
中的依賴項列表相同:
<dependency> <groupid>io.quarkus</groupid> <artifactid>quarkus-resteasy-jackson</artifactid> </dependency> <dependency> <groupid>io.quarkus</groupid> <artifactid>quarkus-hibernate-validator</artifactid> </dependency>
對于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:
@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 }
對于大多數流行的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); } }
由于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; } }
我們可以輕松地使用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規范。
這是我從應用程序啟動時的日志。它打印監聽端口和加載的擴展列表。
因為我們希望在同一臺機器上運行多個應用程序,所以需要覆蓋它們的默認HTTP監聽端口。雖然employee-service仍然在默認的8080
端口上運行,但是其他微服務使用不同的端口,如下所示。
department-service:
organization-service:
讓我們測試一下Swagger UI中的服務間通信。我調用了GET /organizations/{id}/with-departments
,它調用由department-service公開的端點GET GET /departments/organization/{organizationId}
。結果如下圖所示。
我們已經完成了示例微服務體系結構的實現,并在本地機器上運行它們。現在,我們可以進行最后一步,并嘗試在 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分鐘。
然而,經過長時間的等待,我的所有應用程序終于都部署好了。
我通過執行下面可見的命令將它們公開在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 健康檢查,因為它們在默認情況下是禁用的。
關于Openshift中怎么利用Quarkus構建一個微服務問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。