AWS Application Composerのチュートリアルをやってみた #reinvent

2022.12.04

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

先日のre:InventでプレビューリリースされましたAWS Application Composerがどんなものか触ってみたく、公式ガイド記載のチュートリアルをやってみたのでご紹介します。

なお、本記事は「Japan AWS Ambassador Advent Calendar 2022」4日目の記事としてエントリーしています。APN Ambassadorって何?と言う方は「APN Ambassadorsってなんだ?2021年度版」をご参照ください。

AWS Application Composer

AWS Application Composerは、複数のAWSサービスからサーバーレスアプリケーションを構築するために使用できるビジュアルデザイナーです。Application Composerのインタラクティブビルダーを使用して、キャンバス上でAWSリソースを選択、接続、および定義することによって、アプリケーションアーキテクチャを設計することができます。

また同時に、Application ComposerはAWSのベストプラクティスに従って、AWS CloudFormationとAWS Serverless Application Model (AWS SAM)のテンプレートを自動的に作成します。

さっそくやってみよう

AWS Application Composerは現時点ではプレビュー版です。記事中で紹介される内容は一般公開(GA)時には変更される可能性がある点にご留意してお読みください。

Application Composerは嬉しいことに東京リージョンで今すぐプレビュー利用できますので、今回は東京リージョンでチュートリアルを実施します。

AWSコンソールから「Application Composer」の管理コンソールを開きます。Homeページにある「Open demo」を開きます。

Create a demo project」のポップアップが表示され接続モードの選択が必要になりますので「Connected」を選択し、「Select folder」からテンプレートファイル等の保管場所として同期するローカルフォルダーを指定し「Create」します。

Application Composerでは以下、2つの接続モードで作業することができます。

接続モード 説明
接続モード(Connected) 設計中に自動的にテンプレートファイルとプロジェクトフォルダをローカルに同期して保存します
非接続モード(Unconnected) テンプレートファイルを手動でインポートおよびエクスポートします

現時点では「Edge」「Chrome」は接続モードがサポートされているようです。非対応のブラウザを利用している場合、以下のように「Connected」はグレーアウトされており、ブラウザ非対応であるとのメッセージが表示されます。

デモプロジェクトを作成した直後のローカルフォルダーは以下のとおりでした。

$ tree application-composer-demo/
application-composer-demo/
├── README.md
├── samconfig.toml
├── src
│   ├── CreateItem
│   │   ├── index.js
│   │   └── package.json
│   ├── DeleteItem
│   │   ├── index.js
│   │   └── package.json
│   ├── GetItem
│   │   ├── index.js
│   │   └── package.json
│   ├── ListItems
│   │   ├── index.js
│   │   └── package.json
│   └── UpdateItem
│       ├── index.js
│       └── package.json
└── template.yaml

チュートリアル用に提供されているプロジェクトが展開されます。Application Composerの基本的な操作方法については右上に表示されている「Take a quick tour of composer」メッセージ内の「Start」をクリックすると5ページ程度のツアーを通じてざっくりと理解することができます。

チュートリアル1: Application Composerデモプロジェクトの読み込みと変更

先の手順でデモアプリケーションとして、以下を含む基本的なCRUD(Create/Read/Update/Delete)サーバーレスアプリケーションのテンプレートが展開されています。

  • 5つのルートを持つAmazon API Gatewayリソース
  • 5つのLambda関数
  • Amazon DynamoDBテーブル

画面上部の「Canvas」「Template」で画面表示を切替えることができるようですね。試しに「Template」に切り替えてみると以下のような画面になりました。

ビューを「Canvas」に戻し、DynamoDBテーブルにLambda関数を追加しアプリケーションアーキテクチャを拡張してみます。左ペインのメニューを「Resources」に切り替え、「Lambda Function」をドラッグ&ドロップでCanvas内に配置します。

そしてDynamoDBテーブル「items」と配置した「Function」の端子を接続します。画面上部の「Arrange」をクリックすると配置をキレイに整理してくれるようですね。

追加したリソースカード「Function」をダブルクリックすると、リソースのプロパティパネルが表示されますので必要な値を設定します。

設定を保存するには画面右上の「Menu」から「Save change」をクリックします。接続モードを利用している場合は自動的にローカルに保存されています。

チュートリアル1は以上です。

チュートリアル2: Application Composer を使用して最初のアプリケーションを構築する

次のチュートリアル2では、データベースでユーザーを管理するCRUDサーバーレスアプリケーションを作成します。作成するAPIは以下のように設定します。

Method Path Function Name
GET /items getItems
GET /items/{id} getItem
PUT /items/{id} updateItem
POST /item addItem
DELETE /items/{id} deleteItem

AWSコンソールから「Application Composer」の管理コンソールを開きます。Homeページにある「Create project」を開きます。新規のプロジェクトを作成しますので「New blank project」を選択、今回も接続モードを利用することにします。

空のプロジェクトを作成しましたのでローカルフォルダにあるのはtemplate.yamlのみでした。

$ tree application-composer-tutorial2/
application-composer-tutorial2/
└── template.yaml

このステップでは、Amazon API Gatewayリソースと5つのLambda関数を含むアプリケーションアーキテクチャの設計を開始します。

左ペインのリソースパレットからAPI Gatewayを1つ、Lambda関数を5つ配置します。

次にAPIルートを設定していきます。API Gatewayリソースカードをダブルクリックし、リソースプロパティを表示します。次に1つ目のルートを「Method:GET」、「Path:/items」に設定します。

次に「Add route」をクリックして2つ目以降のルートを設定します。追加する設定は先の表を参照しながら残り4つを設定し、保存します。

