今天來談如何使用Quarkus,其實這系列原本是因為要接公司接下來的專案而寫,本來這系列是想寫給跟相關案子的人可以快速上手這Quarkus框架,不過遇到一些公司湯圓
(專案可能後續有機會不用維護),所以對於Quarkus的興趣就興致缺缺了(感覺還是java spring flux大宗?)。
我心態這樣,文章不會得名阿!?? 這次參加也不是拿名次為主,就真的只是為了案子….可惜。不過也因此重新過了許多重要觀念,是真的很認真寫每一個章節,算因獲得福??????
不過還是針對以前的經驗稍微過一下我覺得比較重要的部分,我認為上手框架在Application面有下列項目要理解
以上為熟悉框架常見須了解的項目,其餘的話則就是針對框架特性,如前些章節提到的Native Image與適合響應寫法這件事情,這邊就不在多說了。接著我們針對每個項目快速過一遍(個人覺得Quarkus官方文件不太友善…),基本上這邏輯在大都框架都適用。
Quarkus 中如何撰寫 REST API,基本上Quarkus在這部分是使用RESTEasy API去實作。使用 JAX-RS 註解
(由 RESTEasy 支援)來定義路由、請求方法、輸出輸入格式等。
JAX-RS 註解就是讓開發者可以用簡單的標註方式(Attribute),直接在 Java 程式碼中定義 RESTful API 的各種設定,包括路由、HTTP 方法、參數處理等,符合 REST 規範和 HTTP 協定,不用寫一堆繁瑣的設定檔,整個開發過程變得更有效率。
@Path
註解定義路徑@Path
是用來指定 API 的路徑,也就是客戶端發送 HTTP 請求時訪問的 URL。例如,我們要提供一個 /notifications
的 API:
@Path("/notifications")
public class NotificationResource {
// 這個類所有的路由都會以 /notifications 開頭
}
如果我們有一個 /notifications/{id}
的 API,則可以使用路徑參數:
@GET
@Path("/{id}")
public Notification getNotification(@PathParam("id") Long id) {
// 根據 ID 取得推播訊息
return notificationService.findById(id);
}
@Path("/{id}")
:{}
用來代表路徑中的參數部分。@PathParam("id")
:提取 URL 中的 id
值,並作為參數傳入方法。@GET
、@POST
、@PUT
、@DELETE
註解定義 HTTP 方法@GET
:用來處理查詢(GET)請求,取得資料。@POST
:用來處理新增(POST)請求,新增資料。@PUT
:用來處理更新(PUT)請求,更新資料。@DELETE
:用來處理刪除(DELETE)請求,刪除資料。例如,
@GET
public List<Notification> listNotifications() {
return notificationService.getAll();
}
@POST
public Response createNotification(Notification notification) {
notificationService.add(notification);
return Response.status(Response.Status.CREATED).build();
}
@PUT
@Path("/{id}")
public Response updateNotification(@PathParam("id") Long id, Notification notification) {
notificationService.update(id, notification);
return Response.ok().build();
}
@DELETE
@Path("/{id}")
public Response deleteNotification(@PathParam("id") Long id) {
notificationService.delete(id);
return Response.noContent().build();
}
@GET
:用來查詢推播訊息,回傳 List<Notification>
。@POST
:新增推播訊息,並回傳 201 Created
。@PUT
:更新某個指定的推播訊息(根據 ID)。@DELETE
:刪除某個指定的推播訊息(根據 ID)。@Produces
和 @Consumes
註解定義資料格式用來指定 API 返回的資料格式 和 API 接收的資料格式。常用的格式有:
MediaType.APPLICATION_JSON
:JSON 格式,最常見的 Web API 格式。MediaType.TEXT_PLAIN
:純文本格式。例如
@GET
@Produces(MediaType.APPLICATION_JSON) // 回傳 JSON 格式
public List<Notification> listNotifications() {
return notificationService.getAll();
}
@POST
@Consumes(MediaType.APPLICATION_JSON) // 接收 JSON 格式
@Produces(MediaType.APPLICATION_JSON) // 回傳 JSON 格式
public Response createNotification(Notification notification) {
notificationService.add(notification);
return Response.status(Response.Status.CREATED).entity(notification).build();
}
@Produces(MediaType.APPLICATION_JSON)
:指定 API 的回應是 JSON 格式。@Consumes(MediaType.APPLICATION_JSON)
:指定 API 接收的是 JSON 格式。@QueryParam
、@HeaderParam
、@PathParam
提取參數@PathParam
:用來提取 URL 路徑中的參數,如 /notifications/{id}
。@QueryParam
:用來提取 URL 中的查詢參數,如 /notifications?type=new
。@HeaderParam
:用來提取 HTTP 標頭中的參數。例如
@GET
@Path("/filter")
public List<Notification> getNotificationsByType(@QueryParam("type") String type) {
return notificationService.findByType(type);
}
@GET
@Path("/{id}")
public Notification getNotificationById(@PathParam("id") Long id) {
return notificationService.findById(id);
}
@POST
@Path("/secure")
public Response createSecureNotification(@HeaderParam("Authorization") String token, Notification notification) {
if (authService.validateToken(token)) {
notificationService.add(notification);
return Response.status(Response.Status.CREATED).build();
} else {
return Response.status(Response.Status.UNAUTHORIZED).build();
}
}
@QueryParam("type")
:提取查詢參數,如 /filter?type=new
。@HeaderParam("Authorization")
:提取 HTTP 標頭中的 Authorization
欄位,用來進行身分驗證。@DefaultValue
設定預設值當查詢參數或路徑參數沒有提供時,可以使用 @DefaultValue
註解來提供預設值。
例如
@GET
@Path("/list")
public List<Notification> listNotifications(@QueryParam("page") @DefaultValue("1") int page,
@QueryParam("size") @DefaultValue("10") int size) {
return notificationService.getNotifications(page, size);
}
這裡 page
和 size
參數在未提供時會自動設為 1
和 10
。
Google建議作法
):在 URL 路徑中添加版本號,如 /v1
和 /v2
。這樣不同的客戶端可以根據版本號訪問不同的 API。範例如下// API 版本 1
@Path("/v1/notifications")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class NotificationResourceV1 {
@GET
public List<Notification> getAllNotificationsV1() {
// 這裡返回的可能是舊版的資料格式
return notificationService.getAllNotificationsV1();
}
}
// API 版本 2
@Path("/v2/notifications")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class NotificationResourceV2 {
@GET
public List<Notification> getAllNotificationsV2() {
// 這裡可能返回更豐富的資料或不同的格式
return notificationService.getAllNotificationsV2();
}
@POST
public Response createNotificationV2(Notification notification) {
notificationService.createNotificationV2(notification);
return Response.status(Response.Status.CREATED).build();
}
}
@Path("/notifications")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class NotificationResource {
@GET
public Response getAllNotifications(@HeaderParam("X-API-Version") String apiVersion) {
if ("v1".equals(apiVersion)) {
return Response.ok(notificationService.getAllNotificationsV1()).build();
} else if ("v2".equals(apiVersion)) {
return Response.ok(notificationService.getAllNotificationsV2()).build();
} else {
return Response.status(Response.Status.BAD_REQUEST)
.entity("Unsupported API version: " + apiVersion).build();
}
}
}
另外需注意的是,RESTEasy 不只支持傳統的阻塞式 REST API,支持響應式(Reactive) API 實作。套件選用上會有些不同
這部分通常使用 Hibernate ORM with Panache 來簡化與資料庫的連線互動。大致的步驟為
在 build.gradle
文件中添加相應的依賴來使用 Quarkus 與 PostgreSQL 資料庫。
dependencies {
// Quarkus Hibernate ORM with Panache - 用來簡化 JPA 操作
implementation 'io.quarkus:quarkus-hibernate-orm-panache'
// PostgreSQL JDBC Driver - 連接 PostgreSQL 資料庫
implementation 'io.quarkus:quarkus-jdbc-postgresql'
// 其他 Quarkus 套件...
}
使用 application.properties
文件來進行資料庫連線的配置。
# 資料庫類型
quarkus.datasource.db-kind=postgresql
# 資料庫連線資訊
quarkus.datasource.username=your_db_username
quarkus.datasource.password=your_db_password
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/your_db_name
# 啟用 Hibernate 的 DDL 自動生成功能 (none, create, update, drop-and-create)
quarkus.hibernate-orm.database.generation=update
# PostgreSQL 的 SSL 加密連線
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/your_db_name?ssl=true&sslmode=require
ssl=true
:啟用 SSL 。sslmode=require
:要求所有連線都必須使用 SSL,確保資料傳輸的安全性。創建一個對應到資料庫表的 實體類(Entity),以便進行 CRUD 操作。Quarkus 提供了 Panache,能夠簡化 JPA 操作。假設我們有一個 Notification
表,用來存放推播訊息,我們可以創建對應的實體類:
import javax.persistence.Entity;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
@Entity
public class Notification extends PanacheEntity {
public String title;
public String message;
public String timestamp;
// PanacheEntity 已經自動提供了 ID 欄位及常用的 CRUD 方法
}
@Entity
:標示這是一個 JPA 實體類,會對應到資料庫中的Table。PanacheEntity
:繼承自 Quarkus 提供的 Panache 類,內建了 id
屬性(自動生成),並提供常見的 CRUD 方法(如 persist()
和 delete()
)。不需要單獨創建 Repository 類,就能直接進行 CRUD 操作。範例如下
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;
@Path("/notifications")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class NotificationResource {
// 查詢所有推播訊息
@GET
public List<Notification> listAll() {
return Notification.listAll(); // Panache 提供的 listAll 方法
}
// 查詢單一推播訊息
@GET
@Path("/{id}")
public Notification getNotification(@PathParam("id") Long id) {
return Notification.findById(id); // 根據 ID 查詢
}
// 新增推播訊息
@POST
public Notification createNotification(Notification notification) {
notification.persist(); // 新增資料到資料庫
return notification;
}
// 更新推播訊息
@PUT
@Path("/{id}")
public Notification updateNotification(@PathParam("id") Long id, Notification notification) {
Notification existingNotification = Notification.findById(id);
if (existingNotification == null) {
throw new WebApplicationException("Notification with ID " + id + " does not exist.", 404);
}
existingNotification.title = notification.title;
existingNotification.message = notification.message;
existingNotification.timestamp = notification.timestamp;
return existingNotification;
}
// 刪除推播訊息
@DELETE
@Path("/{id}")
public void deleteNotification(@PathParam("id") Long id) {
Notification.deleteById(id); // 根據 ID 刪除
}
}
listAll()
:查詢所有資料。findById(id)
:根據 ID 查詢資料。persist()
:插入或更新資料。deleteById(id)
:根據 ID 刪除資料。我們使用 Notification.listAll()
簡單地查詢所有推播訊息。使用 persist()
方法將新的推播訊息存入資料庫。使用 deleteById()
方法刪除推播訊息,整體 CRUD 操作變得非常簡單直觀。
雖然不用實作Repository,不過大多狀態下一般會去設計Repository介面來抽換Data Source。
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import javax.enterprise.context.ApplicationScoped;
import java.util.List;
@ApplicationScoped
public class NotificationRepository implements PanacheRepository<Notification> {
// PanacheRepository 提供了基本的 CRUD 方法,也可以自定義查詢
public List<Notification> findByTitle(String title) {
return list("title", title);
}
}
在 NotificationResource
中,使用 依賴注入(DI) 來注入 NotificationRepository
。這樣即使將來更換資料來源,也只需要修改 Repository 的實作,而不影響 API 層。
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;
@Path("/notifications")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class NotificationResource {
@Inject
NotificationRepository notificationRepository; // 注入 Repository
@GET
public List<Notification> listAll() {
return notificationRepository.listAll(); // 使用 Repository 來查詢資料
}
@GET
@Path("/{id}")
public Notification getNotification(@PathParam("id") Long id) {
return notificationRepository.findById(id); // 根據 ID 查詢
}
@POST
public Notification createNotification(Notification notification) {
notificationRepository.persist(notification); // 新增推播訊息
return notification;
}
@DELETE
@Path("/{id}")
public void deleteNotification(@PathParam("id") Long id) {
notificationRepository.deleteById(id); // 刪除推播訊息
}
}
這樣設計的好處是,如果將來需要更換資料來源,只需實作新的 NotificationRepository
,並使用不同的資料存取方式即可,對上層 API 邏輯沒有影響。
在實際部署中,將資料庫帳號密碼寫死在 application.properties
是不安全的。通常會透過環境變數來動態配置這些敏感資訊。
可以將資料庫的連線資訊(如帳號、密碼等)透過環境變數傳入,而不是直接寫在 application.properties
中。例如:
# 透過環境變數來讀取資料庫的帳號、密碼
quarkus.datasource.username=${DB_USERNAME:default_username}
quarkus.datasource.password=${DB_PASSWORD:default_password}
# 其他連線設定
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/your_db_name
quarkus.datasource.db-kind=postgresql
quarkus.hibernate-orm.database.generation=update
DB_USERNAME
和 DB_PASSWORD
:環境變數。如果環境變數沒有設置,會使用 default_username
和 default_password
作為預設值。quarkus.datasource.username
和 quarkus.datasource.password
:可以直接從環境變數中取得資料庫帳號和密碼,這樣在不同的環境下,可以動態改變。一般在K8環境會使用ConfigMap 搭配 Secret來做變數注入,而GCP上也是如此,通常實際帳號密碼會放在Secret,再由ConfigMap去吃不同環境Yaml去注入。
明天繼續!
框架上手補充(包含語言特性)