AWS Application Composerとは?

AWS Application ComposerとはAWSリソースのサーバーレスアプリケーションの設計とデプロイメントを簡素化するためのビジュアルツールです。

公式サイトはこちら

ビジュアルデザインの直感的な操作と自動生成されるテンプレートにより、開発プロセスが簡素化され、スケーラブルで安定したサーバーレスアプリケーションの構築が容易になります。

またApplication Composer自体の追加料金は発生しない(デプロイ等してリソースを作成すればそのリソース分の料金はかかります)のでライトに使用できますね。

今回はこの Application Composerを使ってtemplate.yamlの構成を視覚的に確認してみたいと思います。

 

実際に使ってみた

今回は既存のtemplate.yamlをAWS Application Composerに読み込ませてみます。

コード(長いので折りたたんでいます、クリックしてください)

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: >
  SAM Template for an API Gateway with multiple Lambda functions, multiple S3 buckets, DynamoDB table, and CORS settings.

Globals:
  Function:
    Timeout: 30

Resources:
  MyApi:
    Type: 'AWS::Serverless::Api'
    Properties: 
      Name: MyApi
      StageName: Prod
      Auth:
        DefaultAuthorizer: CognitoAuthorizer
        Authorizers:
          CognitoAuthorizer:
            UserPoolArn: !Sub arn:aws:cognito-idp:ap-northeast-1:123456789012:userpool/ap-northeast-1_7mtCnXmWT
      Cors:
        AllowMethods: "'OPTIONS,GET,POST'"
        AllowHeaders: "'Content-Type,Authorization'"
        AllowOrigin: "'*'"

  # S3 Buckets
  MyS3Bucket1:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: my-s3-bucket1-${AWS::AccountId}
      AccessControl: Private

  MyS3Bucket2:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: my-s3-bucket2-${AWS::AccountId}
      AccessControl: Private

  MyS3Bucket3:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: my-s3-bucket3-${AWS::AccountId}
      AccessControl: Private

  MyS3Bucket4:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: my-s3-bucket4-${AWS::AccountId}
      AccessControl: Private

  # DynamoDB Table
  MyDynamoDBTable:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      TableName: MyDynamoDBTable
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5

  # Lambda Functions
  MyLambdaFunction1:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: index.handler1
      Runtime: python3.12
      CodeUri: .
      Environment:
        Variables:
          S3_BUCKET: !Ref MyS3Bucket1
          DYNAMO_TABLE: !Ref MyDynamoDBTable
      Policies:
        - S3ReadPolicy:
            BucketName: !Ref MyS3Bucket1
        - S3WritePolicy:
            BucketName: !Ref MyS3Bucket1
        - DynamoDBCrudPolicy:
            TableName: !Ref MyDynamoDBTable
      Events:
        GetResource1:
          Type: Api
          Properties:
            Path: /resource1
            Method: get
            RestApiId: !Ref MyApi

  MyLambdaFunction2:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: index.handler2
      Runtime: python3.12
      CodeUri: .
      Environment:
        Variables:
          S3_BUCKET: !Ref MyS3Bucket2
          DYNAMO_TABLE: !Ref MyDynamoDBTable
      Policies:
        - S3ReadPolicy:
            BucketName: !Ref MyS3Bucket2
        - S3WritePolicy:
            BucketName: !Ref MyS3Bucket2
        - DynamoDBCrudPolicy:
            TableName: !Ref MyDynamoDBTable
      Events:
        GetResource2:
          Type: Api
          Properties:
            Path: /resource2
            Method: get
            RestApiId: !Ref MyApi

  MyLambdaFunction3:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: index.handler3
      Runtime: python3.12
      CodeUri: .
      Environment:
        Variables:
          S3_BUCKET: !Ref MyS3Bucket3
          DYNAMO_TABLE: !Ref MyDynamoDBTable
      Policies:
        - S3ReadPolicy:
            BucketName: !Ref MyS3Bucket3
        - S3WritePolicy:
            BucketName: !Ref MyS3Bucket3
        - DynamoDBCrudPolicy:
            TableName: !Ref MyDynamoDBTable
      Events:
        GetResource3:
          Type: Api
          Properties:
            Path: /resource3
            Method: get
            RestApiId: !Ref MyApi

  MyLambdaFunction4:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: index.handler4
      Runtime: python3.12
      CodeUri: .
      Environment:
        Variables:
          S3_BUCKET: !Ref MyS3Bucket4
          DYNAMO_TABLE: !Ref MyDynamoDBTable
      Policies:
        - S3ReadPolicy:
            BucketName: !Ref MyS3Bucket4
        - S3WritePolicy:
            BucketName: !Ref MyS3Bucket4
        - DynamoDBCrudPolicy:
            TableName: !Ref MyDynamoDBTable
      Events:
        GetResource4:
          Type: Api
          Properties:
            Path: /resource4
            Method: get
            RestApiId: !Ref MyApi

  # IAM Role for Lambda
  MyLambdaRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: MyLambdaPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: 'arn:aws:logs:*:*:*'
              - Effect: Allow
                Action:
                  - cognito-idp:ListUsers
                Resource: '*'

