初心者が作ったAPI Gateway+Lambda+DynamoDBのCloudFomationテンプレ

# この記事について

AWS初心者が練習がてら作ったCloudFormation。
IAMロールがめんどくせーと思うた。

## とりあえず作ったもの

タイトルどおりAPI Gateway+Lambda+DynamoDBのスケルトン的構成を作ってみた。
API Gatewayが意外と曲者だった。ポイント解説はまた今度書く。

```yml:template.yml

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  ApiGatewayRestApi:
    Type: "AWS::ApiGateway::RestApi"
    Properties:
      Name: "SampleAPI"
      EndpointConfiguration:
        Types:
          - "REGIONAL"
  ApiGatewayResource:
    Type: "AWS::ApiGateway::Resource"
    Properties:
      RestApiId: !Ref ApiGatewayRestApi
      ParentId: !GetAtt ApiGatewayRestApi.RootResourceId
      PathPart: "sample"
  ApiGatewayMethod:
    Type: "AWS::ApiGateway::Method"
    Properties:
      RestApiId: !Ref ApiGatewayRestApi
      ResourceId: !Ref ApiGatewayResource
      HttpMethod: "POST"
      AuthorizationType: "NONE"
      Integration:
        Type: "AWS"
        IntegrationHttpMethod: "POST"
        Uri: !Join ["", ["arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/", !GetAtt MySampleFunction.Arn, "/invocations"]]
        IntegrationResponses:
          - StatusCode: 200
      MethodResponses:
        - StatusCode: 200
  LambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref 'MySampleFunction'
      Action: lambda:InvokeFunction
      Principal: apigateway.amazonaws.com
  DynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: "sample_table"
      AttributeDefinitions:
        - AttributeName: "id"
          AttributeType: "S"
      KeySchema:
        - AttributeName: "id"
          KeyType: "HASH"
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
  MySampleFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      MemorySize: 128
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          var AWS = require('aws-sdk');
          var dynamo = new AWS.DynamoDB.DocumentClient({
              region: 'ap-northeast-1'
          });

          exports.handler = (event, context, callback) => {
              console.log("event:", event);
              dynamo.put({
                  "TableName": "sample_table",
                  "Item": {
                      "id": event.id,
                      "message": event.message
                  }
              }, function( err, data ) {
                  if (err) {
                      console.log("dynamo_err:", err);
                      context.fail("DB ERROR");
                  } else {
                      context.succeed({result:"OK"});
                  }
              });
          };
      Runtime: nodejs8.10
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
      - PolicyName: root
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - logs:*
            Resource: arn:aws:logs:ap-northeast-1:*:*
      - PolicyName: DynamoAccessPolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - dynamodb:*
            Resource: !GetAtt DynamoDBTable.Arn

```

## はまりポイントの解説

### Lambdaのpermission

これ入れないとAPI GatewayからLambda実行してくれない。
Lambdaのある種のセキュリティ機構であることはわかるがめんどい。

```yaml
  LambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref 'MySampleFunction'
      Action: lambda:InvokeFunction
      Principal: apigateway.amazonaws.com
```

### API GatewayのIntegrationとMethodResponses

最初、Integrationは書いたが、MethodResponsesを書かずにデプロイしたがうまく動かず、何が原因かわからなかった。色々調べたらMethodResponsesとIntegrationResponsesを合わせてやる必要があるという。
めんどくせー、自動でうまく補完しといてくれやと言いたいが、まあ仕方ない。

```yaml
  ApiGatewayMethod:
    Type: "AWS::ApiGateway::Method"
    Properties:
      RestApiId: !Ref ApiGatewayRestApi
      ResourceId: !Ref ApiGatewayResource
      HttpMethod: "POST"
      AuthorizationType: "NONE"
      Integration:
        Type: "AWS"
        IntegrationHttpMethod: "POST"
        Uri: !Join ["", ["arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/", !GetAtt MySampleFunction.Arn, "/invocations"]]
        IntegrationResponses:
          - StatusCode: 200
      MethodResponses:
        - StatusCode: 200
```



## 残課題

* サーバレス用に作られたSAM形式なるものがいいらしいので、また今度ためす。
* lambdaのコードはテンプレに書くよりS3に置くほうがいい。

# 参考

* https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-reference.html
* https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md