iT邦幫忙

2024 iThome 鐵人賽

DAY 28
0

今天來談如何使用Quarkus,其實這系列原本是因為要接公司接下來的專案而寫,本來這系列是想寫給跟相關案子的人可以快速上手這Quarkus框架,不過遇到一些公司湯圓(專案可能後續有機會不用維護),所以對於Quarkus的興趣就興致缺缺了(感覺還是java spring flux大宗?)。

我心態這樣,文章不會得名阿!?? 這次參加也不是拿名次為主,就真的只是為了案子….可惜。不過也因此重新過了許多重要觀念,是真的很認真寫每一個章節,算因獲得福??????

框架上手需學項目

不過還是針對以前的經驗稍微過一下我覺得比較重要的部分,我認為上手框架在Application面有下列項目要理解

  1. REST API撰寫 : 如何撰寫Controller層,一般針對框架或是套件會有很多Attribute上的設置。通常會涉及許多框架或套件提供的註解(Attributes)設置,用來定義HTTP請求的路由、方法和參數等。
  2. DB連線 : 如何使用框架做資料庫連線,包括如何設置SSL加密連線,確保數據傳輸的安全。
  3. 依賴注入(DI框架): 基本上現行架構都會帶DI設置,在系統設計上是很重要的環節,除了關係到架構設計的影響外,還與物件是否有效管理生命週期有關。
  4. 環境配置管理 : 根據不同環境(如SIT、UAT、Prod)注入對應的配置變數,保證應用能夠適應不同的運行環境。
  5. Middleware如何使用 : 負責應用之間的溝通協調,第一層可以實現身份驗證、安全驗證與冪等性檢查等功能。
  6. 安全性設置:包括HTTPS、CORS、XSS防護和CSRF防護,確保應用的資料傳輸安全,並防止常見的網路攻擊。
  7. Log套件使用 : 記錄應用程式的運行狀況,包含正常運作記錄和錯誤發生時的資訊,讓開發者進行調試與監控。
  8. 錯誤處理與異常管理 : 當應用程式遇到錯誤或意外情況時,錯誤處理機制會捕捉並提供有意義的錯誤訊息,而不是讓應用 Crash,這通常涉及到Error Handling的設計。
  9. AOP套件或框架使用 :將如日誌記錄、交易管理等橫切關注點與業務邏輯分離,使系統更具可維護性和靈活性。
  10. 身分認證OpenId與 SAML使用 : 透過OpenID或SAML協議進行身份認證,常用於用戶登入和安全性確認。
  11. Object-Object Mapper : 資料物件與不同格式(如 JSON)互相轉換的工具。
  12. 單元測試 : 驗證應用程式內部每個功能是否按預期邏輯處理。
  13. 容器化和部署 : 將應用打包成一個可獨立運行的服務單元,這個服務單元可以在任何支持容器的平台上運行。
  14. 任務排程 : 允許應用程式定期執行特定任務,如定期產生報告或進行資料備份。
  15. 緩存機制 : 將常用的資料存儲在記憶體中,減少資料庫的頻繁訪問,從而加速應用的響應時間。

以上為熟悉框架常見須了解的項目,其餘的話則就是針對框架特性,如前些章節提到的Native Image與適合響應寫法這件事情,這邊就不在多說了。接著我們針對每個項目快速過一遍(個人覺得Quarkus官方文件不太友善…),基本上這邏輯在大都框架都適用。

REST API撰寫

Quarkus 中如何撰寫 REST API,基本上Quarkus在這部分是使用RESTEasy API去實作。使用 JAX-RS 註解(由 RESTEasy 支援)來定義路由、請求方法、輸出輸入格式等。

  • @Path:定義 API 的路徑。
  • @GET、@POST、@PUT、@DELETE:定義 HTTP 方法。
  • @Produces、@Consumes:定義輸入和輸出的格式,如 JSON。
  • @PathParam、@QueryParam、@HeaderParam:提取路徑、查詢或標頭中的參數。
  • @DefaultValue:為查詢參數或標頭參數設置預設值。
  • @FormParam:提取表單參數。

JAX-RS 註解就是讓開發者可以用簡單的標註方式(Attribute),直接在 Java 程式碼中定義 RESTful API 的各種設定,包括路由、HTTP 方法、參數處理等,符合 REST 規範和 HTTP 協定,不用寫一堆繁瑣的設定檔,整個開發過程變得更有效率。

1. 使用 @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 值,並作為參數傳入方法。

2. 使用 @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)。

3. 使用 @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 格式。

4. 使用 @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 欄位,用來進行身分驗證。

5. 使用 @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。

6. Quarkus 中實現 API 版本控制的方式

  • URL 路徑版本控制(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();
    }
}

  • HTTP 標頭版本控制:通過 HTTP 請求的標頭來指定版本號。
@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();
        }
    }
}

  • 查詢參數版本控制:使用查詢參數來指定 API 版本號(不推薦,就不給範例了)。

另外需注意的是,RESTEasy 不只支持傳統的阻塞式 REST API,支持響應式(Reactive) API 實作。套件選用上會有些不同

  • RESTEasy Classic:傳統的阻塞式 API,適合於同步請求和響應的處理方式,這是大多數 REST API 開發中常用的形式。
  • RESTEasy Reactive:非阻塞式 API,適合高併發場景,能有效處理大量並發請求,提供更好的資源利用率。

DB連線

這部分通常使用 Hibernate ORM with Panache 來簡化與資料庫的連線互動。大致的步驟為

  1. 安裝 Quarkus 的資料庫套件
  2. 配置資料庫連線,包括基本連線設定和 SSL 加密連線。
  3. 創建資料庫實體(Entities)。
  4. 使用 Panache 進行簡化的 CRUD 操作。

1. 安裝 Quarkus 的資料庫套件

在 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 套件...
}

2. 配置資料庫連線,包括基本連線設定和 SSL 加密連線。

使用 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

SSL 加密連線設定

# PostgreSQL 的 SSL 加密連線
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/your_db_name?ssl=true&sslmode=require
  • ssl=true:啟用 SSL 。
  • sslmode=require:要求所有連線都必須使用 SSL,確保資料傳輸的安全性。

3. 創建資料庫實體(Entities)

創建一個對應到資料庫表的 實體類(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())。

4. 使用 Panache 進行簡化的 CRUD 操作

不需要單獨創建 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 操作變得非常簡單直觀。

5. 使用 Repository Interface 抽換 Data Source

雖然不用實作Repository,不過大多狀態下一般會去設計Repository介面來抽換Data Source。

建置 Repository Interface

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);
    }
}

使用 Repository Interface

在 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 邏輯沒有影響。

6. 使用環境變數來管理 DB 帳號密碼

在實際部署中,將資料庫帳號密碼寫死在 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去注入。

明天繼續!


上一篇
開發概念建置- 微服務監控設計大小事
下一篇
開發-Quarkus急迫上手-Part2
系列文
微服務奇兵:30天Quarkus特訓營 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
pal2097
iT邦新手 4 級 ‧ 2024-10-07 21:45:55

框架上手補充(包含語言特性)

  • DB ORM
  • DB Code First Package
  • grpc
  • 語言Naming Sytle
  • Project layout
  • ci lint
  • rfc7807實現方式

我要留言

立即登入留言