SwaggerでRESTful APIの管理を楽にする

  • 282
    いいね
  • 1
    コメント

スクリーンショット 2016-10-03 23.43.04.png

背景

最近は変化し続ける要件に対応するために、システムも柔軟であることが求められています。
そのため、部分的に変更やスケールの可能なシステムを構築し、API経由で連携するマイクロサービス的アーキテクチャが増えてきています。

そういった設計の中で問題になっていくのが、従来のモノリシックなアプリケーションではIDEやコンパイラなどで行っていた、機能間のインターフェイスをどう管理するかという部分です。

Swaggerとは?

スクリーンショット 2016-09-28 21.01.47.png

SwaggerとはRESTful APIのドキュメントや、サーバ、クライアントコード、エディタ、またそれらを扱うための仕様などを提供するフレームワークです。
公式サイトでは、The World's Most Popular Framework for APIsと謳っています。

その理由は、マイクロソフト、Google、IBM、SmartBearなどを大手の企業を含む「Open API Initiative(RESTful APIのインターフェイスを記述するための標準フォーマットを推進する団体)」がLinux Foundationの協力のもとで結成され、APIの記述のために採用したのが「Swagger」です。
Open API InitiativeはSwaggerをベースに、より充実した標準にしていくと説明しています。

しかし実際にSwaggerについて最初に調べたときに悩むのが、関連用語が多く用途と使い分けが分からないことです。
ここではその中でもよりSwaggerの中心になっている用語を説明し、後半ではそれらを使用しどのように開発を進めていくのかをまとめたいと思います。

Swagger関連用語

用語 説明
Swagger Specification Swaggerを扱う上で中心となる概念で、Swaggerの仕様に準じた、RESTful APIインターフェイスを記述するためのフォーマット(YAML/JSON)
Swagger Editorなどで編集するときには、YAMLを用いる方が扱いやすいが、外部のライブラリやSwagger UIと連携する場合にはJSON形式で使用
OpenAPI Specification Swaggerをベースに作られたRESTful APIインターフェイスを記述するためのフォーマット(JSON)
swagger-core JavaのコードからSwagger Specificationを生成するためのJavaライブラリ
swagger-node node.jsのコードからSwagger Specificationを生成するためのnode.jsライブラリ
swagger-js Swagger Specificationを扱うためのクライアントJavaScriptライブラリ
Swagger Editor ブラウザ上で動くSwagger Specificationのエディタ、リアルタイムで構文チェック、可視化
Swagger Codegen Swagger Specificationからクライアントコード生成するコマンドラインツール
Swagger UI Swagger Specificationから動的にドキュメントを生成するツール

その他、公式ページのリンクから関連ツールを確認することができます。

Swaggerツールの関係性

Swaggerには公式、コミュニティを含め多くのツールやライブラリ、サービスが存在します。
ここではその中で主要な使い方になる2つを紹介します。

トップダウン形式

  1. Swagger EditorでSwagger Specificationを編集、定義
  2. Swagger CodegenでSwagger Specificationからソースコードを生成

ボトムアップ形式

  1. 既に存在するREST APIのソースコードからSwagger Coreのアノテーションなどを使用しSwagger Specification定義
  2. 生成されたSwagger SpecificationをもとにSwagger UIによって、REST APIをドキュメント化

Untitled.png

Swaggerの使い方

ここからは、トップダウン、ボトムアップそれぞれの具体的な開発の流れをまとめたいと思います。

トップダウン形式

トップダウンの場合には、まず"Swagger Editor"を使用してYAMLかJSONのSwagger Specificationを作成していきます。
Webから直接アクセスできる、http://editor.swagger.io/ が楽ですが、業務的に外に出せない情報を扱う場合も多いので、各環境にインストールすることも出来ます。もしもDocker環境があるのであれば環境を汚さず手軽に試せるので https://hub.docker.com/r/swaggerapi/swagger-editor/ がおすすめです。
スクリーンショット 2016-10-07 0.12.31.png

