Swaggerを試し、自動生成されたコードを見てみる
イントロ
会社でSwaggerがブームっぽくなっているので、使ってみました。 気になっていた機能は、codeGenです。 これのおかげで、どれほどAPIサーバーの開発が楽になるのかを体感してみたいと思います。
なお、この記事では自分がしたことの履歴のみを記載し、「Swaggerとは」という内容は他のサイトなどに任せるとします。
SwaggerEditorの起動
まずは、Swagger Editorをダウンロードします。
$ git clone https://github.com/swagger-api/swagger-editor.git Cloning into 'swagger-editor'... remote: Counting objects: 33660, done. remote: Compressing objects: 100% (35/35), done. remote: Total 33660 (delta 16), reused 28 (delta 11), pack-reused 33613 Receiving objects: 100% (33660/33660), 198.93 MiB | 4.95 MiB/s, done. Resolving deltas: 100% (19003/19003), done. $ cd swagger-editor $ explorer .
あとは、index.htmlをブラウザで開くだけです。 なにこれ、簡単すぎ。
Swagger-Editorの画面
Swagger-Editor起動時には、サンプルのAPIドキュメントが書かれています。 Swagger Petstoreというサービスのようです。
サンプルを見て、いくつか、気付くところがあります。
- 鍵マークがついているAPIとついていないAPIがある
GET /pet/findByTags
に訂正線が付けられている- Modelを登録して、Responseとかに使える
- Try it outボタンの意味
鍵マーク
直感的には、これの意味が分かりませんでしたが、各APIのスコープ(APIをたたける人の制限)を示すものだったようです。
鍵マークが付いているものは、tokenによる認証が必須のエンドポイントだそうです。 鍵が外れたマークになっていますが、鍵がかかっているマークとかは特にないようです。
後で、token認証を、モックアップサーバーで実際に試してみましょう。 その内容は、別の記事で紹介したいと思います。
訂正線
Deprecated (非推奨)のAPIということだそうです。
Model
Responseデータのデータモデルを登録しておけば、API開発時にそのモデルを指定するだけで、Response内容が設計できるようです。 これだけで、だいぶ設計が楽になりそうです。
Try it outボタン
設計したAPIを試すためのボタンです。
APIの受け口になるWebサーバーを立ち上げていないと、このボタンは使えません。
今は、ブラウザでindex,html
を開いているだけなので使えないようです。
しかし、次に記載するモックアップサーバーを起動したら、そちらでTry it outができます。
モックアップサーバー
立ち上げ
モックアップサーバーを立ち上げるためには、まずCodeGen機能でコードを取得しましょう。 上のタブのGenerate Serverをクリックし、自分が慣れている言語・フレームワークを選択します。 自分は、nodejs-serverを選択しました。
Zipで資材がダウンロード出来るので、それを解凍します。
解凍された中にREADMEが入っていました。
npm start
を実行せよ、とのことです。
当然ここは、上で選択した言語・フレームワークごとに適切な指示が書かれており、それに従えば良いものと思われます。
APIドキュメントページ
実行後、http://localhost:8080
が、API Webサーバーのホスト:ポートになっているようです。
http://localhost:8080/docs/
が、APIドキュメントページです。
このページを、Swagger UIと呼ぶそうです。
API仕様をユーザーに見せるためには、これをちょこちょこっと修正するだけで良さそうです。
これは、自力でAPI開発するのが馬鹿馬鹿しくなる位、幸せになれそう。
docsのほうで、Try it outボタンがあります。これをクリックすると、試しにAPIがたたけるようです。
何もパラーメータが必要ない、GET /store/inventory
を試してみましょう。
すると、打ち込んだリクエスト、レスポンス、レスポンスコード、レスポンスヘッダーが出力されます。
モックアップサーバーとして、十分です。
モックアップサーバーのコード
CodeGenでどんなコードが生成されていたのか、気になりますよね。見てみましょう。
package.json
まずは、package.json
を見てみましょう。
先ほどのnpm start
で何が行われていたか、一応チェックしたところ、node index.js
だけでした。
あっさりしたものです。
あと、依存しているライブラリはこの通りです。
"dependencies": { "connect": "^3.2.0", "js-yaml": "^3.3.0", "swagger-tools": "0.10.1" }
Webサーバーフレームワークには、connectを使っているようです。 connectについては、以下の記事が分かりやすそうです。
swagger-toolsは、まだ謎です。 他のコードを見ていけば、何のために存在しているか、つかめることでしょう。
ディレクトリ構造
次に、ディレクトリ構造を見てみます。
$ tree -I node_modules . . ├── api │ └── swagger.yaml ├── controllers │ ├── Pet.js │ ├── Store.js │ └── User.js ├── index.js ├── package.json ├── package-lock.json ├── README.md ├── service │ ├── PetService.js │ ├── StoreService.js │ └── UserService.js └── utils └── writer.js 4 directories, 12 files
それぞれのディレクトリについて、見ていきましょう。
api
ディレクトリ
api
ディレクトリには、Swagger Editorで設計したyamlファイルが入っています。
最初は、なぜ設計に用いたファイルが、ここにも置かれているのか疑問でしたが、どうやらmiddlewareの作成に利用しているようです。
また、ディレクトリ構造や、各ファイルを軽く見ると、ルーティングを定義しているコードがない事にも気付きます。
index.js
内で、後述するコントローラーの関数と、api
ディレクトリ内のyamlファイルをパラメータとして、ルーティングを設定する関数middleware.swaggerRouter
を呼ぶことで、ルーティングを設定しているようです。
これらのmiddlewareの作成などに、swagger-toolsが大きな役割を担っているようです。 では、他のディレクトリを見に行く前に、swagger-toolsを見に行きましょう。
swagger-tools
node_modules
内のswagger-toolsの中身を見てみましょう。
全てのファイルを列挙すると、多すぎるので、2階層に限定しています。
$ tree swagger-tools -I node_modules -L 2 swagger-tools ├── bin │ └── swagger-tools ├── index.js ├── lib │ ├── helpers.js │ ├── specs.js │ └── validators.js ├── LICENSE ├── middleware │ ├── helpers.js │ ├── swagger-metadata.js │ ├── swagger-router.js │ ├── swagger-security.js │ ├── swagger-ui │ ├── swagger-ui.js │ └── swagger-validator.js ├── package.json ├── README.md └── schemas ├── 1.2 ├── 2.0 └── json-schema-draft-04.json 7 directories, 15 files
index.js
で、module.exports
されているinitializeMiddleware
は、後述しますが、自動生成されたコードのindex.js
でも利用されています。
swagger-tools内のindex.js
では、specs
もmodule.export
されています。
specs
は、./lib/specs.js
をrequireしているだけですが、specs.js
の中では、Specification
関数があり、Swagger仕様オブジェクト、というものが作られます。
これも重要な気がします。
var initializeMiddleware = function initializeMiddleware (rlOrSO, resources, callback)
第一引数、The Resource Listing or the Swagger Objectの略だそうです。
ここに、自動生成された方のAPI仕様ファイル、./api/swagger.yaml
をyamlとして読み込んだオブジェクトが投入されます。
ルーティングは、自動生成されたindex.js
の中で、後述するコントローラーや、Swagger UIのパスなどをパラメータとして、middlewareのルーティングを設定する関数swaggerRouter
に渡してやれば良いようです。
これは、index.js
の33行目で行われています。
// Route validated requests to appropriate controller
app.use(middleware.swaggerRouter(options));
controllers
ディレクトリ
controllers
ディレクトリは、MVCのC、コントローラーですね。
届いたリクエストに応じたイベントハンドラで、モデルを操作します。
コントローラーのコードが自動生成出来るって、すごくないですか? どのようにして自動生成出来ているのか見るために、少しコードを見てみましょう。
var utils = require('../utils/writer.js'); var Pet = require('../service/PetService'); module.exports.addPet = function addPet (req, res, next) { var body = req.swagger.params['body'].value; Pet.addPet(body) .then(function (response) { utils.writeJson(res, response); }) .catch(function (response) { utils.writeJson(res, response); }); }; module.exports.deletePet = function deletePet (req, res, next) { var petId = req.swagger.params['petId'].value; var api_key = req.swagger.params['api_key'].value; Pet.deletePet(petId,api_key) .then(function (response) { utils.writeJson(res, response); }) .catch(function (response) { utils.writeJson(res, response); }); }; module.exports.findPetsByStatus = function findPetsByStatus (req, res, next) { var status = req.swagger.params['status'].value; Pet.findPetsByStatus(status) .then(function (response) { utils.writeJson(res, response); }) .catch(function (response) { utils.writeJson(res, response); }); };
writer.js
とservice
ディレクトリ内のコードに依存しています。
これらの関数の名前は、yamlファイル内の各エンドポイントのoperationId
に設定された値と同じです。
それぞれの関数内をよく見てみると、モデルの操作は行っていません。
モデルの操作は、コントローラーでは行わず、service
ディレクトリ内のコードに処理を任せているようです。
コントローラーでは、そのサービスへの適切な引数・返値に合わせた処理のみが記載されています。
では、service
ディレクトリを見ていきましょう。
service
ディレクトリ
先ほどと同じように、PetService.js
の中を少し見ていきましょう。
/** * Add a new pet to the store * * * body Pet Pet object that needs to be added to the store * no response value expected for this operation **/ exports.addPet = function(body) { return new Promise(function(resolve, reject) { resolve(); }); } /** * Deletes a pet * * * petId Long Pet id to delete * api_key String (optional) * no response value expected for this operation **/ exports.deletePet = function(petId,api_key) { return new Promise(function(resolve, reject) { resolve(); }); } /** * Finds Pets by status * Multiple status values can be provided with comma separated strings * * status List Status values that need to be considered for filter * returns List **/ exports.findPetsByStatus = function(status) { return new Promise(function(resolve, reject) { var examples = {}; examples['application/json'] = [ { "photoUrls" : [ "photoUrls", "photoUrls" ], "name" : "doggie", "id" : 0, "category" : { "name" : "name", "id" : 6 }, "tags" : [ { "name" : "name", "id" : 1 }, { "name" : "name", "id" : 1 } ], "status" : "available" }, { "photoUrls" : [ "photoUrls", "photoUrls" ], "name" : "doggie", "id" : 0, "category" : { "name" : "name", "id" : 6 }, "tags" : [ { "name" : "name", "id" : 1 }, { "name" : "name", "id" : 1 } ], "status" : "available" } ]; if (Object.keys(examples).length > 0) { resolve(examples[Object.keys(examples)[0]]); } else { resolve(); } }); }
ここに、具体的なモデルの操作を、記載できそうですね。 基本的にAPIリクエストを受け取った後は、非同期的に処理を行う事が前提になっているようです。 具体的なモデルの操作は、yamlに書かれていないので、ほぼ空欄同然になっています。
findPetsByStatus
関数だけは、色々と書かれています。
これは、この関数が、Petモデルを返すため、Petモデルに定義されていたexampleがここに書かれているのだと思われます。
このexampleは、Swagger EditorのyamlまたはJSONで修正する事ができます。
findPetsByStatus
関数を修正する際は、このPetモデルのexampleを見ながら修正できるので、ものすごくありがたいですね。
残り
utils
ディレクトリとindex.js
の説明は、特にAPI設計開発において主要な所ではないので飛ばしました。
見れば何をしているか、すぐに分かりますし。
おわりに
確かに、Swagger Editorを使えば、API開発の簡単化+ミスの抑制に繋がると思います。 ただし気になるのは、既にある程度のAPIが公開されているアプリケーションに対して、後からSwaggerを使ってAPIの設計を管理したい、と思ったとき、どう使っていけば良いのか…。 これは、追々、ちゃんと使ってみてから、また見解を書きたいと思います。
自動生成されたコードに関して言うと、ルーティングがハードコーディングされていない設計は、拡張性が高まるので、素晴らしいと思います。 サービス・コントローラーをあらかじめ用意しておけば、yamlを書き換えるだけで、すぐにAPIの中を切り替えられます。 ユーザーからして、突然APIで提供される機能が変わったら大事なので、API提供の思想としてそれをして良いときは、見極めないと行けませんが。
あと、MVCではなく、コントローラーとモデルの間に、サービスが挟まっていますね。 このサービスの意義を、今後、もう少し深掘りしたいと思います。 恐らく、Springと同じサービスで、ここにビジネスロジックを書くのでしょう。