Java
AWS
lambda

ゼロからはじめるServerless Java Container

日頃AWSやその他クラウドサービスを使ってインテグレーションしていく中で、 https://github.com/awslabs を定期的にウォッチしているのですが、その中で Serverless Java Container が気になったので試してみました。

https://github.com/awslabs/aws-serverless-java-container

Serverless Java Container is 何?

簡単に言うと、API GatewayとLambdaを使ったサーバレスアプリケーションを Jersey, Spark, Spring Frameworkといったフレームワークを使って作るためのライブラリです。

このライブラリを利用することで、「つなぎ」となる必要最低限のコードを書いてあげさえすれば、あとはいつも通り、フレームワークの流儀に沿ってアプリケーションを実装していくだけで、Lambda上で動くハンドラができあがる、というシロモノです。
絶対不可欠なライブラリではありませんが、あると便利なので、一考の価値はあると思います。


で、このAdvent Calendarにエントリした時には気づいていなかったのですが、AWSの中の人がこのライブラリについて詳しく紹介しているスライド・動画があることに気づきました :neutral_face:
「AWS Dev Day Tokyo 2017」で登壇された時のものみたいですね。

というわけで、このライブラリについての詳細な解説については上記を参考にしてもらうとして、今回のエントリでは、以下のような違いを出しつつ、このライブラリを使ってアプリケーションを作ってみることにします。

  • SAMやCloudFormationなどを使わずに、ゼロから構築してみる
  • よくあるPetStoreアプリケーションではなく、Hello, worldアプリケーションを作る
  • ビルドにはMavenではなくGradleを使う

試してみる

アプリケーションの雛形を作る

Spring Initialzrから新規にGradleプロジェクトを作っていきます。
必要な入力項目は以下のとおり。

項目 入力値
Group com.example
Artifact demo
Dependencies DevTools

「Generate Project」を押すと、プロジェクトの雛形がZipファイルで作られるので、展開後のディレクトリをワークスペースとします。

依存関係にServerless Java Containerを追加

今回の主役となるライブラリを追加します。執筆時点での最新バージョンは0.8のようでした。

build.gradle
    compile('com.amazonaws.serverless:aws-serverless-java-container-spring:0.8')

READMEにしたがってConfigとLambdaHandlerを作成

Serverless Java ContainerリポジトリのREADMEの「Spring support」のセクションを参考にして、アプリケーションとLambdaの「つなぎ」となる部分のコードを作っていきます。

まずはコンフィグ。

com.example.demo.AppConfig.java
@Configuration
@ComponentScan("com.example.demo")
public class AppConfig {
}

続いてハンドラ。

com.example.demo.LambdaHandler.java
public class LambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyResponse> {

    private static class Singleton {

        static SpringLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler = instance();

        static SpringLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> instance() {
            try {
                return SpringLambdaContainerHandler.getAwsProxyHandler(AppConfig.class);
            } catch (final ContainerInitializationException e) {
                throw new RuntimeException("Cannot get Spring Lambda Handler", e);
            }
        }
    }

    @Override
    public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) {
        return Singleton.handler.proxy(awsProxyRequest, context);
    }
}

LambdaHandler の方は、READMEの通りに実装するとコンパイルエラーになってしまうので、少し修正しました。
SpringLambdaContainerHandler.getAwsProxyHandler がチェック例外 ContainerInitializationException を投げるので、そのままフィールドとして初期化できないんですよね…。

コントローラを作る

準備が終わったので、アプリケーション本体を作っていきます。
とは言え、今回は簡単なHello, Worldアプリケーションなので、これだけです。

com.example.demo.controller.DemoController.java
@RestController
public class DemoController {

    @GetMapping("/hello")
    public String hello(@RequestParam(required = false) Optional<String> message) {
        return "Hello, " + message.orElse("world") + "!";
    }
}

普通のSpringアプリケーションのコードですね。

Lambda用のパッケージの作成

下記ドキュメントを参考に、Lambda用のパッケージを作ります。
http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/create-deployment-pkg-zip-java.html

実は、普段Lambda関数を作る時はランタイムとしてNode.jsを使うことが多く、Javaランタイムを使うのは始めてでした。
build.gradle に以下を追加すればOKです。

build.gradle
task buildZip(type: Zip) {
    from compileJava
    from processResources
    into('lib') {
        from configurations.runtime
    }
}

build.dependsOn buildZip

Lambda関数を作ってパッケージをアップロード

Lambda関数を作って、パッケージをアップロードします。

ここで関数をテストする場合、イベントテンプレートとして「API Gateway AWS Proxy」を選択すればよいです。
選択して出てくるテンプレート中で、実装したアプリケーションに合わせて

  • "queryStringParameters""message": "好きな文字列"
  • "httpMethod""GET"
  • "path""/hello"

としてそれぞれ変更してください(下図)。

lambda.png

API Gatewayと連携させる

仕上げに、APIを作り、デプロイします。

今回は、ルートの直下にプロキシリソースを作ってしまいます(プロキシリソースの呼び出し先のLambda関数は、上記で作成したLambda関数を指定してください)

apigw2.png

テストの際は、クエリ文字列として message=好きな文字列(今回はServerless) を指定し、GETメソッドを呼び出すと、Springのコントローラが発火し、

Hello, Serverless!!

がレスポンスとして返ってくることが確認できます。

また、APIをデプロイした後は、curl コマンドなどでアクセスしても、きちんとレスポンスが取得できることが確認します。

$ curl "https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello?message=Serverless"

まとめ

繰り返しになってしまいますが、Serverless Java Containerライブラリ、簡単に言うと、API GatewayのLambdaプロキシ利用時に、普通のSpringアプリケーションとしてハンドラを実装できるようにするためのラッパー、という感じだと思います。
API Gatewayと組み合わせて力を発揮するライブラリですね。
最初は、Lambda上でSpringを使う時の足回りを面倒見てくれるライブラリかな?と思ったのですが、ちょっとイメージしていたものとはズレていました…。

現実的には、RDBに依存したアプリケーションの場合のコネクションの話1など、既存のWebアプリケーションがそのまま載せ替えられるか?というと検討ポイントはありそうですが、使い慣れたフレームワークをサーバレスアプリケーション化する場合には、こういったライブラリの活用もよいのかなーと思いました。

おまけ:起動時間など

LambdaのランタイムとしてJavaを使っている場合、起動時間も少し気になるところだと思うので、メモしておきます。

ちなみに、メモリの設定は512MBです。

正確なベンチマークは取得できていませんが、今回のアプリケーションで確認した範囲においては、10ミリ秒前後の処理時間で済むみたいです(コンテナが起動して、ApplicationContextが初期化済みになっている場合ですが)。