iT邦幫忙

2025 iThome 鐵人賽

DAY 2
0
Software Development

事件驅動電力交易平台:Spring Boot 實戰系列 第 2

Day 2|從 API 開始設計:OpenAPI 驅動式開發與契約生成實作

  • 分享至 

  • xImage
  •  

API 是模組溝通的基礎,因此我採用 API-first 的開發流程,也就是先設計好完整的 OpenAPI 文件,再透過工具生成程式碼與測試契約。這樣不僅讓不同服務有清楚的溝通界面,也能提前暴露潛在的錯誤與設計不一致問題。

我使用 Stoplight Studio 撰寫 openapi.yaml,定義包含 掛單請求、查詢歷史訂單、撮合結果事件 等 REST API 與資料格式,並使用 OpenAPI Generator 工具產出:

  • Controller interface(Spring Web 用)
  • Request/Response DTO
  • Spring Cloud Contract 測試 stub

以下我掛上買單的api為例子,詳細如何操作stoplight等工具就不贅述,網路上有許多詳細教學這裡為了說明未來如何跟spring-contract整合先做簡單介紹。
在操作後會產生下面的bidService.yaml:

/bid/buy:
 post:
    summary: '掛買單'
    operationId: post-bid-add
    description: 掛單功能 - 買入訂單
    parameters:
      - $ref: '#/components/parameters/ID_TOKEN'
      - $ref: '#/components/parameters/txnSEq'
    requestBody:
      $ref: '#/components/requestBodies/PlaceBuyOrderReq'
    responses:
      '200':
        description: 掛單成功
      '400':
        description: 請求錯誤

可以看到在parameters還有requestBody可以使用$ref來去與其他物件作關聯,這點很重要這樣在使用opeanapi-generator時可以幫你自動生成相關dto來避免在java中新增class時型別錯誤等等問題,也可以幫助在後端程式中實踐與前端溝通的內容是符合api規範的。那相關dto在yml檔中會像這樣:

PlaceBuyOrderReq:
  content:
    application/json:
      schema:
        type: object
        properties:
          bidPrice:
            type: integer
            description: 買入價格
          amount:
            type: integer
            description: 數量
          bidder:
            type: string
            format: uuid
            description: 買方 UUID
        required:
          - bidPrice
          - amount
          - bidder

當然各項屬性型別等等都可以在工具上依業務邏輯需求做調整。
透過 openapi-generator-cli 可以快速生成符合規範的程式碼:

openapi-generator-cli generate \
  -i bidService.yaml \
  -g spring \
  -o generated/order

生成的 interface 會包含如 postBidBuyOrder() 等方法,讓我專注在實作業務邏輯。那生成的範例如下:

public interface OrderServiceApi {
    @Operation(
        operationId = "post-bid-add",
        summary = "掛買單"
    )
    @PostMapping(
        value = "/bid/buy",
        consumes = "application/json"
    )
    ResponseEntity<Void> createBuyOrder(
        @RequestHeader(value = "ID_TOKEN", required = false) String idToken,
        @RequestHeader(value = "txnSEq", required = false) String txnSEq,
        @RequestBody @Valid PlaceBuyOrderReq placeBuyOrderReq
    );
}

public class PlaceBuyOrderReq {

    @NotNull
    private Integer bidPrice;

    @NotNull
    private Integer amount;

    @NotNull
    @Pattern(regexp = "^[0-9a-fA-F-]{36}$")
    private String bidder;
}

不過在實作過程中也遇到一些問題,例如:

  • 產生的 DTO 命名不直觀、會重複
  • 無法正確處理 Enum 或複雜泛型
  • Spring 的 @Valid 需要自行補上

這些都需要後續人工微調或在模組間共用 DTO 時注意相容性,我目前在使用openapigenerator時會將它生成的dto做為參考在手動調整一些不符合預期的內容。OpenAPI Generator 最大的優點在於能配合 Spring Cloud Contract,讓我將定義的 API 契約直接當作測試依據,用來生成測試用的相關cloud-contract.yml有所依據,能夠確保 wallet-service、order-service 在版本更新後仍然對得上,不會出現接口破壞的問題。


上一篇
IThome 鐵人賽 Day 1|打造電力交易平台:系統設計與技術藍圖
下一篇
Day 3|事件驅動實作:從 Order Service 發送 RabbitMQ 訂單事件
系列文
事件驅動電力交易平台:Spring Boot 實戰9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言