左側が実際に編集出来るYAMLまたはJSONファイル、右側がその内容をもとに可視化されたUIになっています。

ここからは下記の記述をもとに少しずつ解説していきます。
(Swagger Specificationの詳細はこちらから確認できます。)

# Example YAML to get you started quickly.
# Be aware that YAML has indentation based scoping.
# Code completion support is available so start typing for available options.
swagger: '2.0'

# This is your document metadata
info:
  version: "0.0.0"
  title: <enter your title>

# Describe your paths here
paths:
  # This is a path endpoint. Change it.
  /persons:
    # This is a HTTP operation
    get:
      # Describe this verb here. Note: you can use markdown
      description: |
        Gets `Person` objects.
        Optional query param of **size** determines
        size of returned array
      # This is array of GET operation parameters:
      parameters:
        # An example parameter that is in query and is required
        -
          name: size
          in: query
          description: Size of array
          required: true
          type: number
          format: double
      # Expected responses for this operation:
      responses:
        # Response code
        200:
          description: Successful response
          # A schema describing your response object.
          # Use JSON Schema format
          schema:
            title: ArrayOfPersons
            type: array
            items:
              title: Person
              type: object
              properties:
                name:
                  type: string
                single:
                  type: boolean

apiinfo

version情報やタイトルなど

swagger: '2.0'

# This is your document metadata
info:
  version: "0.0.0"
  title: <enter your title>

paths

エンドポイントのルートを表します。paths以下各エンドポイントを記述します。

paths:
  /persons: 

また、RESTなどでよく利用するリソースIDなどの変数形式なデータは以下のように記述します。

paths:
  /persons/{id}:    

get, post

HTTPメソッドを指定します

paths:
  /persons:
    get:

description

エンドポイントの説明を記述します。

get:
  # Describe this verb here. Note: you can use markdown
  description: |
    Gets `Person` objects.
    Optional query param of **size** determines
    size of returned array

parameters

HTTPパラメータを記述します。

  parameters:
    -
      name: size
      in: query
      description: Size of array
      required: true
      type: number
      format: double
名前 説明
name パラメータの名前
in queryを指定すると/persons?id=xxのようにパラメータで値を送る(postの場合はbody)。pathを指定すると/persons/{id}のようにURLでパラメータを送るように設定する
description パラメータの説明
required 必須要素か否か
type パラメータの型。string, boolean, numberなど

responses

APIのレスポンス情報

  responses:
    200:
      description: Successful response
      schema:
        title: ArrayOfPersons
        type: array
        items:
          title: Person
          type: object
          properties:
            name:
              type: string
            single:
              type: boolean
パラメータ名 概要
schema レスポンスの値
type 型を指定する。arrayやobjectなど
items 配列などの中身
properties プロパティ情報。itemsを入れ子にすることもできる

definitions

APIの記述をおこなっていくと、同じ要素が複数箇所で登場する場合がある。
そういった場合には別箇所で要素の定義を行い再利用したほうが便利な場合も多い。
definitionsはこういった、要素を定義し利用することができる。

Locationはオブジェクトの名前

definitions:
  Location:
    type: object
    properties:
      id:
        type: string
      name:
        type: string
      latitude:
        type: number
      longitude:
        type: number

上記を定義しておくとpropertiesで下記のように$refで呼び出せる

  responses:
    200:
      description: OK
      schema:
        type: object
        properties:
          data:
            $ref: '#/definitions/Location'

また、下記のように入れ子も可能。

definitions:
  Media:
    type: object
    properties:
      id:
        type: integer
      location:
        $ref: '#/definitions/Location'
      comments:
        type: object
        properties:
          data:
            type: array
            items:
              $ref: '#/definitions/Comment'
  Location:
    type: object
    properties:
      id:
        type: string
      name:
        type: string
      latitude:
        type: number
      longitude:
        type: number
  Comment:
    type: object
    properties:
      id:
        type: string
      created_time:
        type: string
      text:
        type: string

セキュリティ

oauthなどのセキュリティ関連も設定も可能。

