serverless-import-swaggerの紹介

新規サービス開発のプロジェクトの中で、Serverless FrameworkSwagger形式のAPI定義をインポートする、AKIRA-MIYAKE/serverless-import-swaggerを開発したので、その紹介を行います。

開発の動機

開発中のサービスは、Angular2のSPAをS3に配置してCloudFrontで配信、データ取得などのためのAPIAWS Lambda + API Gatewayで実装、というサーバレスアーキテクチャを採用しています。
フロントエンドとバックエンドの開発を並行してスムーズに行うことができるように、Swagger形式でAPIを定義しそれに基づいて実装するようにしています。

AWS LambdaへのデプロイやAPI Gatewayとの紐付けをを手作業で行うのは考えられないので、Serverless Frameworkを用いてイベントや権限を定義、CIでの自動デプロイの環境を整えています。
Serverlessでは、このような感じYAML形式で関数を定義します。
基本的にSwaggerで定義したAPIのメソッドがLambda関数に対応します。Swaggerの定義からServerlessの関数定義を作れたら嬉しいですよね。
でもそんなプラグインはないらしい…ということで開発しました。

開発の上での考え方

Serverlessにはプラグインを追加できる仕組みがありますが、独立したCLIツールとしています。作り始めた時点では、Serverlessのバージョンが0.5から1.0に変わったすぐあとぐらいで、変更が生じる可能性が高かったこと、ビルドやデプロイのプロセスに処理を追加するわけではなかったためです。
他には以下の点を実現できるようにしています。

  • providerなどの共通項目を別途定義して読み込めること
  • Swaggerのタグを用いて任意にサービスを分割できること
  • 生成されるサービスにプレフィックスを付与できること
  • 関数名がAPIに基づいて自動生成されること
  • 手動で追加した項目が再度実行時に消えないこと

基本的な使い方

  1. npm install serverless-import-swaggerでインストールします。
  2. Swagger定義ファイルの取り込む対象のメソッドにタグを追加します。
  3. Swagger定義ファイルをプロジェクトに配置します。
  4. providerなどの共通項目を定義したYAMLファイルを用意します。
  5. sisコマンドを実行することでインポートが実行されます。Serverlessのサービスが存在しない場合は自動的に作成されます。

オプションは以下の通りです。

# 共通オプション
-i, --input <path>             インポートするSwagger.yamlのファイルを指定します。 (デフォルトは "./swagger.ya?ml" です)
-c, --common <path>            共通項目を定義したYAMLファイルを指定します。 (デフォルトは "./serverless.common.ya?ml" です)
-o, --out-dir <path>           Sserveressサービスの出力先を指定します。 (デフォルトは "./" です)
-f, --force                    このオプションを追加すると、新しい出力で既存の内容を完全に上書きします。

# タグやサービスのプレフィックスに関するオプション
-A, --api-prefix <prefix>      Swaggerのタグにつけるプレフィックスです。 (デフォルトは "sls" です)
-S, --service-prefix <prefix>  出力されるサービスに付与されるプレフィックスです。 (デフォルトはなしです)

# ベースパスの扱いに関するオプション
-B, --base-path                このオプションを追加すると、httpイベントのパスからベースパスが取り除かれます。

# CORSに関するオプション
-C, --cors                     このオプションを追加すると、httpイベントに`cors=true`が追加されます。
-O, --options-method           このオプションを追加すると、GETメソッドの関数には`cors=true`がそれ以外のメソッドを含むパスにはoptionsメソッドの関数が追加されます。

サービス分割時のベースパスについて

デフォルトでは、Swaggerのタグに基づいてサービスが分割され、APIのパスがそのままインポートされます。
CloudFormationの制約のため、API数が一定規模を超える場合サービスを分割する必要がありますが、API Gatewayでカスタムドメインに複数のAPIに紐づける場合、ベースパスの指定が必要となります。
そのため、Swaggerで/foo/barと定義したAPIがカスタムドメインからアクセスすると/{base-path}/foo/barとなってしまい、定義と異なってしまいます。

-B, --base-pathオプションを追加することで、Serverlessのイベントのパスからベースパスを除去するようになります。
先ほどの例だと、serverless.ymlでは/fooが取り除かれて/barとなり、API Gateway/fooのベースパスでマッピングすることで、Swaggerの定義と一致させることができます。
ただし、この機能を使うと、ベースパスを基準にしたサービスの分割を強制されることとなります。

CORSについて

-C, --corsオプションをつけるとAPI GatewayのデフォルトのCORSのためのOPTIONSメソッドが用意されます。単純なAPIアクセスを行う場合であれば、このオプションだけで問題はないと思います。
ただし、Cookieや独自ヘッダを用いた通信を行う場合、Access-Control-Allow-Originワイルドカードを指定することができないため、対象のレスポンスを返す関数だけでなく、preflight requestで呼び出されるOPTIONSも独自に定義する必要があります。
-O, --options-methodオプションを付与すると、SwaggerのAPIのパスの中にGET以外のメソッドが定義されている場合、自動でそのパスのOPTIONSメソッドに対応する関数を追加します。
追加された関数でOPTIONSのヘッダを独自に定義することで、Cookieや独自ヘッダを用いたCORSを実現することができます。


ベースパスやCORSの部分は現在のプロジェクトやAWSの制約によって用意したものなので、もう少しうまい解決方法があるかもしれませんが…。
開発中のサービスがリリースされ、落ち着いた頃に整理ができればと思います。