外部連携APIのモックサーバをSwaggerを利用して作る
外部APIとの連携をしたい。でも同時並行で実装中でモックすら無い。どうしよう…。
様々な関係会社の方々と連動して仕様を定めながら開発を進めていかなければならない場合、実際の接続を確認しながら実装を作りたいけど接続先のサーバがない、という状況が起こると思います。 簡単なものであれば、 Mockable.ioなどを使ってシンプルに応答を返すこともできるのですが、実際に開発やテストを行うには幾つかの機能が求められます。
- 特定条件のリクエストに対してエラー応答したい、またはタイムアウトを発生させたい
- 特定条件のリクエストに対して様々なパターンの正常応答を返したい
- 開発の進行に伴い、実装に合わせてモックの動きもバージョン管理したい
このようなニーズに答えるにはMockable.ioなどのサービスだけでは難しく、API開発元に開発中バージョンのモックを作ってもらうか、自らモックサーバを実装する必要が出てきます。
できれば開発元にモックやクライアントライブラリの作成をお願いしたいと思いつつ、オープンなAPIでなければそのようなものは作られないでしょう。 しかし、自作するにしてもスクラッチでWebアプリケーションを作って本格的にAPIサーバを作るのは辛いですよね。(そこまで辛くなければ良いのですが…) そこで、なんらかのツールを使ってAPIモックサーバを作ることを考えてみましょう。
APIドキュメンテーションツールの選択肢
そもそも、外部との連携を行うためのHTTPベースのAPI(Application Programming Interface)を作る場合は、クライアントとなるシステムと正しいやり取りを行うために 正確なドキュメント が必要です。 エクセルなどで頑張る人もいると思うのですが、これらを支援するツールが APIドキュメンテーションツール です。様々なツールがあり、それぞれAPIの仕様を記述するのに決められたフォーマットを持っています。 以下に代表的なフォーマットを列挙します。
これらのフォーマットに共通した特徴として、
などを記述できるようになっており機械的に処理ができるようになっているので、ツールを使えばHTMLのAPIドキュメントが出力できますし、自動的にクライアント実装を生成してWebブラウザでAPIのリクエストを実際に発行したりすることができます。 接続先を変更することで実際に稼働しているAPIとも接続ができ、「動作するドキュメント*1」としてとても有用です。
それと同時に、各言語向けのサーバまたはクライアントの実装テンプレートを出力するジェネレータが存在します。 あくまでもテンプレートなので必要に応じて個別に実装を作り込む必要がありますが、全てを実装しなければならなかったことを考えるとかなり楽になります。
このフォーマットの中で現時点で最も人気なのが Swagger で、RESTful APIのドキュメンテーションフォーマットを策定するOpen API Initiativeのベースとなるフォーマットとして指定されています。*2
今回は Swagger + Node.js によるモックサーバの作り方のサンプルを書いてみます。
Swagger-Editor によるドキュメント作成
Swaggerの定義を書くのに便利なツールはSwagger-Editorです。
初回アクセス時にはテンプレートが表示されており、執筆時点ではUber APIをサンプルにAPI定義が書かれています。
左画面にYAML形式のAPI定義を記述すると、右画面のドキュメントが自動的に更新されていき、内容に間違いがある場合はエラーを表示して指摘してくれます。
YAMLの記述方法について
SwaggerのAPI定義は Swagger Specification を参照することになりますが、全体的にちょっとわかりにくいかもしれません。
長いですが、以下はユーザのCRUDを定義したサンプルコードです。
### メタ情報 swagger: '2.0' info: title: Sample API description: Sample of Simple API version: "1.0.0" host: sample.example.com schemes: - https # 全てのAPIのペースになるパス。 /v1/users のようになる。 basePath: /v1 produces: - application/json consumes: - application/json ### エンドポイント一覧 paths: /users: get: summary: ユーザ一覧 parameters: - name: name in: query description: ユーザ名 type: string required: false responses: 200: description: ユーザ一覧 schema: type: array items: $ref: "#/definitions/User" examples: "application/json": - id: 1 name: hoge email: hoge@example.com post: summary: ユーザ追加 parameters: - name: user in: body description: ユーザ情報 schema: $ref: "#/definitions/UserRequest" responses: 201: description: Created schema: $ref: "#/definitions/Success" default: description: Error schema: $ref: "#/definitions/Error" /user/{id}: get: summary: ユーザ情報 description: | 長文はこちらに書く parameters: - name: id in: path description: ユーザID required: true type: integer format: int32 responses: 200: description: 商品一覧の配列 schema: $ref: '#/definitions/User' default: description: エラー schema: $ref: '#/definitions/Error' put: summary: ユーザ更新 description: | 長文はこちらに書く parameters: - name: id in: path description: ユーザID required: true type: integer format: int32 - name: user in: body description: ユーザ情報 schema: $ref: "#/definitions/UserRequest" responses: 200: description: 商品一覧の配列 schema: $ref: '#/definitions/User' default: description: エラー schema: $ref: '#/definitions/Error' delete: summary: ユーザ削除 parameters: - name: id in: path description: ユーザID required: true type: integer format: int32 responses: 200: description: Deleted schema: $ref: "#/definitions/Success" ### JSONモデルの定義 definitions: User: type: object properties: id: type: integer format: int32 name: type: string email: type: string format: email required: - id - name - email UserRequest: type: object properties: name: type: string email: type: string format: email required: - name - email Success: type: object properties: code: type: integer format: int32 message: type: string fields: type: string Error: type: object properties: code: type: integer format: int32 message: type: string fields: type: string
大まかには「メタ情報」「エンドポイント定義」「モデル定義」に別れます。 メタ情報はAPIサーバのホスト名やポート番号、リクエスト可能なHTTPスキーマ(https)、使用するフォーマット(application/json)、必要な認証情報などを記載します。
paths
はエンドポイントのパス( /products/{id}
)、HTTPメソッド(get)の順に階層を記述し、その下層にAPIの説明文(summary, description)やリクエストパラメータ(parameters)、HTTPステータス別のレスポンスパラメータ(responses)を記述します。またAPIエンドポイントが多いときに利便性を向上するため、タグ(tags)などを指定できます。
parameters
の in
は
- エンドポイントのパス(path)
- クエリ文字列(query)
- ヘッダ文字列(header)
- フォーム形式(formData)
- リクエストボディ(body)
が指定でき、例えば JSON形式のリクエストボディの場合は、
name: user in: body description: user to add to the system required: true schema: $ref: '#/definitions/UserRequest'
のように指定します。ここで $ref: '#/definitions/UserRequest'
となっている部分はJSONで記述する UserRequest
モデルの定義箇所を指定していて、paths
の次の definitions
セクションで記述します。
definitions: UserRequest: type: object properties: name: type: string description: 名前 email: type: string format: email description: メールアドレス required: - name - email
name
と email
の属性名と形式を指定し、どちらも必須です。
サーバ実装の生成
APIドキュメントが出来上がったら、 Generate Server から Node.js を選び、zipファイルをダウンロードします。これでひとまずのAPIサーバが手に入ります。
解凍すると以下のようなファイル構成になっています。
nodejs-server-server ├── LICENSE ├── README.md ├── api │ └── swagger.yaml ├── controllers │ ├── Default.js │ └── DefaultService.js ├── index.js └── package.json
api/swagger.yaml
は先程記述したYAML定義そのもので、サーバ実装は controllers
以下に各エンドポイントの定義が書いてあり、 index.js
がサービスを起動させます。
最初に実行するためには Node.js のパッケージを幾つかインストールする必要がありますので、このフォルダで以下のコマンドを実行し、 package.json
に書かれているパッケージ一覧をインストールします。
$ npm install
インストールが完了すると、次のコマンドで起動できます。
$ node index.js Your server is listening on port 8080 (http://localhost:8080) Swagger-ui is available on http://localhost:8080/docs
http://localhost:8080/docs
にアクセスすると、Swagger-UIというHTMLドキュメント兼クライアントが表示できます。
試しに GET /v1/user/1
を実行するにはこのようにします。
しかしこれは上手く行きませんでした。なぜならYAMLの定義にサーバのホスト名が sample.example.com
と書いてあったため、リクエスト先に実際にサーバがないためです。 ローカルで試したい場合は api/swagger.yaml
を少しだけ修正して http://localhost:8080
へのリクエストにしましょう。
- host: "sample.example.com" + host: "localhost:8080" basePath: "/v1" schemas: - - "https" + - "http"
編集した後、ブラウザをリロードするとリクエスト先が変更されますので、再度リクエストをしてみると次のようにレスポンスが返ってきます。
このレスポンスは controllers/DefaultService.js
の userIdGET
という関数によって以下のように定義されています。
exports.userIdGET = function(args, res, next) { /** * parameters expected in the args: * id (Integer) **/ var examples = {}; examples['application/json'] = { "name" : "aeiou", "id" : 123, "email" : "aeiou" }; if(Object.keys(examples).length > 0) { res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(examples[Object.keys(examples)[0]] || {}, null, 2)); } else { res.end(); } }
この値を直接変更したり、条件分岐を記述することで様々なレスポンスを返すようになります。 また、JSONボディによるPOSTリクエストでは数値のパラメータに文字列を記述するなどのデータフォーマット違反を検知するなど、API定義に記述された内容を満たしているかswagger-toolsモジュールが自動的にチェックしてくれます。
次回はAPI定義のパラメータに記述できるバリデーションや Node.js モックサーバをより使いやすくする方法に関して説明しようと思います。
*1:動作しないドキュメントはメンテナンスを怠ると陳腐化し弊害を生むことも多々あります。
*2:http://www.publickey1.jp/blog/15/open_api_initiative.html