securityDefinitions:
  oauth:
    type: oauth2
    flow: implicit
    authorizationUrl: https://twitter.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token
    scopes:
      basic: |
       to read any and all data related to twitter
security:
  - oauth:
    - basic

メソッド内でoauthを指定する。

paths:
  /statuses/mentions_timeline:
    get:
      description: Returns the 20 most recent mentions for the authenticating user
      security:
       - oauth:
         - basic

Swagger Specificationからコードを生成

Swagger EditorによってSpecificationは出来上がったので、ここからはソースコードを生成してみます。
といっても、Swagger Editorを使用している場合には非常に簡単でGenerate ServerGenerate Clientから生成したいコードの種類を選ぶだけです。
スクリーンショット 2016-10-07 20.26.02.png

選択したコードの種類にもよりますが実行環境があれば、多くのものはそのままか、ビルドをすれば使えるので、開発プロジェクトのベースやモックサーバといて活用することが出来ます。

また、Swagger Editorではなくコマンドラインから実行する、Swagger Codegenを使用してソースを生成することもできます。
コマンドツールをダウンロードして、例えば以下のようなコマンドを実行するとSpringプロジェクトのソースコードが生成されます。

java -jar swagger-codegen-cli.jar generate swagger.json -l spring

Swagger Codegenで生成されるコードはSwagger Editorのコードと同じものになるので、Swagger Specificationとコードを生成する人が違っていたり、ビルドツールやCIなどのコマンドライン環境から使用したい場合に役立ちます。

ボトムアップ形式

実際の開発ではトップダウンで生成したソースコードがそのまま扱えない場合も多いと思います。
また、既に開発が始まっているシステムなどはボトムアップ形式でのSwaggerを扱う方が有効な場合も多いと思います。

例ではSpringで作られたアプリケーションから、Swagger APIとUIを提供するSpringfoxを使用します。

説明のために以下のような簡単な機能を持ったAPIを考えてみます。

  • 全ユーザ検索と、ユーザIDを指定した検索が可能
  • ユーザ登録と削除は管理者機能として提供

コードは以下のような形。

Sample
@Entity
class User {
    @Id
    @GeneratedValue
    Long id;
    String name;
    int age;

    Long getId() { return id; }
    String getName() { return name; }
    int getAge() { return age; }
    void setId(Long id) { this.id = id; }
    void setName(String name) { this.name = name; }
    void setAge(int age) { this.age = age; }
}

interface UserRepository extends JpaRepository<User, Long> {}

@RestController
@AllArgsConstructor
class SampleController {
    UserRepository repository;   

    @GetMapping("/users/")
    List<User> users() {
        return repository.findAll();
    }

    @GetMapping("/users/{id}")
    User user(@PathVariable Long id) {
        return repository.findOne(id);
    }

    @PostMapping("/admin/")
    void create(@RequestBody User user) {
        repository.save(user);
    }

    @DeleteMapping("/admin/{id}")
    void delete(@PathVariable Long id) {
        repository.delete(id);
    }
}

UserクラスとSpring Data JPAのRepositoryを使用したシンプルなAPIです。

このソースから、まず最小構成でSwagger環境を構築します。

Dependencyの追加

build.gradle
dependencies {
  compile "io.springfox:springfox-swagger2:2.6.0"
}

springfox-swagger2はSpringソースからSwagger Coreの機能を利用し、Swagger Specificationに従ったJSON APIを作成するためのライブラリです。

Swaggerの有効化

Swaggerのdependencyを追加した後、@Configrationなクラスに@EnableSwagger2を追加して起動します。

@Configuration
@EnableSwagger2
class Config {}

http://localhost:8080/v2/api-docs にアクセスすると、以下のようなSwagger Specificationに従ったJSONが取得できます。

