API 是模組溝通的基礎,因此我採用 API-first 的開發流程,也就是先設計好完整的 OpenAPI 文件,再透過工具生成程式碼與測試契約。這樣不僅讓不同服務有清楚的溝通界面,也能提前暴露潛在的錯誤與設計不一致問題。
我使用 Stoplight Studio 撰寫 openapi.yaml,定義包含 掛單請求、查詢歷史訂單、撮合結果事件 等 REST API 與資料格式,並使用 OpenAPI Generator 工具產出:
以下我掛上買單的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 時注意相容性,我目前在使用openapigenerator時會將它生成的dto做為參考在手動調整一些不符合預期的內容。OpenAPI Generator 最大的優點在於能配合 Spring Cloud Contract,讓我將定義的 API 契約直接當作測試依據,用來生成測試用的相關cloud-contract.yml有所依據,能夠確保 wallet-service、order-service 在版本更新後仍然對得上,不會出現接口破壞的問題。