サーバーレスアーキテクチャ Advent Calendar 2016 の 19 日目の記事です。
サーバーレスアーキテクチャの構成は、運用費用の面でメリットが出そうな一方で、単純に乗っかると特定のクラウドベンダーに強く依存してしまいます。できることなら、もし AWS のAPI Gateway や Lambda がいきなりサービスを終了してもどうにかなってほしいところです。そこで、 Swagger の定義にもとづいて複数種類の構成で運用できるように Swagger Code Builder というツールを作成してみました。
Swagger CodeGen と似ていますが、プロジェクトの構成が大きく異なったりするので、 swagger-parser だけ流用して作り直してみました。
目指す構成
通常構成
\o/ +-----+ +-----------+
| -------->|Nginx|-->|File System|
/ \ +-----+ +-----------+
User |
|
|
| +---------------+ +----+ +-----+
+->|Spark Framework|-->|Java|-->|MySQL|
+---------------+ +----+ +-----+
Spark Framework は、 Ruby の Sinatra や Python の Flask に似た Java のマイクロフレームワークです。 Spring Boot でも良かったのですが、軽そうな方を選択しました(後述しますが失敗だったかもしれません)。Spark Framework の利用については 過去の記事 も参考にしてみてください。
サーバーレス構成
\o/ +----------+ +--+
| -------->|CloudFront|-->|S3|
/ \ +----------+ +--+
User |
v
+-----------+ +------+ +---------+
|API Gateway|-->|Lambda|-->|Dynamo DB|
+-----------+ +------+ +---------+
認証は Cognito を使いたいとかはいろいろあると思いますが、一旦この構成に絞って考えます。 Lambda なのに Java というのは今の会社に Java を使える人が多いからです。 JavaScript 使いが増えたら node.js, Express の方が良さそうです。個人的には Python, Flask の方が好きですが、この分野には Zappa というものがあるらしいです。巷で噂の Serverless Framework とは、両立を目指している点で違います。
戦略
Controller 層
Swagger の定義からそれぞれの構成のためのコードを自動生成して、呼び出し側に依存しないコードを書くことができるようにします。開発者は規約に従った POJO を受け取って返すコードを書き、呼出し側では自動生成されたコードがそれぞれの構成に合わせて変換します。
# 規約に沿ったロジックの雛形
./swagger-code-builder \
--structure java-services \
--api-spec-path swagger-spec.yaml
# 通常構成の雛形
./swagger-code-builder \
--structure sparkjava \
--api-spec-path swagger-spec.yaml
# Serverless 構成の雛形
./swagger-code-builder \
--structure java-awsserverless \
--api-spec-path swagger-spec.yaml \
--aws-region ap-northeast-1 \
--aws-account-id [Account ID]
ただし、ライブラリやオブジェクトの依存関係の解決はあきらめて個別で書くことにしています。 Gradle の dependencies や Spark Framework で利用している Guice の Module、 Lambda のコンストラクタあたりは手で書きます。 DI をうまく使えばこのあたりは簡略化できるのではと思っているのですが、いまいちまだうまくまとまっていません。
また、 Spark Framework の Request オブジェクトを変換するコードを自動生成していますが、変換先のオブジェクトに自動生成されたもの以外を使いたい場合は、この変換も手直しの必要があります。また、 Swagger CodeGen のような POJO の自動生成はまだ中途半端だったりします。このあたりも永続化層との連携が必要だと思い、あまり手をつけられていません。
API Gateway の設定に関しては、 Vendor Extension を自動生成するようにしましたので、 Lambda のアップロード、 import-rest-api 、権限の付与のスクリプトを走らせるだけで完了します。生成時のオプションで全体に CORS や API Key を設定することもできます。あとは AWS の画面上で数回クリックするだけでデプロイが完了します。
Controller 以前のリクエストを受け付ける部分 (Nginx のリバースプロキシ、Cloud Front の Origin と Behavior) については、 Kong あたりも見つつ、また別な方法を考えるべきかなと思います。
永続化層
Model をロジック (Service Layer) と具体的なデータの読み書きを行う部分 (Data Access Object Layer) に分け、 Dependency Injection で切り替えます。DAO については単純計算で 2 倍のコードを書いてるので、このあたりはまた別の問題解決が必要そうです。
DynamoDB を JDBC で操作するというのはプロプライエタリではいくつかあるようですが、オープンソースでは出てきていないように思えます。 RDB←→DynamoDB は List や Map, Set の扱い、DynamoDB←→Cassandraは、型の定義の対応が面倒そうです。本体に取り込まれてはいませんが、 Spring Data DynamoDB の存在を考えると Spring の方が良かったかもしれません。
最後に
Swagger Code Builder はまだまだ課題の多いツールですが、実際に小さな社内ツールの開発で利用しています。特に API Gateway のインテグレーションのあたりを画面でぽちぽち操作しなくても良いのは、かなりありがたいのではないかなと思います。ベンダーロックインを回避したサーバーレスアーキテクチャの開発を実現したい方は是非試していただけると嬉しいです。感想や Issue をお待ちしております。