トップダウン Swagger

この記事は RECRUIT MARKETING PARTNERS Advent Calendar 2015 の投稿記事です。

全国100万人のサーバサイドエンジニアのみなさん、こんにちは。産まれたばかりのサービスゼクシィキッチン で主にサーバサイドを担当している yewton です。最近きゅんきゅんしてますかー?

さて、昨今の新規サービス開発で避けて通れないのはWeb APIドキュメント作成ですね。今どきスマホアプリが無いサービスなんてあんまり無いでしょう、たぶん。APIドキュメントがクライアントとサーバ間の唯一のI/Fなわけですので、しっかり運用していきたいですよね。

とはいえExcelやらWordやらWikiやらに一生懸命書いていったって、メンテされずに陳腐化していくのがオチですね?このあたりは 以前もこのブログで取り上げられているので、ぜひ今一度読んでいただけるとよいと思います。

以前は API Blueprintaglio が取り上げられていましたが、今回はもうひとつのソリューション、Swagger についてです。

Swagger

swagger-logo

Swagger は SmartBear社 が提供しているOSSのひとつで、シンプルながら強力な RESTful API仕様表現のひとつです。世界最大規模のエコシステムに支えられ、ほぼすべてのモダンな言語、開発環境をサポートしています。Amazon API Gatewayでも使えたりするみたいですね。

Swaggerでは、リクエストやレスポンスの仕様はJSON Schemaで表現します。なので、実質Swagger独自の部分はそれ以外の付加情報 ( パス名やリクエストメソッド、ホスト名など ) の表現だけ、ということです。

Swaggerの目指すゴールは、 言語に依存しない、RESTful APIの標準インタフェースを定義することです。ソースコードやドキュメントを読んだりせずとも、最小の実装でリモートサービスを利用できる世界、すばらしいですね。

Open API Initiative

元々ひとつのデファクトスタンダードではあったのですが、今年2015年11月5日に Open API Initiative が設立されたことで、より標準化の色が濃くなりました。創立メンバーは3ScaleApigeeCapital OneGoogleIBMIntuitMicrosoftPayPalRestlet、そして、SwaggerのSmartBearです。

標準化への道が必ずしも上手くいくとは限りませんが、Swaggerを採用するひとつの根拠にはなるかもしれませんね。

〈標準化〉

ウェブサービスの標準化と聞くと「ウッ」となる方もいるかもしれません。WSDLを想起される方もいるかもしれませんね。実際、SOAPに対するWSDLのREST版と呼んで差し支えないと思います。Swagger以前にも WADL というRESTfulサービスの仕様標準があったりしましたが、みなさん感じられているように、あまり快く受け入れられてはいません。XML見るのも辛いし、まして手で書くなんて苦行だしね…。

Swaggerは先達の反省を踏まえ、極力シンプルなものを目指して策定されました。RESTに固執はせず、基本概念のみ踏襲し、WADLのように何でも表現出来るわけではないけれど、だいたいの共通設計パターンをシンプルに表現出来るようになっています ( 逆に特殊なことを表現しようとするとめんどくさい or 出来ない )。結果として、機械にとってはもちろん、人間にもある程度読みやすい表現になっています。

トップダウンではじめよう

ある程度大きな ( あるいは大きくなる予定の ) プロダクトであれば、サーバとクライアントの作業を並行で進めるため、最初にAPI仕様を策定することも多いと思います。Swaggerはボトムアップ ( 実装から仕様を生成するアプローチ ) とトップダウン ( まず仕様から作り始めるアプローチ ) の両方をサポートしますが、今回はトップダウンアプローチを採った場合について考えてみましょう。

先に言っておくと、以下で説明する内容のソースはすべてこちらに置いてあります。お手元に git cloneして、いじって動かしてみてください。

何はともあれ触ってみよう

