misc.tech.notes

主に技術的な雑記的な

SwaggerのAPI定義をRuby DSLで書いてAPI Gatewayにデプロイできる「rapis」というgemを作った

背景

API Gatewayはとにかく設定が面倒である。Serverlessの概念はシステムをシンプルに保てるとても素晴らしいものだが、それを実現するための設定が複雑になって、運用に支障が出たりするのは厳しい。*1

「柔軟なリソース定義を担保しようと思ったらまあ仕方ないよね」みたいな気持ちもあるが、やっぱり面倒なものは面倒だ。

俺達(俺)は書いたコードをサクッと上げてサクッとAPI公開したいだけなんだ!!1

書いたコードをサクッとLambdaで実行出来るようにするツールはすでにある。

github.com

問題はAPI Gatewayの方。現状でAPI定義をする上で一番マシだと思えるのがSwaggerのインポートによる方法だが、それでも、x-amazon-apigateway-integrationみたいなAPI Gatewayの独自要素*2に色々突っ込む必要があったりして、まだまだ十分とはいえないし、冗長な表現も多い。

先人の知恵を借りてみる

同じように、非常に生のJSONで扱うのがツラい某CloudFormationというものがあるが、これをRuby DSL*3でかけるようにすることで劇的(当社比)に見通しが良くなり、冗長な表現を避けることができるようになるツールとして、kumogataがある。ただ変換を行うだけではなく、実際にデプロイを行ったりするための運用に便利な機能も付いていて、最近もv2になってChange Sets(プレビュー的な機能)に対応したりプラグイン機構を導入したりして進化してる。本当にいつもお世話になってます。

github.com

じゃあ、Swagger定義も同じようにRuby DSL化すれば楽になるのでは?

というのが今回の趣旨。併せて、運用に便利なdiffを取る機能や定義のデプロイ等も含めてツール化してみた。

github.com

使い方

とりあえずインストールはgemなのでこれです。

gem install rapis

詳しくはREADMEを読んでいただければと思いますが、ざっくり主なコマンドの解説だけ。

API Gatewayの仕様上の注意事項としては、Swagger定義のインポートはAPIのベースとなる設定に対して行うが、実際にAPIとして公開されるステージにデプロイ(反映)する作業は別のステップとなっており、エクスポートについてはベースの定義を出力することはできず、デプロイ済みのステージから抜くことしかできないということ(デプロイはまあ良いとしても、大本の設定は抜けるようにしてほしい。。。)*4

rapis create -n <API名>
  • APIとそれらが持つステージを一覧表示
rapis list
  • 既存API定義をエクスポート
rapis export -r <APIID> -s <Stage>
  • API Gatewayに保存されている定義とローカルの定義のdiffを取る
rapis diff -r <APIのID> -s <Stage名>
rapis apply -r <APIのID>
  • 最新の定義をAPI Gatewayのステージにデプロイ(反映)
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を使ってます。

github.com

ということで、偉大な先人へ感謝をしつつ締めたいと思います。よろしくお願いします。

*1:普段、こんなことを言ったりしてるけど、これはServerless自体を否定してるんじゃなくて「サーバのお守りをしたくないだけならPaaSで良いんじゃないの?Serverlessはそこがコアじゃないよね?」って意味である

*2:参考→ Swagger に対する API Gateway 拡張 - Amazon API Gateway

*3:他のフォーマットもサポートしてる

*4:そもそもAPI Gatewayのデプロイの概念がよく分からんという方はコチラを→Amazon API Gateway での API のデプロイ - Amazon API Gateway