SwaggerのAPI定義をRuby DSLで書いてAPI Gatewayにデプロイできる「rapis」というgemを作った
背景
API Gatewayはとにかく設定が面倒である。Serverlessの概念はシステムをシンプルに保てるとても素晴らしいものだが、それを実現するための設定が複雑になって、運用に支障が出たりするのは厳しい。*1
「柔軟なリソース定義を担保しようと思ったらまあ仕方ないよね」みたいな気持ちもあるが、やっぱり面倒なものは面倒だ。
便利だけど、1リソースでモックのレスポンス返すだけのAPI定義で50行超か・・・ / “AWS CloudFormation が Amazon API Gateway をサポートしたので使ってみた | Developers.IO” https://t.co/vgKAk7BVHY
— Masashi Terui (@marcy_terui) 2016年5月10日
Terraformですらこの記述量なのか。。。 / “TerraformでAPI Gatewway - @ijin” https://t.co/KPf9jV4wzl
— Masashi Terui (@marcy_terui) 2016年5月2日
Swaggerですら面倒くさい。1APIにつき3行くらいで記述したい。
— Masashi Terui (@marcy_terui) 2016年5月2日
俺達(俺)は書いたコードをサクッと上げてサクッとAPI公開したいだけなんだ!!1
書いたコードをサクッとLambdaで実行出来るようにするツールはすでにある。
問題はAPI Gatewayの方。現状でAPI定義をする上で一番マシだと思えるのがSwaggerのインポートによる方法だが、それでも、x-amazon-apigateway-integration
みたいなAPI Gatewayの独自要素*2に色々突っ込む必要があったりして、まだまだ十分とはいえないし、冗長な表現も多い。
先人の知恵を借りてみる
同じように、非常に生のJSONで扱うのがツラい某CloudFormation
というものがあるが、これをRuby DSL*3でかけるようにすることで劇的(当社比)に見通しが良くなり、冗長な表現を避けることができるようになるツールとして、kumogata
がある。ただ変換を行うだけではなく、実際にデプロイを行ったりするための運用に便利な機能も付いていて、最近もv2になってChange Sets(プレビュー的な機能)に対応したりプラグイン機構を導入したりして進化してる。本当にいつもお世話になってます。
じゃあ、Swagger定義も同じようにRuby DSL化すれば楽になるのでは?
というのが今回の趣旨。併せて、運用に便利なdiffを取る機能や定義のデプロイ等も含めてツール化してみた。
使い方
とりあえずインストールはgemなのでこれです。
gem install rapis
詳しくはREADMEを読んでいただければと思いますが、ざっくり主なコマンドの解説だけ。
API Gatewayの仕様上の注意事項としては、Swagger定義のインポートはAPIのベースとなる設定に対して行うが、実際にAPIとして公開されるステージにデプロイ(反映)する作業は別のステップとなっており、エクスポートについてはベースの定義を出力することはできず、デプロイ済みのステージから抜くことしかできないということ(デプロイはまあ良いとしても、大本の設定は抜けるようにしてほしい。。。)*4
- API作成
rapis create -n <API名>
- APIとそれらが持つステージを一覧表示
rapis list
- 既存API定義をエクスポート
rapis export -r <APIのID> -s <Stage名>
rapis diff -r <APIのID> -s <Stage名>
rapis apply -r <APIのID>
rapis deploy -r <APIのID> -s <Stage名>
記述について
今のところ、機械的にRuby DSLに置き換えるとシンタックスエラーになるような部分以外は特に特別な記述に置き換えるようなことはしていないが、これから使っていく中で汎用的に使えるような省略表現やメソッドなんかを入れていくことで、もっと楽にかけるようにしていきたい。目指せ1APIあたり3行!
ちなみ現状(2016-06-02)ではにこれが
{ "swagger": "2.0", "info": { "version": "2016-05-27T17:07:04Z", "title": "PetStore" }, "host": "p0dvujrb13.execute-api.ap-northeast-1.amazonaws.com", "basePath": "/test", "schemes": [ "https" ], "paths": { "/": { "get": { "consumes": [ "application/json" ], "produces": [ "text/html" ], "responses": { "200": { "description": "200 response", "headers": { "Content-Type": { "type": "string" } } } }, "x-amazon-apigateway-integration": { "responses": { "default": { "statusCode": "200", "responseParameters": { "method.response.header.Content-Type": "'text/html'" }, "responseTemplates": { "text/html": "<html></html>" } } }, "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, "passthroughBehavior": "when_no_match", "type": "mock" } } } }, "definitions": { "Empty": { "type": "object" } } }
こんな感じになる
rest_api do swagger "2.0" info do version "2016-05-27T17:07:04Z" title "PetStore" end host "p0dvujrb13.execute-api.ap-northeast-1.amazonaws.com" basePath "/test" schemes ["https"] paths do path "/" do get do consumes ["application/json"] produces ["text/html"] responses do code 200 do description "200 response" headers( {"Content-Type"=>{"type"=>"string"}}) end end amazon_apigateway_integration do responses do default do statusCode 200 responseParameters( {"method.response.header.Content-Type"=>"'text/html'"}) responseTemplates( {"text/html"=> "<html></html>"}) end end requestTemplates( {"application/json"=>"{\"statusCode\": 200}"}) passthroughBehavior "when_no_match" type "mock" end end end end definitions do Empty do type "object" end end end
実装について
基本的にはAWS APIをゴニョゴニョしているという形なんですが、一番面倒そうなAPI Gateway上ではJSONとして扱われるSwagger定義をRuby DSLと相互変換するのは、kumogata
を含め、AWS等の設定をコード化するCodenize.toolsの作者であるid:winebarrelさんが公開しているdslh
というgemを使ってます。
ということで、偉大な先人へ感謝をしつつ締めたいと思います。よろしくお願いします。
*1:普段、こんなことを言ったりしてるけど、これはServerless自体を否定してるんじゃなくて「サーバのお守りをしたくないだけならPaaSで良いんじゃないの?Serverlessはそこがコアじゃないよね?」って意味である
*2:参考→ Swagger に対する API Gateway 拡張 - Amazon API Gateway
*3:他のフォーマットもサポートしてる
*4:そもそもAPI Gatewayのデプロイの概念がよく分からんという方はコチラを→Amazon API Gateway での API のデプロイ - Amazon API Gateway