Swaggerはオンラインで誰でも利用できるSwagger Editorを提供しています。オンラインだとデータがどこに送信されるやら保存されるやら心配で仕方ないかもしれませんが、実際はlocalStorageに保存されているようなので、あまり心配しなくてもよさそうです。

Swagger Editor では実際にYAMLを編集しながら結果をリアルタイムにプレビューすることが出来ます。エラーがあった場合はその場所の指摘も ( ある程度 ) してくれます。エディタとしての設定項目もあり、保存も出来るので、それなりには使えます。

Swagger のコアツールとして提供されているのでメンテナンス性も問題ありません。まあそれでもIssue が結構あるんだけど…。利用者も多いからある程度は仕方ありませんね。OSSなのでいざとなったらアナタが直せばいいんです。ちなみに Node.js アプリですよ。

より良い編集環境を目指して

やっぱり使い慣れたエディタで

Swagger Editorはよいツールだけれど、やはり何かを書くときはブラウザより、手に馴染んだエディタを使いたい…。

そんなワガママを言う人 ( 自分を含む ) のために、ちょっとしたツールを作りました。Swagger似非ライブプレビューツール1)ソースを見ると分かりますが、やっていることは単純な力技です。動くからいいよね!本体に組み込めばもっとスマートに実現出来ると思うので、もし、このプレビュー機能が公式に欲しい!という声が聞こえてきたら、本体への機能要望&PRをするかもしれません。です。Docker Hubにpushしているので、Dockerさえ使える環境ならpullするだけで使うことが出来ます。具体的には以下のようにしてください:

docker run --rm -it -v /path/to/spec-file:/runtime/dist/spec-files -p 3000:3000 yewton/swagger-pseudo-live-preview

これでDockerホストの3000番ポートにブラウザでアクセスすると、Swagger Editorのプレビュー部分のみが表示されます。対象の仕様ファイルは、ボリュームオプションで指定したディレクトリ以下にある swagger.yaml です ( 現状名前は固定です、ごめんなさい )。指定した仕様ファイルを編集して保存すると自動でプレビューが更新されるので、エラー等を確認しながら編集作業を進めることが出来るようになります。これなら、EmacsでもVimでもAtomでも、好きなエディタが使えますね。

動作イメージ

生のYAML書くのはちょっと大変

これでブラウザ上でモノを書くというストレスフルな行為からは解放されたわけですが、そもそもYAMLを書く事自体が辛いと感じる方もいるかもしれません ( XMLやJSONよりはマシですが )。制御構造が使えないため、なかなかDRYに書きづらいという問題もあります。YAMLにもアンカー/エイリアスを利用したグラフ構造を表現する方法がありますが、こんな Issueもあったりして、現状だと辛い状況です。なんらかのDSLが提供されていると嬉しいですね。

DSLといえばRuby、ということで (?)、RubyでSwagger仕様を表現するためのライブラリが非公式ではありますが存在します。それが、swagger-blocksです。非公式ですが、公式ページ上で紹介されているので、ちょっと安心ですね (?)。

RailsとかSinatraとかの特定のフレームワークに依存することもなく、基本的には本当に単純にSwaggerをRubyで表現出来るようにするだけで、後はその表現をSwagger JSON 文字列で出力できるだけ、本当にそれだけです。シンプルですが、それ故に何にでも応用が効きます。

というわけで応用を効かせて、より一層横着するための拡張を作りました。gemですが例によってDocker Hubに動く状態のイメージが置いてあるので、すぐに試せます。具体的には以下のようにしてください:

docker run --rm -v /path/to/gen:/gen \
                -v /path/to/blocks:/blocks \
                -v /path/to/descs:/descs \
                yewton/swagger-blocks_ext swagger:gen[/gen/swagger.yaml,/blocks/root,/descs]