{
  "swagger": "2.0",
  "info": {
    "description": "Api Documentation",
    "version": "1.0",
    "title": "Api Documentation",
    "termsOfService": "urn:tos",
    "contact": {},
    "license": {
      "name": "Apache 2.0",
      "url": "http://www.apache.org/licenses/LICENSE-2.0"
...

次にSwagger UIを有効にします。

Dependencyの追加

build.gradle
dependencies {
  compile "io.springfox:springfox-swagger2:2.6.0"
  compile "io.springfox:springfox-swagger-ui:2.6.0" // 追加
}

springfox-swagger-uiはSwagger Specification形式のAPIを呼び出し、Swagger UIを構築するためのHTMLやJavaScriptを提供します。
Springfoxはこの2つのライブラリにより、ソースからSwagger Specification + Swagger SpecificationからSwagger UIのボトムアップ形式の開発を容易に可能にします。

起動するとデフォルトでは、http://localhost:8080/swagger-ui.htmlのような{アプリケーションのルートパス}/swagger-ui.htmlがSwagger UIへのパスとなります。
スクリーンショット 2016-10-01 16.57.41.png

このようにdependencyの追加と設定クラスを作るだけでソースコードから、閲覧用のSwagger UIを簡単に作成することができます。
また、ソースコードを修正すれば自動的に反映されるので、動作しているアプリケーションからRESTfulなAPIの情報を常時確認することができるようになります。

また、Swagger UIは単純なAPIドキュメントとしてだけでなく、APIを直接呼び出すことのできるインタラクティブな機能を提供します。
必要なParametersなどを入力してTry it out!ボタンを選択すると実際にAPIを呼び出した結果も確認することが出来ます。
スクリーンショット 2016-10-07 21.57.04.png

Swagger UIのカスタマイズ

特に設定を行わない状態でも多くのAPI情報を得ることができますが、もっと細かく情報を追加したくなる場合も多いと思います。
そういった場合には、SwaggerのConfigクラスの修正やソースの中にアノテーションを付加することで追加出来ます。

Configクラスによるカスタマイズ

Configクラスによるカスタマイズは主にSwagger UI(Swagger Specification API)の全体に関わる設定です。

ここでは例として、先程のConfigクラスに設定を追加し、User用とAdmin用にグループを作成、表示項目を分割してみます。

@Configuration
@EnableSwagger2
class Config {
    @Bean
    public Docket user() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("user")
                .select()
                .apis(RequestHandlerSelectors.withMethodAnnotation(GetMapping.class))
                .paths(PathSelectors.regex("/users.*"))
                .build()
                .apiInfo(apiinfo());
    }

    @Bean
    public Docket admin() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("admin")
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build()
                .apiInfo(apiinfo());
    }

    private ApiInfo apiinfo() {
        return new ApiInfoBuilder()
                .title("User API")
                .description("User情報を扱うためのAPIをです。")
                .version("1.0")
                .contact(new Contact("disc99","http://disc99.com", "disc99@mail.com"))
                .build();
    }
}

Docketを@Beanによりコンテナに登録することで、Swagger Specificationのメイン情報を設定することが出来ます。

メソッド 説明
getName グループ名を指定することで、複数のDocketを登録することができ、登録するとSwagger UI上部のセレクトボックスからプルダウンでグループを選択が可能
select Swagger Specificationの対象を選択
apis 使用するAPIの設定
paths 使用するAPIのパスを設定
apiInfo APIの基本情報を設定

Swagger UIを確認すると、設定項目が反映されています。

スクリーンショット 2016-10-08 10.26.59.png

さらに詳しい情報はSpringfoxのドキュメントから確認できます。

アノテーションによるカスタマイズ

アノテーションによるカスタマイズは主に各エントリポイント単位の設定です。
こちらも、先程のAPIに詳細情報を追加します。

@Entity
class User {
    @Id
    @GeneratedValue
    Long id;
    String name;
    int age;

    @ApiModelProperty(value = "ユーザID", required = true)
    Long getId() { return id; }
    @ApiModelProperty(value = "ユーザ名", required = true)
    String getName() { return name; }
    @ApiModelProperty(value = "ユーザ年齢")
    int getAge() { return age; }
    void setId(Long id) { this.id = id; }
    void setName(String name) { this.name = name; }
    void setAge(int age) { this.age = age; }
}

@RestController
@AllArgsConstructor
class SampleController {
    //...

    @ApiOperation(value = "ユーザ情報取得", notes = "指定されたユーザの情報を取得します。", response = User.class, tags = {"User",})
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "指定されたユーザ情報", response = User.class)})
    @GetMapping("/users/{id}")
    User user(@ApiParam(value = "ユーザID", required=true) @PathVariable Long id) {
        return repository.findOne(id);
    }

    //...
}

