使用 Swagger Inflector 編寫 API

  2015 年 8 月 13 日

作者:Tony Tam

Swagger Inflector 是 Swagger 團隊為在 JVM 上編寫 API 而開發的新專案。它目前處於預覽狀態,但正在積極開發和支援,作為在 Java 中編寫 API 的另一種以設計優先的方式。讓我們逐步了解如何使用 Inflector 專案。

[embed]http://www.slideshare.net/Swagger-API/the-inflector-project[/embed]

開始使用

首先,我們從 Swagger 規格開始。您可以在您喜愛的文字編輯器中以 JSON 或 YAML 建立規格,或使用 http://editor.swagger.io 上的絕佳線上編輯器來建構您的 API 定義。不用擔心進行變更!我們將逐步說明 Inflector 如何協助您快速迭代您的設計,而無需編寫任何程式碼。

在此示範中,我們假設您已從 此處 複製 Swagger 描述。

接下來,我們建立一個 YAML 檔案來設定 Inflector。這允許許多選項,但現在,讓我們只放置 Swagger 描述的位置

# inflector.yaml

swaggerUrl: ./src/main/swagger/swagger.yaml

由於 Inflector 在底層使用 Swagger Parser,因此您可以指向本機或遠端檔案。如果您正在託管您的定義,只需指定 httphttps 協定,Swagger Parser 就會擷取它。此功能有一些非常有趣的用例,我們將在後續的部落格文章中介紹。

現在讓我們新增 Inflector 相依性。在執行階段,Inflector 只會使用 Jersey 2.6 作為 REST 框架,因此有很多用於生產整合的選項。現在,讓我們使用 Jetty Maven 外掛程式並新增 Inflector 作為相依性

    <build>

<plugins>

<plugin>

<groupId>org.eclipse.jetty</groupId>

<artifactId>jetty-maven-plugin</artifactId>

<version>9.2.9.v20150224</version>

<configuration>

<monitoredDirName>.</monitoredDirName>

<scanTargets>

<scanTarget>inflector.yaml</scanTarget>

<scanTarget>src/main/swagger/swagger.yaml</scanTarget>

</scanTargets>

<scanIntervalSeconds>1</scanIntervalSeconds>

<webApp>

<contextPath>/</contextPath>

</webApp>

<httpConnector>

<port>8080</port>

<idleTimeout>60000</idleTimeout>

</httpConnector>

</configuration>

</plugin>

<!-- 您的其他外掛程式 -->

</plugins>

</build>

<dependencies>

<dependency>

<groupId>io.swagger</groupId>

<artifactId>swagger-inflector</artifactId>

<version>1.0.0-SNAPSHOT</version>

</dependency>

<!-- 您的其他相依性 -->

查看範例 pom.xml,其中包含您需要的一切。

請注意,我們在外掛程式組態中設定了兩個掃描目標。這會告訴 Jetty,如果 ./inflector.yamlsrc/main/swagger/swagger.yaml 描述檔案變更,則重新啟動應用程式。您會看到這如何讓使用 Inflector 進行開發變得輕鬆。

最後,讓我們新增一個 web.xml。這讓我們可以在 Jersey 環境中,於服務的根目錄上掛載 Inflector 應用程式。我們還將新增 CORS 篩選器,以便我們可以輕鬆地從 Swagger UI 讀取 Swagger API。

<!-- from https://github.com/swagger-api/swagger-inflector/blob/master/samples/jetty-webxml/src/main/webapp/WEB-INF/web.xml -->

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<servlet>

<servlet-name>swagger-inflector</servlet-name>

<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>

<init-param>

<param-name>javax.ws.rs.Application</param-name>

<param-value>io.swagger.inflector.SwaggerInflector</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>swagger-inflector</servlet-name>

