Swagger InflectorでAPIを記述する

  2015年8月13日

著者: Tony Tam

Swagger Inflectorは、JVM上でAPIを記述するためのSwaggerチームによる新しいプロジェクトです。現在プレビュー版ですが、JavaでAPIをコーディングするための代替の設計先行型のアプローチとして、積極的に開発とサポートが行われています。Inflectorプロジェクトの利用方法について説明します。

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

はじめに

まず第一に、Swagger仕様から始めます。お気に入りのテキストエディタでJSONまたはYAML形式で仕様を作成するか、http://editor.swagger.ioの素晴らしいオンラインエディタを使用してAPIの定義を構築できます。変更を心配する必要はありません!Inflectorがコードを一切書かずに設計を迅速に繰り返すのにどのように役立つかについて説明します。

このデモでは、ここからSwaggerの説明をコピーしたと仮定します。

次に、Inflectorを設定するための単一のYAMLファイルを作成します。これにより、いくつかのオプションが可能になりますが、ここではSwaggerの説明の場所だけを記述します。

# inflector.yaml

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

Inflectorは内部でSwagger Parserを使用するため、ローカルファイルまたはリモートファイルを指定できます。定義をホストしている場合は、httpまたはhttpsプロトコルを指定するだけで、Swagger Parserがそれを取得します。この機能には非常に興味深いユースケースがあり、それについては今後のブログ記事で説明します。

次に、Inflectorの依存関係を追加しましょう。実行時には、InflectorはRESTフレームワークとしてJersey 2.6を単に利用するため、本番環境への統合には多くのオプションがあります。今のところ、Jetty Maven Pluginを使用して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をご覧ください。

プラグイン設定で2つのスキャンターゲットを設定したことに注意してください。これは、`./inflector.yaml`または`src/main/swagger/swagger.yaml`記述ファイルが変更された場合に、Jettyがアプリケーションを再起動するように指示します。これにより、Inflectorでの開発がどれほど簡単になるかがわかるでしょう。

最後に、`web.xml`を追加しましょう。これにより、InflectorアプリケーションをJerseyコンテキストのサービスルートにマウントできます。また、Swagger UIからSwagger APIを簡単に読み取れるようにCORSフィルターも追加します。

<!-- 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と同じで、フィルターや静的サーブレットなどを追加できます。ここに魔法はありません!

これでプロジェクトの設定は完了です。さあ、試してみましょう。

サーバーを起動する

mvn package jetty:run

Jettyプラグインで設定されたポート`8080`でSwaggerリストを読み取ります。Swagger記述で`basePath`に設定した内容に基づいて、`https://:8080/{basePath}/swagger.json`でSwagger記述を開くことができます(もちろん、仕様に従ってbasePathは省略できます)。これにより、編集したJSON形式の仕様が表示されます。わかっています、大したことありませんが、まだ始まったばかりです。

Jettyプラグインの設定により、`swagger.yaml`ファイルを変更すると自動的にリロードされる(1秒ごとにスキャンされる)ことに注意してください。重要なポイントが1つあります!無効な仕様を記述した場合、サーバーはそれを提供することを拒否します。有効なSwagger記述を作成するためのコンテキストに依存したヘルプについては、Swagger Editorを使用してください!

では、興味深い部分に進みましょう。記述からルートを取り上げてみましょう。

  /pet/{petId}:

get

タグ

- pet

概要: IDでペットを検索

説明: 単一のペットを返します

operationId: getPetById

consumes

- application/x-www-form-urlencoded

produces

- application/xml

- application/json

parameters

- name: petId

in: path

説明: 返すペットのID

required: true

type: integer

format: int64

レスポンス

"200":

説明: 成功した操作

schema

$ref: "#/definitions/Pet"

"400":

説明: 無効なIDが提供されました

"404":

説明: ペットが見つかりません

このエンドポイント(`https://: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

タグ

- pet

概要: ストアに新しいペットを追加

operationId: addPet

# このクラスでコントローラーを探すようにInflectorを設定しています!

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

起動時、Inflectorはクラス `io.swagger.sample.SampleController` でこのメソッドに一致するメソッドを探します。しかし、そのシグネチャは何でしょうか?

Inflectorは`operationId`をメソッド名として使用します。存在しない場合は、作成するためのアルゴリズムがあります(簡単に済ませるには指定するべきです)。その後、操作への入力パラメータと、コンテキストのための追加の1つのパラメータに一致するパラメータシグネチャを探します。実際、デバッグログを有効にしてアプリケーションを起動すると、何を見つけようとしているかがわかります。

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

最初のパラメーターとしての`RequestContext`に注目してください。これには、ヘッダー、メディアタイプなどに関する貴重な情報が含まれています。提供される内容については、ソースをご覧ください。

`body`は`JsonNode`オブジェクトに直接マッピングされることに注意してください。これにより、ドメインモデルとして扱う`Map`または`Array`型オブジェクトが得られます(これはXMLにも当てはまります)。

コントローラー+パッケージを自動的に命名する

上級者向けに、Inflectorを`controllerPackage`をスキャンするように設定できます。

# inflector.yaml

controllerPackage: io.swagger.sample.controllers

Inflectorが常に検索する場所。`controllerPackage`を使用してデフォルトのパッケージ検索を設定し、FQ名を使用する代わりに`x-swagger-router-controller: SampleController`を設定することもできます。

その後、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」を使う代わりに、具体的なオブジェクトを使いたいとしましょう。これは簡単にできます。2つの選択肢があります。

まず、`inflector.yaml`で`modelMappings`構文を使ってモデルを直接マッピングできます。

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の役割に置きます。