今回は以下のアノテーションを使用し、情報を追加しています。
こちらは、Swaggerが提供しているアノテーションで、より詳しい情報はこちらから確認することができます。

アノテーション 説明
@ApiOperation APIの操作や、パスなどの情報を設定
@ApiResponses, @ApiResponse APIのレスポンスに関する情報を設定
@ApiParam APIのパラメータに関する情報を設定
@ApiModelProperty APIのモデルに関する情報を設定

スクリーンショット 2016-10-08 9.19.29.png

Swaggerからドキュメント生成

Swagger UIを用いれば、動的にUIドキュメンテーションを作ることは出来ますが、そのためには動作させるためのサーバが必要になったり、オフライン環境やブラウザを使わなくても確認できるドキュメントが欲しくなる場合もあると思います。
そのような場合に使えるのが、Swagger2Markupです。
このライブラリはSwagger Specificationから、AsciiDocやHTML、PDFなどのドキュメントを作成することができます。
今回は、Springアプリケーション用にSwagger2Markupと統合されたspringfox-staticdocsを使用します。

Dependencyの追加

先程まで作成していたプロジェクトに以下の依存性を追加します。

build.gradle
dependencies {
  compile "io.springfox:springfox-swagger2:2.6.0"
  compile "io.springfox:springfox-swagger-ui:2.6.0"
  testCompile "io.springfox:springfox-staticdocs:2.6.0" // 追加
}

ドキュメント生成コードを記述

ドキュメントを作成するためにはSwagger Specificationを取得するためにテストコードからを記述し作成します。

@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Application.class, loader = SpringApplicationContextLoader.class)
public class Swagger2MarkupTest {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
    }

    @Test
    public void convertSwaggerToAsciiDoc() throws Exception {
        this.mockMvc.perform(get("/v2/api-docs")
                .accept(MediaType.APPLICATION_JSON))
                .andDo(Swagger2MarkupResultHandler.outputDirectory("src/docs/asciidoc/generated").build())
                .andExpect(status().isOk());
    }
}

このテストを実行することで、src/docs/asciidoc/generated配下にdefinitions.adocoverview.adocpaths.adocなどのasciidocファイルが生成されます。
これらを、Spring REST Docsと組み合わせて使用することで、実際に動作しているAPIから簡単に今風なドキュメントを作成することが出来ます。
スクリーンショット 2016-10-07 21.41.37.png

Swaggerの競合

最後にSwaggerの他にどのような競合があるかも上げておきます。

  • API Blueprint
    MarkdownファイルからAPIドキュメントやサンプルデータになるJSONを生成するための仕様で、apiary.ioと合わせて使用するこで、REST APIの一括して管理することができる。

  • RAML
    RESTful API Modeling Languageの略で、YAMLをベースにしたREST API定義のための言語。チャットワークのAPIドキュメントなどにも採用されている。

トレンド

Swaggerは固有名詞ではないのでノイズが入っている可能性もありますが、ここ3年のトレンドを見る限り、Swaggerが優勢なようです。

Kobito.H7p5Wh.png

まとめ

Swaggerは Swagger Specificationを中心にトップダウン、ボトムアップなどSwaggerを使用することで一貫してRESTful APIを扱うことができます。
使い勝手の面ではまだ、競合するツールと迷う部分もありますが、OpenAPI Specificationのベースにもなっているなどの将来性も踏まえると有力な選択肢になると思います。

参考