これで、/blocks/root.rbrequireした結果のObjectSpaceに含まれるすべてのオブジェクトを探索2)今回は一回プロセス立ち上げてすぐに死ぬような処理なのでこんなことをしてしまいますが、例えばこのgemを何も工夫せずにRailsアプリケーションに組み込むなんてことはお勧めしません。swagger-blocks本体がやっているように、探索するクラスを明示的に指定するようにしないと、かなり重くなると思います。して、その結果から/gen/swagger.yamlを生成します ( /descsについては後述します )。最初に読み込むファイルさえ指定すれば、あとは全体がどのような構成になっていても関係ありません。自分のやりやすい形で仕様を管理できます。

swagger-blocksによる仕様表現の中身はただのRubyクラスなので、制御構造でもなんでも使いたい放題です。例えば、環境ごとに微妙に仕様が違うというような場合にも対応できます。より具体的に言えば、RAILS_ENVによって生成する仕様を変えたり出来るわけですね。両方の場合の仕様を生成してしまえば、一つのドキュメントに複数の場合をコトバで表現するよりも的確に伝えられます。

注意点として、Swagger::Blocksの各メソッドに渡すBlockはSwagger::Blocks::Nodeインスタンスに対してinstance_evalされるということに気をつけてください。Blockの評価コンテキストは呼び出したクラスではなく生成されたインスタンスです。何かの宣言を使い回したいといった場合は、ローカル変数として文字列をアサインして、Block内でevalする、といったことが必要になります3)ここはあまり自信が無いのでもっと簡単で筋のいい方法があれば知りたいです。ぜひ教えて下さい。

Markdownは単体で編集したいよね

YAMLをRubyで表現することでよりDRYに書けるようになりました。しかし、仕様に添える説明文等の長い文章はいずれにせよ書きづらいですね。まして、SwaggerでサポートされているGFM syntaxをRuby文字列の中で書くはちょっと辛そうです。

key :description, <<EOS
This is a sample server Petstore server.
[Learn about Swagger](http://swagger.io) or join the IRC channel `#swagger` on irc.freenode.net.
For this sample, you can use the api key `special-key` to test the authorization filters
EOS

そこで先程の拡張に、descriptionをMarkdownファイルで書けるようにする機能も提供しています。使い方の具体的なイメージは以下のとおりです:

key :description, md("description")

これで、先ほどのRakeタスクに渡した/descs以下の、info/description.mdを読み込んだ文字列を使うようになります。Markdownファイルの親ディレクトリは、それぞれのSwagger::Blocks::Nodeの具象クラス名から決まります。InfoNodeであればinfo/OperationNodeであればoperation/といった具合です。

運用について考える

ここまでである程度快適にSwaggerによるAPI仕様を書く準備を整えてきました。あとは、実際にこの仕様を運用していくだけです。せっかく書いた仕様を陳腐化させないために、どういった運用をすればいいか考えてみたいと思います。

仕様と実装は一蓮托生

仕様やドキュメントが陳腐化する最たる要因は、コードと別管理になっていることでしょう。そこで、実装と仕様を同じリポジトリにチェックインすることをおすすめします。そして、実装が変更される場合は常に、それより以前か同時に、仕様の変更もコミットされるように運用を徹底しましょう。仕様もコードと同じようにレビューすることが大切です。

PRごとにドキュメントを生成しよう

仕様のレビューをするにあたって、差分と合わせて変更後のドキュメントを確認したいという要求はもっともでしょう。幸い、Swaggerから静的なHTML仕様を生成することは簡単です。現状、いちばん見栄えのいい静的なドキュメントを生成できるのはbootprint-swaggerだと思います4)公式のジェネレータはMarkdownがレンダリングされないなどの問題がありました。また、Swagger2Markupというツールも試しましたが、MarkdownではなくAsciiDocとして解釈されてしまうため、表示に難があります。

生成されたページのイメージはこちらで確認できます。このサンプルプロジェクトではCircleCIを使って自動的にページを生成しています。実際の運用では、例えばPull Requestから自動的にHTMLページを作成して、適当なHTTPサーバで配信するような仕組みを検討しましょう。

モックサーバを提供しよう

