Logo
17 Oct 2017 | 2 min. (270 words)

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をパラメータとして受け取る形になります。詳細は以下が参考になります。

  • ハンドラー作成用に事前定義されているインターフェイスの利用 (Java)

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という値で保存されているはずです。

今回作成したコードは以下のありますので、参考までに。

  • https://github.com/zephiransas/firstsls
Java AWS serverless lambda dynamodb
Twitter Facebook

AWS Lambdaにコマンドを追加する

…

Spring BootのCSSをGulpで管理する

…

Theme Bleak by zutrinken Published with Hugo
Menu
  • serverless frameworkをインストール
  • 生成されたコード
  • 最初のデプロイ
  • DymanoDBを設定する