GIG

赴くままに技術を。

SwaggerでWeb APIを作る - Web APIの設計

Web APIの設計

Web APIの設計でExcelを使って定義書を作成していたが、仕様の変更等によって気がついたら設計書とシステムに乖離しているなんてことがあった。そのようなことがないようにWeb APIの定義情報を常に正とするようなアプリにしたい。

そこでSwaggerは、いくつかの企業によるコンソーシアムによって、Web APIの標準化を行うための規約とそのツール群を使う。

Swagger

Swaggerのサイトに行くとSwagger-editorやSwagger-UIなどツールがいくつかあるけど、swagger-nodeとbootprint-openapiで一通りできる。

GitHub - swagger-api/swagger-node: Swagger module for node.js

$ npm install -g swagger

GitHub - bootprint/bootprint-openapi: Bootprint-module to render OpenAPI specifications

$ npm install -g bootprint
$ npm install -g bootprint-openapi
$ npm -g install html-inline

手順としては、以下。

Swagger-editorでWeb API定義ファイルを作成する

はじめにプロジェクトのディレクトリを作る。 その際にNodeJSのwebフレームワークを選択させられる。これは後で作るモックサーバをカスタマイズするときに改造していくものである(Flaskが対応していたら便利だけど)。 expressを選択する。

$ swagger project create example

次に生成されたディレクトリに降りて、エディタを起動する。

$ cd example/
$ swagger project edit

Webブラウザが起動し、エディタが表示される。 GUIに対応できない環境(x windowを起動していないサーバなど)で利用するときは、-s | --silentを付ける。

今回はSwagger Editorのpetstores_simple.yamlを使う。

ドキュメント化する

bootprint-openapiでSwaggerファイルをHTML化する。

$ pwd
example
$ bootprint openapi api/swagger/swagger.yaml target

するとtargetディレクトリ配下にhtml, css, javascriptが書き出され、適当なwebサーバに配置することで閲覧できる。 さらにcss, javascriptをHTMLにまとめたいときは下記のようにし、dist.htmlを公開すれば良い。

$ html-inline target/index.html > target/dist.html

モックサーバを立てる

モックサーバはサーバ機能をまだ実装していない段階で、クライアント側の仕様検討、実装を待ちにすることなく行う目的で建てる。

$ pwd
example
$ swagger project start -m

クライアント側(同一端末だが)でAPIを叩く。 (2016.12.12 修正: 下記で/petsを叩いていたのだけど、そもそもbathPath: /apiなので、 そのようなAPIはないのは当たり前だった。

$ curl -i http://localhost:10010/api/pets
HTTP/1.1 404 Not Found
X-Powered-By: Express
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 21
Date: Mon, 12 Dec 2016 04:50:26 GMT
Connection: keep-alive

Cannot GET /api/pets

おや? 同じ事象がRunning in mock modeチュートリアル例でも起きる。

調べてみると、下記のissueに遭遇。x-swagger-router-controllerを指定しないとダメとのこと。 これを付けるのは、pathかoperationの階層で定義すれば良い。これとoperationIdAPIから呼ばれるメソッドを指定する。

issueでも言われているけど、モックなのにこれを指定しないといけないのは使い勝手が悪い気が...。 Flaskでswaggerに沿ったサーバ機能を作ることができるconnexionでは、むしろoperationレベルでのみでxx-swagger-router-controllerを設定できる。これだとoperationIdのみでもう良くないかと思えてきた。なのでモック使うとき以外は特に使用しないことにする。

github.com

pathに含まれるoperationで全て同じとする。

..(省略)..

  /pets:
    x-swagger-router-controller: pets

..(省略)..

  /pets/{id}:
    x-swagger-router-controller: pets

..(省略)..

改めてクライアント側で叩く。 そのまま使うと決められた値(numberなら1, stringなら"Sample text")が返ってくる。 レスポンスをカスタマイズしたい場合は、Implementing mock mode controllersを参照。

$ curl -X GET -i http://localhost:10010/api/pets
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json
Date: Sat, 24 Sep 2016 13:08:50 GMT
Connection: keep-alive
Content-Length: 51

[{"id":1,"name":"Sample text","tag":"Sample text"}]
$ cat pet.json 
{"id":1,"name":"foo","tag":"dog"}
$ curl -X POST --header "Content-Type: application/json" --header "Accept: application/json" -i http://localhost:10010/api/pets -d @pet.json
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json
Date: Sat, 24 Sep 2016 13:29:25 GMT
Connection: keep-alive
Content-Length: 49

{"id":1,"name":"Sample text","tag":"Sample text"}
$ curl -X GET -i http://localhost:10010/api/pets/1
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json
Date: Sat, 24 Sep 2016 13:36:02 GMT
Connection: keep-alive
Content-Length: 49

{"id":1,"name":"Sample text","tag":"Sample text"}
$ curl -X DELETE -i http://localhost:10010/api/pets/1
HTTP/1.1 500 Internal Server Error
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json
Date: Sat, 24 Sep 2016 13:37:25 GMT
Connection: keep-alive
Content-Length: 196

{"message":"Response validation failed: void does not allow a value","code":"INVALID_TYPE","failedValidation":true,"path":["paths","/pets/{id}","delete","responses","204"],"originalResponse":"{}"}

最後だけこけた。どうやらレスポンスが空は許可されていないみたい。 対象療法だけど、レスポンスを追加した。

    delete:
      description: deletes a single pet based on the ID supplied
      operationId: deletePet
      parameters:
        - name: id
          in: path
          description: ID of pet to delete
          required: true
          type: integer
          format: int64
      responses:
        '200':
          description: pet deleted
          schema:
            $ref: '#/definitions/pet'
        default:
          description: unexpected error
          schema:
            $ref: '#/definitions/errorModel'

どうにか。

$ curl -X DELETE -i http://localhost:10010/api/pets/1
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json
Date: Sat, 24 Sep 2016 13:51:11 GMT
Connection: keep-alive
Content-Length: 49

{"id":1,"name":"Sample text","tag":"Sample text"}

サーバ機能の雛形を生成する

最後にAPI仕様書からサーバ機能の雛形を作る

swagger-nodeの方ではないSwagger-Editorを用いると、サーバ機能のダウンロードができる(swagger-codegenでも良い)。 インストールしても良いが、Dockerイメージがあるので、コンテナにて行う。

$ docker pull swaggerapi/swagger-editor
$ docker run -p 80:8080 swaggerapi/swagger-editor

あとはhttp://(YOUR_CONTAINER_URI)にアクセスし、API仕様書の内容をコピー&ペーストして、[Generate Server]からお好みのフレームワークの雛形をダウンロードする。これからFlaskでサーバ機能を作るので、Flaskを選択する。