AWS Application Composerを使ってサーバーレスアプリを作ってみた

この記事は、[AWS LambdaとServerless Advent Calendar 2022](https://qiita.com/advent-calendar/2022/lambda)　アドベントカレンダーの19日目の記事です。

## はじめに

皆さん、こんにちは。AWSのソリューションアーキテクトの福井です。今年もAWS re:Inventで数多くの新しいサービスとサービスのアップデートが発表されました。本記事では、今回新たに発表された [AWS Application Composer (Preview)](https://aws.amazon.com/jp/application-composer/)を使ってサーバーレスアプリケーションを構築してみましたので、紹介させて頂きます。

## AWS Application Composerとは

[AWS Application Composer](https://aws.amazon.com/jp/application-composer/) は、ビジュアルなエディターを使って様々なAWSのサービスを組み合わせて、サーバーレスアプリケーションを構築することができるサービスで、ビジュアルエディタで編集した内容を[AWS Serverless Application Model (SAM)](https://aws.amazon.com/jp/serverless/sam/) のテンプレートを生成したり、テンプレートを編集した結果をビジュアルエディター側に自動的に反映する機能を提供しています。

なお、AWS Application Composerは2022年12月現在、Previewとして公開されています。一般利用可能となるまでは、本番環境への適用はしないようにしてください。

## AWS Application Composerを触ってみた

AWS Application Composerは現在、米国東部 (オハイオ)、 米国東部 (北バージニア)、 米国西部 (オレゴン)、 アジアパシフィック (東京)、ヨーロッパ (フランクフルト)、ヨーロッパ (アイルランド)の各リージョンで利用可能です。今回は、米国西部（オレゴン）リージョンを選択しました。

AWSマネジメントコンソールにログインして、リージョンを選択し、AWS Application Composerのマネジメントコンソールを開きます。

![image001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83238/0513f404-0099-ed0f-33c9-395c35470912.png)

[Create project]ボタンをクリックします。

![image002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83238/28260baf-ac57-79ed-1e04-1bea69f2c84c.png)

Create projectのダイアログが表示されるので、[New blank project]を選択し、[Connected]を選択した状態で、[Select folder]ボタンをクリックします。フォルダの選択ダイアログが開きますので、任意のフォルダを選択します。

:::note warn
現在のところ、[Connected]モードが利用できるブラウザはChromeのみになります。
:::

[サイトにファイルの読み取りを許可しますか？]というダイアログが表示されるので、[ファイルを表示する]ボタンをクリックします。

![image003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83238/951fe3f0-b9ce-6ae9-ea93-cea4bd9fa09c.png)

[「指定したフォルダ」に変更を保存しますか？]というダイアログが表示されるので、[変更を保存]ボタンをクリックします。

![image004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83238/af04a9b9-9328-0007-f253-3ebdb4319067.png)

AWS Application Composerのキャンバスが表示され、編集が可能になります。

![image005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83238/75ce5942-0b26-f45e-4de1-49643173b755.png)


## キャンバス上にサーバーレスリソースをドラッグ＆ドロップして編集


キャンバスの左側のペインにはサーバーレスリソースを表すアイコンが並んでいます。このアイコンを掴んでキャンバスにドラッグ＆ドロップすることで、SAMのテンプレートを組み立てることができます。

### Amazon API Gatewayリソースの作成

以下の手順で、API Gatewayをイベントソースとする Lambdaアプリケーションを構築します。

- キャンバスに API Gatewayのアイコンをドラッグ＆ドロップします。
- キャンバスにドロップしたリソースをクリックし、右側のペインでプロパティーを入力します。ここでは、Logical ID をItemApiに変更しました。

![image006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83238/4c0d7009-c2ca-cb9e-c9fc-3f0003a9ac3e.png)

- 最初のGETメソッドのRouteを /items に変更し、[Add　route] ボタンをクリックして図のようにルートを追加し、[Save]ボタンをクリックして変更を保存します。

![image007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83238/ec6b13bc-2b45-1767-9f99-a8b3a7ca857b.png)


### AWS Lambda リソースの作成

同じように、今度は Lambdaのアイコンをドラッグ＆ドロップします。Logical IDを入力し、Source pathを "src/listitem" のように各ファンクションごとに変更し、RuntimeをPython 3.9に変更しました。API GatewayのRouteと接続することで、自動的にAPI GatewayをLambdaのイベントソースにマップしてくれます。

``` diff_yaml:template.yaml
  ListItem:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: ListItem
      CodeUri: src/listitem
      Handler: handler.handler
      Runtime: python3.9
      MemorySize: 256
      Timeout: 30
      Tracing: Active
+      Events:
+        ItemApiGETitems:
+          Type: Api
+          Properties:
+            Path: /items
+            Method: GET
+            RestApiId: !Ref ItemApi

```
すべてのAPI Gatewayのルートに対応するLambdaファンクションを作成しました。

![image008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83238/d6387885-e4a6-f8b1-e93b-88723cb506f8.png)

### ローカルファイルとの同期

ここまでの作業でローカルのフォルダは以下のようになっています。

```shell:tree
.
├── src
│   ├── Function
│   │   ├── handler.py
│   │   ├── index.js
│   │   ├── package.json
│   │   └── requirements.txt
│   ├── createitem
│   │   ├── handler.py
│   │   └── requirements.txt
│   ├── deleteitem
│   │   ├── handler.py
│   │   └── requirements.txt
│   ├── getitem
│   │   ├── handler.py
│   │   └── requirements.txt
│   ├── listitem
│   │   ├── handler.py
│   │   └── requirements.txt
│   └── updateitem
│       ├── handler.py
│       └── requirements.txt
└── template.yaml
```
最初に作成されるsrc/Function ディレクトリは不要なので、削除します。ローカルファイルと同期されているので、ローカルのファイルを編集するとその内容をApplication Composerに反映することもできます。 template.yamlをエディタで開いて編集してみます。Lambdaのメモリサイズを3008から256に置換し、保存したところ、Application Composer側にも反映されました。

![image009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83238/fe14c842-1ec5-9923-a984-d702004ffcf4.png)

### Amazon DynamoDB　リソースの追加と接続

同じように Amazon DynamoDB　のアイコンもドラッグ＆ドロップして、すべてのLambdaファンクションと接続します。

![image010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83238/bdd1d8b5-fee9-f650-1cb1-0c0265bf59c6.png)

### AWS SAM テンプレートを確認

左上の[Template]ボタンをクリックすると、生成されたAWS SAM （Serverless Application Model）　のテンプレートを確認することができます。このテンプレートを直接編集することも可能です。

![image011.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83238/f15a02b7-cbe6-e05b-58d5-271af0f945a3.png)



これまでの操作で生成されたSAMテンプレートは以下の通りです。

``` yaml:template.yaml

Transform: AWS::Serverless-2016-10-31
Resources:
  ItemApi:
    Type: AWS::Serverless::Api
    Properties:
      Name: !Sub
        - ${ResourceName} From Stack ${AWS::StackName}
        - ResourceName: ItemApi
      StageName: Prod
      DefinitionBody:
        openapi: '3.0'
        info: {}
        paths:
          /items:
            get:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ListItem.Arn}/invocations
              responses: {}
            post:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${CreateItem.Arn}/invocations
              responses: {}
          /items/{id}:
            get:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetItem.Arn}/invocations
              responses: {}
            put:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${UpdateItem.Arn}/invocations
              responses: {}
            delete:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DeleteItem.Arn}/invocations
              responses: {}
      EndpointConfiguration: REGIONAL
      TracingEnabled: true
  ListItem:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: ListItem
      CodeUri: src/listitem
      Handler: handler.handler
      Runtime: python3.9
      MemorySize: 256
      Timeout: 30
      Tracing: Active
      Events:
        ItemApiGETitems:
          Type: Api
          Properties:
            Path: /items
            Method: GET
            RestApiId: !Ref ItemApi
      Environment:
        Variables:
          TABLE_NAME: !Ref Items
          TABLE_ARN: !GetAtt Items.Arn
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref Items
  ListItemLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain
    Properties:
      LogGroupName: !Sub /aws/lambda/${ListItem}
  GetItem:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: GetItem
      CodeUri: src/getitem
      Handler: handler.handler
      Runtime: python3.9
      MemorySize: 256
      Timeout: 30
      Tracing: Active
      Events:
        ItemApiGETitemsid:
          Type: Api
          Properties:
            Path: /items/{id}
            Method: GET
            RestApiId: !Ref ItemApi
      Environment:
        Variables:
          TABLE_NAME: !Ref Items
          TABLE_ARN: !GetAtt Items.Arn
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref Items
  GetItemLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain
    Properties:
      LogGroupName: !Sub /aws/lambda/${GetItem}
  CreateItem:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: CreateItem
      CodeUri: src/createitem
      Handler: handler.handler
      Runtime: python3.9
      MemorySize: 256
      Timeout: 30
      Tracing: Active
      Events:
        ItemApiPOSTitems:
          Type: Api
          Properties:
            Path: /items
            Method: POST
            RestApiId: !Ref ItemApi
      Environment:
        Variables:
          TABLE_NAME: !Ref Items
          TABLE_ARN: !GetAtt Items.Arn
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref Items
  CreateItemLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain
    Properties:
      LogGroupName: !Sub /aws/lambda/${CreateItem}
  UpdateItem:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: UpdateItem
      CodeUri: src/updateitem
      Handler: handler.handler
      Runtime: python3.9
      MemorySize: 256
      Timeout: 30
      Tracing: Active
      Events:
        ItemApiPUTitemsid:
          Type: Api
          Properties:
            Path: /items/{id}
            Method: PUT
            RestApiId: !Ref ItemApi
      Environment:
        Variables:
          TABLE_NAME: !Ref Items
          TABLE_ARN: !GetAtt Items.Arn
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref Items
  UpdateItemLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain
    Properties:
      LogGroupName: !Sub /aws/lambda/${UpdateItem}
  DeleteItem:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: DeleteItem
      CodeUri: src/deleteitem
      Handler: handler.handler
      Runtime: python3.9
      MemorySize: 256
      Timeout: 30
      Tracing: Active
      Events:
        ItemApiDELETEitemsid:
          Type: Api
          Properties:
            Path: /items/{id}
            Method: DELETE
            RestApiId: !Ref ItemApi
      Environment:
        Variables:
          TABLE_NAME: !Ref Items
          TABLE_ARN: !GetAtt Items.Arn
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref Items
  DeleteItemLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain
    Properties:
      LogGroupName: !Sub /aws/lambda/${DeleteItem}
  Items:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      BillingMode: PAY_PER_REQUEST
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      StreamSpecification:
        StreamViewType: NEW_AND_OLD_IMAGES

```
この後は Lambdaファンクションのコードを編集し、`sam build`して、`sam deploy -g`するだけで、AWSにサーバーレスアプリをデプロイできます。

## 既存のテンプレートの読み込みと表示

AWS Application Composer　は、SAMテンプレートの生成だけでなく、既存のテンプレートを読み込んでキャンバスに表示することもできます。Create project のダイアログで、[Load existing project] を選択し、フォルダを指定してYAMLテンプレートを読み込むことができます。

![image012.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83238/d98b2b0e-d1f3-e63a-1e0d-7d8c6901c93a.png)

キャンバス上でアーキテクチャが視覚化されることで、チームで素早く確認することができそうです。

## SAM Accelerate との組み合わせ
[SAM Accelerate](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/accelerate.html) とは、AWS SAM が提供する開発環境向けの高速なAWSへのデプロイの仕組みです。AWS CloudFormation スタックの変更セットの更新をスキップするため、本番環境では使えませんが、SAM Template 内のAPI Gateway や Lambda に対する変更を素早く反映できるため、開発中のコードを実行し動作を確認するのに便利です。AWS Application Composer と SAM Accelerate　を組み合わせることで、素早く SAM　テンプレートを作成し、デプロイして変更を確認することが可能になります。 

SAM Accelelate を開始するには以下のコマンドを実行します。

```shell
sam sync --watch --stack-name sam-app
```

sam sync --watchコマンドを始めて実行するとAWS SAM は CloudFormation デプロイを実行します。デプロイ後、AWS SAM　はアプリケーションの変更を監視します。Lambda 関数などのコードリソースへのその後の変更については、AWS SAM は、サービス API を使用して変更を自動的にデプロイします。IAM ロールなどのインフラストラクチャの変更については、AWS SAM は、AWS CloudFormation デプロイを自動的に開始します。

## まとめ

いかがでしょうか。AWS Application Composer を利用することでサーバーレスアプリケーションの開発がさらに加速しそうだと感じました。現在はまだプレビューですが、利用者のフィードバックを受けて今後さらに改善されていくでしょう。またキャンバス上でサーバーレスアーキテクチャを視覚化するメリットも感じることができました。この記事を見て、面白そうだと感じたらぜひ使ってみてください。




#### 免責
本投稿は、個人の意見で、所属する企業や団体は関係ありません。
また掲載しているサンプルプログラム等の動作に関しても一切保証しておりません。