Outputs:
  ApiUrl:
    Description: "URL for the API"
    Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod"

  S3Bucket1Name:
    Description: "Name of the S3 bucket 1"
    Value: !Ref MyS3Bucket1

  S3Bucket2Name:
    Description: "Name of the S3 bucket 2"
    Value: !Ref MyS3Bucket2

  S3Bucket3Name:
    Description: "Name of the S3 bucket 3"
    Value: !Ref MyS3Bucket3

  S3Bucket4Name:
    Description: "Name of the S3 bucket 4"
    Value: !Ref MyS3Bucket4

  DynamoDBTableName:
    Description: "Name of the DynamoDB Table"
    Value: !Ref MyDynamoDBTable

 

かなり長くて分かりづらいですね(笑)

このコードを読ませてみましょう。

コンソールからApplication Composerのホーム画面に飛び、プロジェクトの作成をクリックします

メニューから開く→テンプレートファイルを選択します

先ほどのtemplate.yamlを読み込ませてあげるとリソースの構成が確認できます

想定通り、4つのLambdaに各S3バケットが一つずつ接続されて、DynamoDBは全ての関数に接続されています。

なぜかDynamoDBがS3バケットの間に挟まっちゃっているので少し調整しました

個人的にはこちらの方が見やすいと感じました。

よく見るとAPI GatewayのAuthorizerがどこにもつながってなくて「!」とエラーが出ています。

こちらをCognitoのユーザープールと接続してみましょう。

右側のリソースから「cognito」と検索しCognito UserPoolをドラッグさせてみましょう。

Authorizorの点とUser Poolの点を繋ぎ合わせたら修正完了です。

修正すると、テンプレートも更新されていました!

このテンプレートファイルはメニューから「テンプレートファイルを保存」で入手できます。

修正したコード(長いので折りたたんでいます、クリックしてください)

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: |
  SAM Template for an API Gateway with multiple Lambda functions, multiple S3 buckets, DynamoDB table, and CORS settings.

Globals:
  Function:
    Timeout: 30

