swagger-decorator:註解方式為 Koa2 應用動態生成 Swagger 文檔

swagger-decorator:註解方式為 Koa2 應用自動生成 Swagger 文檔 從屬於筆者的服務端應用程序開發與系統架構,記述了如何在以 Koa2 與 koa-router 開發服務端應用時,通過自定義 swagger-decorator 庫來實現類 Spring-Boot 中註解方式動態生成 Swagger 標準的介面文檔。

swagger-decorator:註解方式為 Koa2 應用動態生成 Swagger 文檔

目前我司服務端應用程序框架主要採用了 Java Spring 與 Node.js,而因為今年有很多的調研階段的產品線 Demo 發布,持續部署、介面文檔以及線上質量監控這三個問題愈發突出。本文則主要針對介面文檔的實時發布進行一些探討;在前後端分離的今天,即使是由單人縱向負責某個業務流,也需要將前後端交互的介面規範清晰地定義並且發布,以保證項目的透明性與可維護性。理想的開發流程中,應當在產品設計階段確定好關鍵欄位命名、資料庫表設計以及介面文檔;不過實際操作中往往因為業務的多變性以及人手的缺失,使得介面的定義並不能總是實時地在項目成員之間達成一致。如果要讓開發人員在更改介面的同時花費額外精力維護一份開發文檔,可能對於我司這樣的小公司而言存在著很大的代價與風險。軟體開發中存在著所謂 Single Source of Truth 的原則,我們也需要盡量避免文檔與實際實現的不一致造成的團隊內矛盾以及無用的付出。綜上所述,我們希望能夠在編寫後台代碼、添加註釋的同時,能夠自動地生成介面文檔;筆者比較熟悉 Spring 中以註解方式添加 Swagger 文檔的模式,不過 Java 庫的抽象程度一般較高,用起來也不怎麼順手。筆者在編寫我司 node-server-boilerplate 根據自己的想法設計了 swagger-decorator。此外,項目中使用 Flow 進行靜態類型檢測,並且遵循我司內部的 JavaScript 編程樣式指南。

我們可以使用 npm 或者 yarn 安裝 swagger-decorator,需要注意的是,因為使用了註解,因此建議是配置 Webpack 與 Babel,不熟悉的同學可以直接參考 node-server-boilerplate :

$ yarn add swagger-decorator# 依賴於 Babel 的 transform-decorators-legacy 轉換插件來使用 Decorator$ yarn add transform-decorators-legacy -D

安裝完畢之後,我們需要對項目中使用的路由進行封裝。目前筆者只是針對 koa-router 中的路由對象進行封裝,未來若有必要可以針對其他框架的路由解決方案進行封裝。我們首先需要做的就是在路由定義之前使用 wrappingKoaRouter 函數修飾 router 對象:

import { wrappingKoaRouter } from "swagger-decorator";...const Router = require("koa-router");const router = new Router();wrappingKoaRouter(router, "localhost:8080", "/api", { title: "Node Server Boilerplate", version: "0.0.1", description: "Koa2, koa-router,Webpack"});//定義默認的根路由router.get("/", async function(ctx, next) { ctx.body = { msg: "Node Server Boilerplate" };});//定義用戶處理路由router.scan(UserController);

該函數的參數說明如下,對於 info 的結構參考這裡:

/** * Description 將 router 對象的方法進行封裝 * @param router 路由對象 * @param host API 域名 * @param basePath API 基本路徑 * @param info 其他的 Swagger 基本信息 */export function wrappingKoaRouter( router: Object, host: string = "localhost", basePath: string = "", info: Object = {}) {}

值得一提的是,在封裝 router 時,筆者自定義了 scan 方法,其能夠根據自動遍歷目標類中的自定義方法,有點類似於 Java 中的 ComponentScan:

/*** Description 掃描某個類中的所有靜態方法,按照其註解將其添加到* @param staticClass*/router.scan = function(staticClass: Function) { let methods = Object.getOwnPropertyNames(staticClass); // 移除前三個屬性 constructor、name methods.shift(); methods.shift(); methods.shift(); for (let method of methods) { router.all(staticClass[method]); }};

準備工作完成之後,我們即可以開始定義具體的介面控制器;筆者不喜歡過多的封裝,因此這裡選用了類的靜態方法來定義具體的介面函數,整個 Controller 也只是樸素函數。下面筆者列舉了常見的獲取全部用戶列表、根據用戶編號獲取用戶詳情、創建新用戶這幾個介面的文檔注釋方式:

import { apiDescription, apiRequestMapping, apiResponse, bodyParameter, pathParameter, queryParameter} from "swagger-decorator";import User from "../entity/User";/** * Description 用戶相關控制器 */export default class UserController { @apiRequestMapping("get", "/users") @apiDescription("get all users list") @apiResponse(200, "get users successfully", [User]) static async getUsers(ctx, next): [User] { ... } @apiRequestMapping("get", "/user/:id") @apiDescription("get user object by id, only access self or friends") @pathParameter({ name: "id", description: "user id", type: "integer" }) @queryParameter({ name: "tags", description: "user tags, for filtering users", required: false, type: "array", items: ["string"] }) @apiResponse(200, "get user successfully", User) static async getUserByID(ctx, next): User { ... } @apiRequestMapping("post", "/user") @apiDescription("create new user") @bodyParameter({ name: "user", description: "the new user object, must include user name", required: true, schema: User }) @apiResponse(200, "create new user successfully", { status_code: "200" }) static async postUser(): number { ... }}

在對介面註解的時候,我們需要用實體類指明返回值或者請求體中包含的參數信息,因此我們也需要使用 swagger-decorator 提供的 entityProperty 註解來為實體類添加描述。值得一提的是,這裡我們支持直接將 Object 作為描述對象的返回值,算是避免了 Java 中的一大痛點。

// @flowimport { entityProperty } from "swagger-decorator";/** * Description 用戶實體類 */export default class User { // 編號 @entityProperty({ type: "integer", description: "user id, auto-generated", required: false }) id: string = 0; // 姓名 @entityProperty({ type: "string", description: "user name, 3~12 characters", required: true }) name: string = "name"; // 朋友列表 friends: [number] = [1]; // 屬性 properties: { address: string } = { address: "address" };}

對於沒有添加註解的屬性,swagger-decorator 會自動根據其默認值來推測類型。然後我們就可以正常地啟動應用,swagger-decorator 已經自動地為 router 對象添加了兩個路由,其中 /swagger 指向了 Swagger UI:

而 /swagger/api.json 指向了 Swagger 生成的 JSON 文檔:

歡迎有興趣的朋友提出 ISSUE、指導意見或者希望納入的特性。

推薦閱讀:

前後端技術分離
Node.js 8 說明
從零開始寫一個 Node.js 的 MongoDB 驅動庫
nodejs + react + redux 實踐
在Egg中使用GraphQL

TAG:Nodejs | koa | JavaScript |