はじめに
これまで、AWS Lambdaを主にPython、時々、Node.jsで作ってきましたが、気分転換にJavaでの開発方法を調べたのでまとめます。
今回は、Javaで書いたLambda関数のソースコードをGradleでビルドしてAWS SAMでデプロイしてみました。
Eclipseでの開発用に AWS Toolkit for Eclipse も提供されていますが、普段はVimで開発してCLIでデプロイしているので、そちらに合わせます。
検証環境
- OS: macOS High Sierra 10.13.6
- aws-cli: 1.15.38
- aws-sam: 0.11.0
- Java 1.8.0_221
- JDK: OpenJDK 1.8.0_212 (Amazon Corretto 8.212)
- Gradle 5.5.1
JavaでLambdaを開発する上でのポイント
ランタイムはJava 8のみ
下の公式ドキュメントにあるように、提供されているJavaのランタイムはJava 8
(JDK 8
)のみです。
それ以外のバージョンを使用する場合は、サポートされるまで待つかカスタムランタイムを使う必要があります。
Java による Lambda 関数のビルド – AWS Lambda
zipファイルかjarファイルにしてでデプロイ
Javaで書かれたアプリケーションを、zipファイルまたはスタンドアロンjarにパッケージしてデプロイします。
公式ドキュメントではMavenを使ってスタンドアロンjarに、Gradleを使ってzipファイルにしてデプロイする方法が紹介されてます。
今回は、Gradleでzipファイルにパッケージングしてデプロイする方法で試しました。
Java の AWS Lambda デプロイパッケージ – AWS Lambda
ハンドラー関数のリクエストの受け方・返し方が3種類
ハンドラー関数のリクエストの受け取り方、そして、レスポンスの返し方に、以下のように複数の方式があります。
- Java のシンプルな型
- POJO (Plain Old Java Object) 型
- ストリーム型
それぞれでハンドラー関数の書き方が変わります。つまり、ハンドラー関数の書き方に3種類あるということです。
「このイベントトリガーを使うならこのタイプ」というような決め方というよりも、どういったデータが送られてくるかという視点で使い分けるようです。
この状況にはこれという明確なものはないようなので、実際にJavaでLambda関数を開発する際はどの方式にするか要検討です。
今回は、「POJO型」を採用しました。
ハンドラーの入出力タイプ (Java) – AWS Lambda
今回作ったもの
Lambda関数のトリガーとして API Gateway
を使った、簡単なWebアプリケーションを作成しました。
POSTリクエストを送ると、”Hello”と返してくるだけのシンプルなAPIです。
サンプルコード
作成したコードはここに置いてます。
https://github.com/mmclsntr/awslambda-javagradle
プロジェクト構成
. ├── build/ │ ├── distributions/ │ │ └── awslambda-javagradle.zip # ビルドで生成されるデプロイパッケージ │ └── ... ├── build.gradle # Gradleビルド設定ファイル ├── src/main/java/awslambda/javagradle │ ├── Greeting.java # アプリの中核となる部分 │ ├── Handler.java # Lambdaのハンドラ関数を格納 │ └── Response.java # Lambdaのレスポンスを整形 └── template.yml # CloudFormationテンプレートファイル └── その他Gradle用ファイル
コーディング
ハンドラクラス
Handler.java
package awslambda.javagradle; import java.util.Collections; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; public class Handler implements RequestHandler<Map<String, Object>, Response> { private static final Logger LOG = Logger.getLogger(Handler.class.getName()); @Override public Response handleRequest(Map<String, Object> input, Context context) { LOG.info("received: " + input); LOG.setLevel(Level.INFO); Greeting greetingBody = new Greeting("Hello"); return Response.builder() .setStatusCode(200) .setObjectBody(greetingBody) .build(); } }
レスポンスクラス
Response.java
package awslambda.javagradle; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Collections; import java.util.Map; import org.apache.log4j.Logger; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class Response { private final int statusCode; private final String body; private final Map<String, String> headers; private final boolean isBase64Encoded; public Response(int statusCode, String body, Map<String, String> headers, boolean isBase64Encoded) { this.statusCode = statusCode; this.body = body; this.headers = headers; this.isBase64Encoded = isBase64Encoded; } ... }
あいさつクラス
サンプルとしてHelloと返してくれる超シンプルなアプリケーションを作ります。
Greeting.java
package awslambda.javagradle; public class Greeting { private String greetings; public Greeting(String greetings) { this.greetings = greetings; } public String getGreetings() { return greetings; } public void setGreetings(String greetings) { this.greetings = greetings; } }
CloudFormationテンプレート
template.yml
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > AWS Lambda Java with Gradle Globals: Function: Timeout: 20 Resources: PostGreetingFunction: Type: AWS::Serverless::Function Properties: CodeUri: build/distributions/awslambda-javagradle.zip Handler: awslambda.javagradle.Handler::handleRequest Runtime: java8 Events: GetOrder: Type: Api Properties: Path: / Method: post Outputs: ApiEndpoint: Description: "API Gateway endpoint URL for Prod stage" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" PostGreetingFunction: Description: "PostGreeting Lambda Function ARN" Value: !GetAtt PostGreetingFunction.Arn
デプロイ
ビルド設定ファイル作成
build.gradle
apply plugin: 'java' repositories { mavenCentral() } sourceCompatibility = 1.8 targetCompatibility = 1.8 dependencies { compile ( 'com.amazonaws:aws-lambda-java-core:1.1.0', 'com.amazonaws:aws-lambda-java-log4j:1.0.0', 'com.amazonaws:aws-lambda-java-events:1.1.0', 'com.fasterxml.jackson.core:jackson-core:2.8.5', 'com.fasterxml.jackson.core:jackson-databind:2.8.5', 'com.fasterxml.jackson.core:jackson-annotations:2.8.5' ) } // Task for building the zip file for upload task buildZip(type: Zip) { from compileJava from processResources into('lib') { from configurations.runtime } } build.dependsOn buildZip
ビルド
Gradle コマンドでビルドします。
gradle build
AWS SAMでデプロイ
デプロイ先S3バケット作成
aws s3 mb s3://<デプロイ先S3バケット> --aws-profile=<AWSプロファイル>
パッケージ
sam package
で、上で作成したS3バケットへ実行ファイルをアップロード & デプロイ用テンプレートファイルを生成します。
sam package \ --s3-bucket <デプロイ先S3バケット名> \ --s3-prefix <デプロイ先S3フォルダ名(プレフィクス) ※任意> \ --output-template-file output.yml \ --profile <AWSプロファイル>
アウトプットとして、 output.yml
ファイルが作られます。
デプロイ
sam deploy
で、LambdaとAPI Gatewayをデプロイします。
sam deploy \ --template-file output.yml \ --stack-name awslambda-javagradle-greeting \ --capabilities CAPABILITY_IAM \ --profile <AWSプロファイル>
確認
AWS上にLambda関数とAPI Gatewayが作られたので、エンドポイントにPOSTリクエストを投げて見ます。
リクエスト
curl -X POST https://xxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/
レスポンス
{"greetings":"Hello"}
所感
私自身、普段はJavaを使わないのでちょっと時間がかかりましたが、簡単に作れたなという印象です。Pythonなどのスクリプト言語で作るよりも当然ながらコード量は増えますが、厳格なサーバーレス開発ができることは魅力的です。
ハンドラー関数の書き方に複数種類あるといったJavaで書く上での特有の仕様が、今回だけでは把握しきれませんでした。実際に使う際は、その辺りも考慮しながら詳細設計したいと思います。
また、噂通り、初期起動が遅いです (レスポンスされるまで5秒くらい)。
参照: https://cero-t.hatenadiary.jp/entry/20160106/1452090214
まとめ
Javaを利用したLambda関数の開発の特徴やコーディング感覚をつかむことができました。
もし、サーブレットから移行するならそこそこ大規模な改修が必要と思いますが、代わりとして使えなくもない印象です。
ランタイムがこのままJava8のみなのか、そこのサポートが少し心配です。。
API Gatewayをトリガーとしましたが、他のサービスも試して見たいと思います。
参考
https://qiita.com/kamata1729/items/8d88ea10dd3bb61fa6cc
https://qiita.com/riversun/items/7fcc06617b469aed8f27