<url-pattern>/*</url-pattern>

</servlet-mapping>

<filter>

<filter-name>CORSFilter</filter-name>

<filter-class>io.swagger.inflector.utils.CORSFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>CORSFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

</web-app>

請注意我們如何新增 CORS 篩選器。這就像任何標準的 web.xml,您可以新增篩選器、靜態 Servlet 等。這裡沒有任何魔法!

這就是我們的專案設定。現在讓我們開始使用它。

啟動伺服器

mvn package jetty:run

並在連接埠 8080 上讀取 Swagger 清單 (在 Jetty 外掛程式中設定)。根據您在 Swagger 描述中的 basePath 中設定的內容,您現在可以在 http://localhost:8080/{basePath}/swagger.json 開啟 Swagger 描述 (當然,根據規格,可以省略 basePath)。這會以 JSON 格式顯示您一直在編輯的規格。我知道,沒什麼大不了,但我們才剛開始。

請注意,根據我們的 Jetty 外掛程式組態,您可以將變更儲存到您的 swagger.yaml 檔案,並進行自動重新載入 (每秒掃描一次)。一個重點!如果您撰寫無效的規格,伺服器將拒絕提供它。使用 Swagger Editor 來取得撰寫有效 Swagger 描述的內容相關說明!

現在是有趣的部分。讓我們從我們的描述中取出一個路由

  /pet/{petId}:

get

tags

- pet

summary: 依 ID 尋找寵物

description: 傳回單一寵物

operationId: getPetById

consumes

- application/x-www-form-urlencoded

produces

- application/xml

- application/json

parameters

- name: petId

in: path

description: 要傳回的寵物 ID

required: true

type: integer

format: int64

responses

"200":

description: 成功操作

schema

$ref: "#/definitions/Pet"

"400":

description: 提供無效的 ID

"404":

description: 找不到寵物

如果您點擊此端點 (http://localhost:8080/v2/pet/1),Inflector 將根據您的 Accepts 標頭,以 JSON 或 XML 格式提供 #/definitions/Pet 模型的範例酬載。不需要程式碼!Inflector 會從 Swagger 結構描述建立範例

{

"id": 0,

"category": {

"id": 0,

"name": "string"

},

"name": "doggie",

"photoUrls": [

"string"

],

"tags": [

{

"id": 0,

"name": "string"

}

],

"status": "string"

}

這很有趣!您可以使用 Swagger 描述中的每個操作嘗試相同的操作。Inflector 將讓模擬服務變得簡單。同樣地,當您修改服務描述並進行變更時,Jetty 外掛程式會重新載入並立即顯示您的變更。您甚至可以載入 swagger-ui 並指向我們的 Swagger 描述來查看實際情況。

現在,讓我們跳過 Hello World 並讓它更真實。假設您不想要模擬回應 - 您想要實作一個控制器並放入一些業務邏輯。Inflector 將透過將請求直接路由到控制器來簡化此操作。

這是透過幾種技術設定的。讓我們逐步了解選項。

明確命名控制器 + 套件

您可以使用 Swagger 的 Extension 在描述中加入額外的元數據。在此用例中,我們將加入要將請求發送到的控制器類別名稱

  /pet:

post

tags

- pet

summary: 將新的寵物添加到商店

operationId: addPet

# 我們正在配置 Inflector 以在此類別中尋找我們的控制器!

x-swagger-router-controller: io.swagger.sample.SampleController

現在在啟動時,Inflector 會在 io.swagger.sample.SampleController 類別中尋找符合此方法的方法。但我們的簽名是什麼???

Inflector 將使用 operationId 作為方法名稱。如果不存在,則會有一個演算法來建立一個(您應該直接指定它以簡化生活)。然後它會尋找符合操作輸入參數的參數簽名,外加一個用於內容的額外參數。事實上,如果您將應用程式配置為以偵錯記錄啟動,您會看到它試圖尋找什麼

DEBUG i.s.i.SwaggerOperationController - looking for operation: addPet(io.swagger.inflector.models.RequestContext request, com.fasterxml.jackson.databind.JsonNode body)

請注意,RequestContext 作為第一個參數——它包含有關標頭、媒體類型等的重要資訊。查看原始碼以查看它為您提供的內容。

請注意,body 直接對應到 JsonNode 物件。這會為您提供一個 MapArray 類型物件,以作為您的網域模型(這也適用於 XML)。

自動定位命名控制器 + 套件

對於勇敢的人,您可以配置 Inflector 掃描 controllerPackage

# inflector.yaml

controllerPackage: io.swagger.sample.controllers

Inflector 將永遠在此處尋找。您也可以使用 controllerPackage 設定預設套件查找,並設定 x-swagger-router-controller: SampleController,而不是使用 FQ 名稱。

然後,inflector 將使用第一個標籤來建構類別名稱。如果您有一個標記的操作

tags:

- pet

Inflector 現在將採用 controllerPackage + ‘.’ + Capitalize(tags[0]) + Controller 作為預期的類別名稱

io.swagger.sample.controllers.PetController

實作商業邏輯

實作商業邏輯就像在我們的控制器中建立該方法一樣簡單

package io.swagger.sample;

import io.swagger.inflector.models.RequestContext;

import io.swagger.inflector.models.ResponseContext;

import com.fasterxml.jackson.databind.JsonNode;

import javax.ws.rs.core.Response.Status;

public class SampleController {

// ResponseContext 是可選的,但它提供了關於寫入客戶端的額外控制

public ResponseContext addPet(RequestContext request, JsonNode body) {

return new ResponseContext()

.status(Status.OK)

.entity(body);

}

}

是的,很無聊,但這展示了我們現在如何完全控制回應物件,以及 Inflector 如何直接對應到我們的控制器!當您不需處理所有管道時,您會發現編寫 REST API 的速度有多快,這真是太棒了!

對應 POJO

假設我們想要一個具體的物件,而不是使用 JsonNode。這很容易做到——您有兩種選擇。

首先,您可以使用 modelMappings 語法直接在 inflector.yaml 中對應模型

modelMappings:

Pet: io.swagger.sample.models.Pet

您猜對了,當我們在 Swagger 描述中看到 Pet 定義時,我們將使用 io.swagger.sample.models.Pet 實作。

其次,您可以設定 modelPackage,Inflector 會嘗試尋找它

modelPackage: io.swagger.sample.models

這會導致 Inflector 從類別載入器載入 io.swagger.sample.models.Pet。如果這兩種情況都存在,您的方法簽名會看起來更友善

DEBUG i.s.i.SwaggerOperationController - looking for operation: addPet(io.swagger.inflector.models.RequestContext request, io.swagger.sample.models.Pet body)

現在您可以開始使用看起來更熟悉的程式碼

  public ResponseContext addPet(RequestContext request, Pet body) {

/* 您所有的商業邏輯 */

}

結論

這是一篇相當長的文章,但希望 Inflector 的設計優先目標很明確。一旦您開始編寫 API 而無需擔心所有的管道,您會發現您將建立更好的 API,而且速度更快。Inflector 消除了產生 Swagger 描述所需的註解和其他連接,並將 Swagger 本身置於 REST API 的 DSL 角色。