Resources:
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      Name: MyApi
      StageName: Prod
      Auth:
        DefaultAuthorizer: CognitoAuthorizer
        Authorizers:
          CognitoAuthorizer:
            UserPoolArn: !GetAtt UserPool.Arn
      Cors:
        AllowMethods: '''OPTIONS,GET,POST'''
        AllowHeaders: '''Content-Type,Authorization'''
        AllowOrigin: '''*'''

  # S3 Buckets
  MyS3Bucket1:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-s3-bucket1-${AWS::AccountId}
      AccessControl: Private

  MyS3Bucket2:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-s3-bucket2-${AWS::AccountId}
      AccessControl: Private

  MyS3Bucket3:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-s3-bucket3-${AWS::AccountId}
      AccessControl: Private

  MyS3Bucket4:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-s3-bucket4-${AWS::AccountId}
      AccessControl: Private

  # DynamoDB Table
  MyDynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: MyDynamoDBTable
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5

  # Lambda Functions
  MyLambdaFunction1:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler1
      Runtime: python3.12
      CodeUri: .
      Environment:
        Variables:
          S3_BUCKET: !Ref MyS3Bucket1
          DYNAMO_TABLE: !Ref MyDynamoDBTable
      Policies:
        - S3ReadPolicy:
            BucketName: !Ref MyS3Bucket1
        - S3WritePolicy:
            BucketName: !Ref MyS3Bucket1
        - DynamoDBCrudPolicy:
            TableName: !Ref MyDynamoDBTable
      Events:
        GetResource1:
          Type: Api
          Properties:
            Path: /resource1
            Method: get
            RestApiId: !Ref MyApi

  MyLambdaFunction2:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler2
      Runtime: python3.12
      CodeUri: .
      Environment:
        Variables:
          S3_BUCKET: !Ref MyS3Bucket2
          DYNAMO_TABLE: !Ref MyDynamoDBTable
      Policies:
        - S3ReadPolicy:
            BucketName: !Ref MyS3Bucket2
        - S3WritePolicy:
            BucketName: !Ref MyS3Bucket2
        - DynamoDBCrudPolicy:
            TableName: !Ref MyDynamoDBTable
      Events:
        GetResource2:
          Type: Api
          Properties:
            Path: /resource2
            Method: get
            RestApiId: !Ref MyApi

  MyLambdaFunction3:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler3
      Runtime: python3.12
      CodeUri: .
      Environment:
        Variables:
          S3_BUCKET: !Ref MyS3Bucket3
          DYNAMO_TABLE: !Ref MyDynamoDBTable
      Policies:
        - S3ReadPolicy:
            BucketName: !Ref MyS3Bucket3
        - S3WritePolicy:
            BucketName: !Ref MyS3Bucket3
        - DynamoDBCrudPolicy:
            TableName: !Ref MyDynamoDBTable
      Events:
        GetResource3:
          Type: Api
          Properties:
            Path: /resource3
            Method: get
            RestApiId: !Ref MyApi

  MyLambdaFunction4:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler4
      Runtime: python3.12
      CodeUri: .
      Environment:
        Variables:
          S3_BUCKET: !Ref MyS3Bucket4
          DYNAMO_TABLE: !Ref MyDynamoDBTable
      Policies:
        - S3ReadPolicy:
            BucketName: !Ref MyS3Bucket4
        - S3WritePolicy:
            BucketName: !Ref MyS3Bucket4
        - DynamoDBCrudPolicy:
            TableName: !Ref MyDynamoDBTable
      Events:
        GetResource4:
          Type: Api
          Properties:
            Path: /resource4
            Method: get
            RestApiId: !Ref MyApi

  # IAM Role for Lambda
  MyLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: MyLambdaPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*
              - Effect: Allow
                Action:
                  - cognito-idp:ListUsers
                Resource: '*'
  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      AdminCreateUserConfig:
        AllowAdminCreateUserOnly: false
      AliasAttributes:
        - email
        - preferred_username
      UserPoolName: !Sub ${AWS::StackName}-UserPool

Outputs:
  ApiUrl:
    Description: URL for the API
    Value: !Sub https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod

  S3Bucket1Name:
    Description: Name of the S3 bucket 1
    Value: !Ref MyS3Bucket1

  S3Bucket2Name:
    Description: Name of the S3 bucket 2
    Value: !Ref MyS3Bucket2

  S3Bucket3Name:
    Description: Name of the S3 bucket 3
    Value: !Ref MyS3Bucket3

  S3Bucket4Name:
    Description: Name of the S3 bucket 4
    Value: !Ref MyS3Bucket4

  DynamoDBTableName:
    Description: Name of the DynamoDB Table
    Value: !Ref MyDynamoDBTable

最後に

自分はtemplateを作成するのが苦手で、何かいい方法がないかと探した際にApplication Composerを見つけました。

まだ対応してないリソース等もあるみたいですが勉強目的だとすごく有効的なツールだなと思いました!

皆さんもぜひ使ってみてください、ここまで読んでくださりありがとうございました。