serverless frameworkでAWS Lambda+DynamoDBをやってみる
serverless framewokを使って、AWS Lambdaの開発&デプロイを簡単に行えるようになったので、メモ。
serverless frameworkをインストール
まずはserverless frameworkをインストールします。
npm install -g serverless
これでslsコマンドがインストールされます。
次に、serverless frameworkでAWS Lambdaの開発環境をscaffoldします。
sls create -t aws-java-gradle -n firstsls
-tオプションにaws-java-gradle
を指定することで、Java + Gradleの環境をScaffoldすることができます。
-tオプションではscaffoldのテンプレートを指定することができaws-java-gradle
以外にも
- aws-nodejs
- aws-python
- aws-java-maven
- aws-kotlin-jvm-maven
などといったテンプレートがあるようです。
生成されたコード
この時点でHello World的なLambdaが既に生成されています。Handler.javaを見てみましょう。
public class Handler implements RequestHandler<Map<String, Object>, ApiGatewayResponse> {
@Override
public ApiGatewayResponse handleRequest(Map<String, Object> input, Context context) {
LOG.info("received: " + input);
Response responseBody = new Response("Go Serverless v1.x! Your function executed successfully!", input);
return ApiGatewayResponse.builder()
.setStatusCode(200)
.setObjectBody(responseBody)
.setHeaders(Collections.singletonMap("X-Powered-By", "AWS Lambda & serverless"))
.build();
}
}
HanderクラスはAWS SDKが提供するRequestHandlerインターフェイスを実装しています。こうすることでこのクラスをAWS Lambdaの関数として機能させることができます。
またジェネリクスでRequestHandler<Map<String, Object>, ApiGatewayResponse>
となっていることから、<Map<String, Object>
を入力値としApiGatewayResponse
を返すfunction、ということになります。
入力値はMap型になっているので、実際には
{
"key1": "value1",
"key2": "value2"
}
のようなjsonをパラメータとして受け取る形になります。詳細は以下が参考になります。
ApiGatewayResponseはserverless frameworkが生成したクラスです。functionもレスポンスとして汎用的に使えますので、ここではこのまま使うようにします。
そして、関数の実体になるメソッドがhandleRequest
メソッドです。AWS Lambdaのfunctionを呼び出した場合には、このメソッドが実行されます。
最初のデプロイ
まずはじめに、functionのデプロイ先リージョンを東京にしておきたいので、serverless.ymlを以下のように修正しておきます。
provider:
...
stage: dev
region: ap-northeast-1
scaffoldされたコードはこのままでも動くので、早速ビルド&デプロイしてみます。
./gradlew build
sls deploy
serverless frameworkでデプロイすると、実際には、serverless.ymlに設定された内容をもとにCloudFormation経由でデプロイが実行され、AWS Lambdaだけではなくzipファイルを置くS3のバケットなど、必要なリソースをすべて自動で準備してくれます。
デプロイが完了したら、AWSのコンソールなどからLambdaが実際に動作しているかどうか、確認しておきましょう。
意図しない課金が発生しないように使わないリソースを消したい、といった時でもCloudFormationですのでスタックを消してしまえば、リソースもすべて消えるので便利です。serverless frameworkからであれば
sls remove
で消すこともできます。
DymanoDBを設定する
おおまかなところはできたので、ここでは先程作ったfunctionをREST APIとして公開し、クエリパラメータをDynamoDBに保存するようなAPIにすることを考えてみます。
最初にfunctionから使うDynamoDBのテーブル名をLambdaの環境変数に設定できるよう、serverless.ymlに以下の記述を追加します。
provider:
...
environment:
DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
この設定により、DYNAMODB_TABLEという環境変数が設定されるようになります。
またserverless.ymlの設定では上記のように${self:service}
などとするとこで、他の設定値を参照することができます。
次にデプロイ時にDynamoDBを作成するよう、serverless.ymlに設定を追加します。
resources:
Resources:
FirstServerlessDynamoDbTable:
Type: 'AWS::DynamoDB::Table'
DeletionPolicy: Retain
Properties:
AttributeDefinitions:
-
AttributeName: id
AttributeType: S
KeySchema:
-
AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
TableName: ${self:provider.environment.DYNAMODB_TABLE}
合わせて、DynamoDBへのアクセス権限を付与するように設定を追加します。
provider:
...
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"
あとはHandlerのhandleRequestにDynamoDBへ書き込む処理を実装していきます。
API Gatewayに渡されたクエリパラメータは、”queryStringParameters”という名前で取得できるので、以下のコードで取得します。
@Override
public ApiGatewayResponse handleRequest(Map<String, Object> input, Context context) {
final String PARAM_NAME = "queryStringParameters";
Map<String, Object> params = (Map<String, Object>) input.get(PARAM_NAME);
...
}
次にこれを以下のコードでDynamoDBに書き込みます。
private void writeDynamoDB(Map<String, Object> input) {
AmazonDynamoDB client = AmazonDynamoDBClientBuilder
.standard()
.withRegion(Regions.AP_NORTHEAST_1)
.build();
DynamoDB dynamoDB = new DynamoDB(client);
Table table = dynamoDB.getTable(System.getenv("DYNAMODB_TABLE"));
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
Item item = new Item()
.withPrimaryKey("id", UUID.randomUUID().toString())
.with("created_at", format.format(new Date()));
input.forEach((key, value) -> item.with(key, value));
table.putItem(item);
}
created_atには現在の日時をセットしていますが、DynamoDBには日付型に相当する型はありませんので、ISO-8601で文字列に変換して格納しています。
あとは再度ビルド&デプロイします。
./gradlew clean build
sls deploy
正しくデプロイできたら、APIをcurlなどで呼び出してみます。
curl -X GET https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello?key1=value1
正しく呼び出せれば、DynamoDBにkey1という項目がvalue1という値で保存されているはずです。
今回作成したコードは以下のありますので、参考までに。