次にLambda関数の設定をします。Lambda関数「Function」リソースカードをダブルクリックし、リソースプロパティを表示します。次に「Logical ID:getitems」を入力し保存。

同様に先の表を参照しながら残り4つのLambda関数にも「Logical ID」を設定します。

表を参照しながら各APIメソッドとパスの端子とLambda関数の端子を結びます。図を整理するために「Arrange」をクリックします。

Shiftを押しながらLambda関数をクリックすると複数選択することができます。この状態から「Group」をクリックするとグループ設定することが出来ます。

Group」のリソースプロパティを表示し、グループ名をAPIに変更し、保存します。

左ペインのリソースパレットからDynamoDBテーブルをドラック&ドロップします。各Lambda関数の端子とDynamoDBテーブルを接続します。

テンプレートビューに切り替えてみると以下のように、およそ220行程度のテンプレートが自動生成されていることがわかります。

template.yaml

template.yaml

Transform: AWS::Serverless-2016-10-31
Resources:
  Api:
    Type: AWS::Serverless::Api
    Properties:
      Name: !Sub
        - ${ResourceName} From Stack ${AWS::StackName}
        - ResourceName: Api
      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/${getitems.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: {}
          /item:
            post:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${additem.Arn}/invocations
              responses: {}
          /itesms/{id}:
            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
  getitems:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: getitems
      CodeUri: src/Function
      Handler: index.handler
      Runtime: nodejs18.x
      MemorySize: 3008
      Timeout: 30
      Tracing: Active
      Events:
        ApiGETitems:
          Type: Api
          Properties:
            Path: /items
            Method: GET
            RestApiId: !Ref Api
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
          TABLE_ARN: !GetAtt Table.Arn
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref Table
  getitemsLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain
    Properties:
      LogGroupName: !Sub /aws/lambda/${getitems}
  getitem:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: getitem
      CodeUri: src/Function2
      Handler: index.handler
      Runtime: nodejs18.x
      MemorySize: 3008
      Timeout: 30
      Tracing: Active
      Events:
        ApiGETitemsid:
          Type: Api
          Properties:
            Path: /items/{id}
            Method: GET
            RestApiId: !Ref Api
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
          TABLE_ARN: !GetAtt Table.Arn
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref Table
  getitemLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain
    Properties:
      LogGroupName: !Sub /aws/lambda/${getitem}
  updateitem:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: updateitem
      CodeUri: src/Function3
      Handler: index.handler
      Runtime: nodejs18.x
      MemorySize: 3008
      Timeout: 30
      Tracing: Active
      Events:
        ApiPUTitemsid:
          Type: Api
          Properties:
            Path: /items/{id}
            Method: PUT
            RestApiId: !Ref Api
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
          TABLE_ARN: !GetAtt Table.Arn
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref Table
  updateitemLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain
    Properties:
      LogGroupName: !Sub /aws/lambda/${updateitem}
  additem:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: additem
      CodeUri: src/Function4
      Handler: index.handler
      Runtime: nodejs18.x
      MemorySize: 3008
      Timeout: 30
      Tracing: Active
      Events:
        ApiPOSTitem:
          Type: Api
          Properties:
            Path: /item
            Method: POST
            RestApiId: !Ref Api
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
          TABLE_ARN: !GetAtt Table.Arn
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref Table
  additemLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain
    Properties:
      LogGroupName: !Sub /aws/lambda/${additem}
  deleteitem:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: deleteitem
      CodeUri: src/Function5
      Handler: index.handler
      Runtime: nodejs18.x
      MemorySize: 3008
      Timeout: 30
      Tracing: Active
      Events:
        ApiDELETEitesmsid:
          Type: Api
          Properties:
            Path: /itesms/{id}
            Method: DELETE
            RestApiId: !Ref Api
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
          TABLE_ARN: !GetAtt Table.Arn
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref Table
  deleteitemLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain
    Properties:
      LogGroupName: !Sub /aws/lambda/${deleteitem}
  Table:
    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
Metadata:
  AWS::Composer::Groups:
    Group:
      Label: API
      Members:
        - getitems
        - getitem
        - updateitem
        - additem
        - deleteitem

Lambda関数を配置するとローカルフォルダには/srcフォルダや仮のindex.jsなどが自動生成されていましたので、これらを使ってとりあえずえいやーでデプロイしてしまうことが出来そうですね!

 tree application-composer-tutorial2/
application-composer-tutorial2/
├── .aws-composer
│   └── 20221203T212813399
│       └── template.yaml
├── src
│   ├── Function
│   │   ├── index.js
│   │   └── package.json
│   ├── Function2
│   │   ├── index.js
│   │   └── package.json
│   ├── Function3
│   │   ├── index.js
│   │   └── package.json
│   ├── Function4
│   │   ├── index.js
│   │   └── package.json
│   └── Function5
│       ├── index.js
│       └── package.json
└── template.yaml

チュートリアル2は以上です。

さいごに

まだプレビュー版ということもあり各リソースプロパティで設定できる項目はそれほど多くないのですが、ドラッグ&ドロップだけで各AWSサービス間の連携ができ、IaC用のテンプレートまで自動生成されるというユーザー体験は非常にワクワクを感じるものでした。GAに向けてさらなる対応リソースの追加、設定項目の追加に期待したいですね。

さて、「Japan AWS Ambassador Advent Calendar 2022」5日目はというと、AWS認定資格試験テキストをはじめとする書籍でも皆さんお世話になっているであろう、NRIネットコム株式会社 佐々木さんのご担当です。楽しみですね!