せっかく作ったSwagger仕様書も、単なるドキュメント代わりではいずれ誰からも参照されずに陳腐化してしまうかもしれません。やはり、何かしらの形で継続的に使ってもらうことが大切な気がしますね。例えば、仕様書から生成したモックサーバをクライアントチームに提供し、テストしてもらう、なんていうシナリオを考えてみましょう。

SwaggerのAPI仕様書があれば簡単にモックレスポンスを返すサーバとテスト用のページを自動生成出来ます。これには、公式で提供されている swagger-codegenを利用します。説明にあるようにDockerイメージをビルドして使うことも出来ますし、ビルド済みのイメージを yewton/swagger-codegenからpullすることも出来ます 5)Swaggerはこれ以外のプロジェクトでもDockerfileはあるのにDocker Hubに無いことがあったりします。置いてもらえるように個人的にリクエストしてはいます。

生成されたサーバのイメージはこちらで確認できます ( Herokuで無料の範囲でホスティングしているので、落ちていることもあるかもしれませんがご容赦ください )。ここでは、実際にWeb UI上からHTTPリクエストを送って挙動を確認できます。GET /users/{name}なんかが分かりやすいと思います。

仕様テストの自動化を目指す

いくら仕様を書いても実装が伴っていなかったら意味がありません。そこで当然の要求として仕様を満たすかどうかを自動テストしたい、という気持ちになります。しかし、テストの仕組みは言語やフレームワークにかなり依存するところなので汎用的な解決は難しいです。状態が絡むAPIリクエストでは尚更です。

言語やフレームワークごとのアプローチとして、例えばRailsではapivoreというgemが利用できます。Swagger JSONを読み込んで仕様を満たすかどうかをテストしてくれるというシロモノです。自分の利用する言語やフレームワーク向けにこういった既存の解決方法が無いか、まずは探してみましょう。

Swaggerは自動コード生成の仕組みを拡張性のある方法で提供してくれているので、自分で作るという方法もあります。基本的にはテンプレートファイルを用意するだけなので、作るべきものが分かっているならば実装自体はそれほど難しくはないと思います。要Java7ですが。

おわりに

Swaggerについて色々書いてきましたが、決して銀の弾丸ではありませんね。ここまで読んでいただいた方は気づいていると思いますが、作業が劇的に楽になったりはしません。

ただ、考えることが減るのは間違いないです。API仕様の管理をどうするかにイチから頭を悩ませるよりも、ある程度世間的に認められている Swagger-way に則った管理方法を採用するというのは、検討すべき選択肢の一つだと思います。どんなものであれ、共通の拠り所があるというのは、チーム開発をする上では精神の安定にも繋がりますよね。

日々APIドキュメントの管理に悩んでいるそこのアナタ。是非、Swagger を採用し、そして共に悩みましょう。

脚注

脚注
1 ソースを見ると分かりますが、やっていることは単純な力技です。動くからいいよね!本体に組み込めばもっとスマートに実現出来ると思うので、もし、このプレビュー機能が公式に欲しい!という声が聞こえてきたら、本体への機能要望&PRをするかもしれません。
2 今回は一回プロセス立ち上げてすぐに死ぬような処理なのでこんなことをしてしまいますが、例えばこのgemを何も工夫せずにRailsアプリケーションに組み込むなんてことはお勧めしません。swagger-blocks本体がやっているように、探索するクラスを明示的に指定するようにしないと、かなり重くなると思います。
3 ここはあまり自信が無いのでもっと簡単で筋のいい方法があれば知りたいです。ぜひ教えて下さい。
4 公式のジェネレータはMarkdownがレンダリングされないなどの問題がありました。また、Swagger2Markupというツールも試しましたが、MarkdownではなくAsciiDocとして解釈されてしまうため、表示に難があります。
5 Swaggerはこれ以外のプロジェクトでもDockerfileはあるのにDocker Hubに無いことがあったりします。置いてもらえるように個人的にリクエストしてはいます。