AWS CDK(Python)でコーディングしたAPIGateway×LambdaをSAMCLI Localで動作確認してからAWSにデプロイしてみた。


AWSは、`IaC`(Infrastructure as Code)で手軽にアプリケーションを構築するためのオープンソースのフレームワークを複数提供しています。
* `SAM`
    * サーバーレスアプリケーション構築用のオープンソースフレームワーク
    * `CloudFormation`より短い定義でいい感じに定義できる
    * Lambda関数のバンドル処理やAWSへのアップロードもコマンドで手軽にできる
    * ローカルでLambda関数＋αを起動させてデバッグができるツールも提供している
* `CDK`
    * 特定のプログラミング言語でクラウドインフラストラクチャをコード化＋`CloudFormation`を通じてデプロイするためのオープンソースフレームワーク
    * AWSにより最適化されたコンストラクト（通称：`L2 Layer`？）が提供されているため、複雑なコーディングなしでリソースを定義＋構築することができる（コーディングの負荷軽減）

昨今ではAWSリソースを`CDK`で管理していくのが主流になりつつありますが、`SAM`はサーバーレスのアプリケーションをローカルで手軽にデバッグできるツールとして優秀ですので、どちらもうまく活用していきたいところです。
そこで今回の記事では、その使い分け方法として、以下のことをやっていきます。（タイトル回収）

**【主な手順】**

1. `CDK`でAWSリソースを定義する
1. 1.の`CDK`のコード結果を`CloudFormation`のテンプレートを出力する
1. 2.で出力したテンプレートを利用して`SAMCLI Local`でデバッグする
1. 3.の結果が問題なければ`CDK`のコマンドでデプロイする

なお、今回は[公式](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-cdk-getting-started.html)の手順を参考に、[aws-cdk-lib.aws_sam](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_sam-readme.html)を利用しないバージョンを試していきます。

# `CDK`でコーディングするAWSリソース
`API Gateway`×`Lambda`のAWSリソースを構築します。
`CDK`のコーディングを行う前に、比較用として`SAM`テンプレートで今回構築するリソースを表現しておきます。
```yaml:比較用SAMテンプレート
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8
      Timeout: 3
      MemorySize: 128
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
```

# 動作環境

本記事を実施した環境は以下の通りです。
```bash
$ uname -a
Linux MikMatsuPC 5.10.16.3-microsoft-standard-WSL2 #1 SMP Fri Apr 2 22:23:49 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
$ python3 --version
Python 3.8.10
$ node -v
v19.6.0
$ cdk --version
2.65.0 (build 5862f7a)
$ sam --version
SAM CLI, version 1.74.0
$ docker --version
Docker version 23.0.0, build e92dd87
```

# 実行手順

## CDKプロジェクト作成
馴染みのコマンドで作成します。
```bash
$ cdk init app --language python
Applying project template app for python

# Welcome to your CDK Python project!

{中略}

Enjoy!

Initializing a new git repository...
Please run 'python3 -m venv .venv'!
Executing Creating virtualenv...
✅ All done!
```

## Lambda関数のコード作成
単純なメッセージが返却されるプログラムを用意します。
```bash
$ mkdir hello_world
$ touch hello_world\app.py
```
プログラムの中身は以下となります。
```python:hello_world\app.py
import json

def lambda_handler(event, context):

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world from SAM and the CDK!",
        }),
    }
```

## スタック作成

`API Gateway`と`Lambda`を定義します。
`API Gateway`は手抜きで、`Lambda`でトリガー指定した際に自動生成されるものを利用します。
`SAM`とほぼコード量は変わらないように見えます。
```python:sam_with_cdk\sam_with_cdk_stack.py
from aws_cdk import (
    Stack,
    Duration,
    aws_lambda as lambda_,
    aws_lambda_event_sources as lambda_event_sources,
)
from constructs import Construct

function_timeout = 3
function_memory_size = 128

class SamWithCdkStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        lambda_function = lambda_.Function(self, "HelloWorldFunction",
            code=lambda_.Code.from_asset("hello_world"),
            handler="app.lambda_handler",
            runtime=lambda_.Runtime.PYTHON_3_8,
            timeout=Duration.minutes(function_timeout),
            memory_size=function_memory_size,
        )
        lambda_function.add_event_source(lambda_event_sources.ApiEventSource(
                method="get",
                path="/hello",
            )
        )
```

## `cdk synth`で`CloudFormation`のテンプレート出力
コマンド実行時には、`--no-staging` を付与して実行します。（SAM CLIでローカルデバッグするためのオプション）
```bash
$ cdk synth --no-staging
```
実行結果で出力されたyamlは一応載せておきます。（長いので折りたたんでいます）
`CDK`の恩恵を受けていますので、`AWS::IAM::Role`や`AWS::Lambda::Permission`などもしっかり自動付与されています。

<details><summary>コマンド出力結果（yaml）</summary>

```yaml
Resources:
  HelloWorldFunctionServiceRole8E0BD458:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - Fn::Join:
            - ""
            - - "arn:"
              - Ref: AWS::Partition
              - :iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
    Metadata:
      aws:cdk:path: SamWithCdkStack/HelloWorldFunction/ServiceRole/Resource
  HelloWorldFunctionB2AB6E79:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket:
          Fn::Sub: cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}
        S3Key: 44b520949ecc9f814580b3164083d23132e7feb8125f24d991a81d197bc5b22c.zip
      Role:
        Fn::GetAtt:
          - HelloWorldFunctionServiceRole8E0BD458
          - Arn
      Handler: app.lambda_handler
      MemorySize: 128
      Runtime: python3.8
      Timeout: 180
    DependsOn:
      - HelloWorldFunctionServiceRole8E0BD458
    Metadata:
      aws:cdk:path: SamWithCdkStack/HelloWorldFunction/Resource
      aws:asset:path: /home/mikmatsu/cdk/sam-with-cdk/hello_world
      aws:asset:is-bundled: false
      aws:asset:property: Code
  SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4F567FE4AE:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: SamWithCdkStackHelloWorldFunction7124C723:ApiEventSourceA7A86A4F
    Metadata:
      aws:cdk:path: SamWithCdkStack/SamWithCdkStackHelloWorldFunction7124C723:ApiEventSourceA7A86A4F/Resource
  SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4FDeployment1B0E4B9D858995340bef49c95618c03485e457d9:
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId:
        Ref: SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4F567FE4AE
      Description: Automatically created by the RestApi construct
    DependsOn:
      - SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4Fhelloget39C449D6
      - SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4Fhello00548847
    Metadata:
      aws:cdk:path: SamWithCdkStack/SamWithCdkStackHelloWorldFunction7124C723:ApiEventSourceA7A86A4F/Deployment/Resource
  SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4FDeploymentStageprod407F57F9:
    Type: AWS::ApiGateway::Stage
    Properties:
      RestApiId:
        Ref: SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4F567FE4AE
      DeploymentId:
        Ref: SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4FDeployment1B0E4B9D858995340bef49c95618c03485e457d9
      StageName: prod
    Metadata:
      aws:cdk:path: SamWithCdkStack/SamWithCdkStackHelloWorldFunction7124C723:ApiEventSourceA7A86A4F/DeploymentStage.prod/Resource
  SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4Fhello00548847:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId:
        Fn::GetAtt:
          - SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4F567FE4AE
          - RootResourceId
      PathPart: hello
      RestApiId:
        Ref: SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4F567FE4AE
    Metadata:
      aws:cdk:path: SamWithCdkStack/SamWithCdkStackHelloWorldFunction7124C723:ApiEventSourceA7A86A4F/Default/hello/Resource
  SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4FhellogetApiPermissionSamWithCdkStackSamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4F869387A4GEThelloD632A017:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName:
        Fn::GetAtt:
          - HelloWorldFunctionB2AB6E79
          - Arn
      Principal: apigateway.amazonaws.com
      SourceArn:
        Fn::Join:
          - ""
          - - "arn:"
            - Ref: AWS::Partition
            - ":execute-api:"
            - Ref: AWS::Region
            - ":"
            - Ref: AWS::AccountId
            - ":"
            - Ref: SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4F567FE4AE
            - /
            - Ref: SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4FDeploymentStageprod407F57F9
            - /GET/hello
    Metadata:
      aws:cdk:path: SamWithCdkStack/SamWithCdkStackHelloWorldFunction7124C723:ApiEventSourceA7A86A4F/Default/hello/get/ApiPermission.SamWithCdkStackSamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4F869387A4.GET..hello
  SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4FhellogetApiPermissionTestSamWithCdkStackSamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4F869387A4GEThello83E18F88:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName:
        Fn::GetAtt:
          - HelloWorldFunctionB2AB6E79
          - Arn
      Principal: apigateway.amazonaws.com
      SourceArn:
        Fn::Join:
          - ""
          - - "arn:"
            - Ref: AWS::Partition
            - ":execute-api:"
            - Ref: AWS::Region
            - ":"
            - Ref: AWS::AccountId
            - ":"
            - Ref: SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4F567FE4AE
            - /test-invoke-stage/GET/hello
    Metadata:
      aws:cdk:path: SamWithCdkStack/SamWithCdkStackHelloWorldFunction7124C723:ApiEventSourceA7A86A4F/Default/hello/get/ApiPermission.Test.SamWithCdkStackSamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4F869387A4.GET..hello
  SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4Fhelloget39C449D6:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: GET
      ResourceId:
        Ref: SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4Fhello00548847
      RestApiId:
        Ref: SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4F567FE4AE
      AuthorizationType: NONE
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri:
          Fn::Join:
            - ""
            - - "arn:"
              - Ref: AWS::Partition
              - ":apigateway:"
              - Ref: AWS::Region
              - :lambda:path/2015-03-31/functions/
              - Fn::GetAtt:
                  - HelloWorldFunctionB2AB6E79
                  - Arn
              - /invocations
    Metadata:
      aws:cdk:path: SamWithCdkStack/SamWithCdkStackHelloWorldFunction7124C723:ApiEventSourceA7A86A4F/Default/hello/get/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Analytics: v2:deflate64:H4sIAAAAAAAA/1VP207DMAz9lr2nZjCBeN2GeENM4wMmLzXFtEmq2tFUVf13knSI8XQuOT52HuDpEdYrvEhl67bq+AzTh6JtTbJOU4fuXCNMr9Fb5eDN/tPf8gMNjkWSmg2jg+kYOsoPGWcjmxOKkApsMyQNu2hb0h0KGey5QaULjmmORLc9l9ErfaG+C6Mjr9m9Uem+pixZSMqHOFgqnb/iWrTwN9KvUGdrYfP8lyuH5SL2TU68R+2j/htPfB98zVq+eRhThb/bwDPcr1ffwlwN0Ss7guOCP5DsvIpUAQAA
    Metadata:
      aws:cdk:path: SamWithCdkStack/CDKMetadata/Default
    Condition: CDKMetadataAvailable
Outputs:
  SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4FEndpointFD4B5AB4:
    Value:
      Fn::Join:
        - ""
        - - https://
          - Ref: SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4F567FE4AE
          - .execute-api.
          - Ref: AWS::Region
          - "."
          - Ref: AWS::URLSuffix
          - /
          - Ref: SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4FDeploymentStageprod407F57F9
          - /
Conditions:
  CDKMetadataAvailable:
    Fn::Or:
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - af-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ca-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-northwest-1
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-2
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-3
          - Fn::Equals:
              - Ref: AWS::Region
              - me-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - sa-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-2
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-2
Parameters:
  BootstrapVersion:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /cdk-bootstrap/hnb659fds/version
    Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]
Rules:
  CheckBootstrapVersion:
    Assertions:
      - Assert:
          Fn::Not:
            - Fn::Contains:
                - - "1"
                  - "2"
                  - "3"
                  - "4"
                  - "5"
                - Ref: BootstrapVersion
        AssertDescription: CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.
```

</details>

## `SAMCLI Local`でローカル実行
では、出力したテンプレートを利用して今回作成した`API Gateway`×`Lambda`をローカルで動かしてみます。
なお、`SAMCLI Local`は`Docker`が必要になりますので、`Docker`が起動していることは事前に確認してください。
今回は二つのコマンドをそれぞれ実行します。
* `sam local invoke` : Lambda 関数を直接呼び出す
`--no-event`のオプション（Lambdaのeventsに何も含めない）を付与し、`cdk sysnth`で出力したテンプレートを利用するようにパスを指定して実行します。
    ```bash
    $ sam local invoke HelloWorldFunction --no-event -t ./cdk.out/SamWithCdkStack.template.json
    Invoking app.lambda_handler (python3.8)
    Local image is up-to-date
    Using local image: public.ecr.aws/lambda/python:3.8-rapid-x86_64.
    
    Mounting /home/mikmatsu/cdk/sam-with-cdk/hello_world as /var/task:ro,delegated inside runtime container
    START RequestId: 3dfd4c52-d2c2-422b-9c75-b9ace38493ad Version: $LATEST
    END RequestId: 3dfd4c52-d2c2-422b-9c75-b9ace38493ad
    REPORT RequestId: 3dfd4c52-d2c2-422b-9c75-b9ace38493ad  Init Duration: 0.03 ms  Duration: 45.05 ms      Billed Duration: 46 ms     Memory Size: 128 MB     Max Memory Used: 128 MB
    {"statusCode": 200, "body": "{\"message\": \"hello world from SAM and the CDK!\"}"}m
    ```
* `sam local start-api` : API をローカルでホストする
本コマンドを実行すると`Dockerが`起動し、API経由でLambda関数を実行することができます。`Docker`したターミナルでは`curl`コマンドが実行できないため、別のターミナルを起動して、`Docker`のログに表示されたローカルのエンドポイント（`http://127.0.0.1:3000/hello`）に対してアクセスします。
    ```bash:APIのDocker起動
    $ sam local start-api -t ./cdk.out/SamWithCdkStack.template.json
    Mounting LambdaFunctionBF21E41F at http://127.0.0.1:3000/hello [GET]
    You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. If you used sam build before running local commands, you will need to re-run sam build for the changes to be picked up. You only need to restart SAM CLI if you update your AWS SAM template
    2023-02-26 22:40:38  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
    ```
    ```bash:別のコマンドプロンプト経由で実行
    $ curl http://127.0.0.1:3000/hello
    {"message": "hello world from SAM and the CDK!"}
    ```
どちらのコマンドでもLambda関数が正常に動いていることが確認できました。


## `CDK`を利用してAWSにデプロイ
ローカルでの動作検証が完了しましたので、いよいよAWSにデプロイします。
```bash
$ cdk deploy

✨  Synthesis time: 3.42s

SamWithCdkStack: building assets...

[0%] start: Building 44b520949ecc9f814580b3164083d23132e7feb8125f24d991a81d197bc5b22c:current_account-current_region
[0%] start: Building f019d22a0c8a1892a25302dc4918de1c5be4b7803f438a50de068bac70348ba0:current_account-current_region
[50%] success: Built 44b520949ecc9f814580b3164083d23132e7feb8125f24d991a81d197bc5b22c:current_account-current_region
[100%] success: Built f019d22a0c8a1892a25302dc4918de1c5be4b7803f438a50de068bac70348ba0:current_account-current_region

SamWithCdkStack: assets built

{中略}

SamWithCdkStack: creating CloudFormation changeset...

 ✅  SamWithCdkStack

✨  Deployment time: 81.34s

Outputs:
SamWithCdkStack.SamWithCdkStackHelloWorldFunction7124C723ApiEventSourceA7A86A4FEndpointFD4B5AB4 = https://vcwi5z4vzk.execute-api.ap-northeast-1.amazonaws.com/prod/
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:0123456789AB:stack/SamWithCdkStack/9a7b2a00-b5e3-11ed-9379-0e63d8c9e43b

✨  Total time: 84.77s
```
デプロイが完了したので、AWSにデプロイされたアプリケーションを呼び出してみます。
`Outputs`に記載されている`API Gateway`のURLに対して`curl`を叩きます。
```bash
$ curl https://vcwi5z4vzk.execute-api.ap-northeast-1.amazonaws.com/prod/hello
{"message": "hello world from SAM and the CDK!"}
```
想定通りの結果が返ってきました！
これでやりたかったことがすべて完了です。

# 【おまけ】`aws-cdk-lib.aws_sam`を利用した場合の注意点
`SAM`テンプレートのままコード化したい！という方もいらっしゃると思います。
その場合は[aws-cdk-lib.aws_sam](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_sam-readme.html)を利用すればほぼそのまま書くことができます。
```python:sam_with_cdk\sam_with_cdk_stack.py
from aws_cdk import (
    Stack,
    aws_lambda as lambda_,
    aws_sam as sam, # samライブラリ追加
)
from constructs import Construct

function_timeout = 3
function_memory_size = 128

class SamWithCdkStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        #sam書き換え
        lambda_function = sam.CfnFunction(self, "HelloWorldFunction", 
            code_uri="hello_world/",
            handler="app.lambda_handler",
            runtime=lambda_.Runtime.PYTHON_3_8.to_string(),
            events={
                "HelloWorld": sam.CfnFunction.EventSourceProperty(
                    type="Api",
                    properties=sam.CfnFunction.ApiEventProperty(
                        path="/hello",
                        method="get",
                    ),
                )
            },
            timeout=function_timeout,
            memory_size=function_memory_size,
        )
```
ただし、先ほどと同じ`cdk synth --no-staging`実行後に`SAMCLI Local`を行うと失敗する可能性があります。（私は上手くいきませんでした）
```bash
$ cdk synth --no-staging
{実行結果省略}
$ sam local invoke HelloWorldFunction --no-event -t ./cdk.out/SamWithCdkStack.template.json
Invoking app.lambda_handler (python3.8)
Local image is up-to-date
Using local image: public.ecr.aws/lambda/python:3.8-rapid-x86_64.

Mounting /home/mikmatsu/cdk/sam-with-cdk/cdk.out/hello_world as /var/task:ro,delegated inside runtime container
START RequestId: a05c8624-768e-4da7-ad9f-8668f2649c62 Version: $LATEST
Traceback (most recent call last): Unable to import module 'app': No module named 'app'
END RequestId: a05c8624-768e-4da7-ad9f-8668f2649c62
REPORT RequestId: a05c8624-768e-4da7-ad9f-8668f2649c62  Init Duration: 0.88 ms  Duration: 41.41 ms      Billed Duration: 42 ms     Memory Size: 128 MB     Max Memory Used: 128 MB
{"errorMessage": "Unable to import module 'app': No module named 'app'", "errorType": "Runtime.ImportModuleError", "stackTrace": []}m
```
なお、以下のように`cdk synth`の出力結果（yaml）をファイル化してそちらを見るようにしたところ上手く実行することができました。（なぜうまく行くのかは不明です。jsonとyamlで差分があるようには見えないのですが…）
もし`aws-cdk-lib.aws_sam`を利用している方で`SAMCLI Local`が上手くいかなかった方は試してみてください。
```bash
$ cdk synth --no-staging > template.yml
$ sam local invoke HelloWorldFunction --no-event -t template.yml
Invoking app.lambda_handler (python3.8)
Local image is up-to-date
Using local image: public.ecr.aws/lambda/python:3.8-rapid-x86_64.

Mounting /home/mikmatsu/cdk/sam-with-cdk/hello_world as /var/task:ro,delegated inside runtime container
START RequestId: 2bc492a1-e9ac-486b-a35d-3acbef7ce22d Version: $LATEST
{"statusCode": 200, "body": "{\"message\": \"hello world from SAM and the CDK!\"}"}END RequestId: 2bc492a1-e9ac-486b-a35d-3acbef7ce22d
REPORT RequestId: 2bc492a1-e9ac-486b-a35d-3acbef7ce22d  Init Duration: 0.15 ms  Duration: 41.35 ms      Billed Duration: 42 ms     Memory Size: 128 MB     Max Memory Used: 128 MB
```

また、`aws-cdk-lib.aws_sam`を利用した場合、Lambda関数のバンドル処理をどのように行えば良いのかわからず、AWSへのデプロイを試せませんでした。
今回メインの内容ではないので調査は割愛しますが、必要に迫られたら再調査するかもしれません。


# 最後に

今回の検証で、`CDK`と`SAM`が意外と相性良く、どちらもスムーズに動かすことができるということがわかりました。
今までは使い分けを意識することがなく、どちらか一方のフレームワークを選択し利用していたので、今後は二つのフレームワークを上手く使い分けて開発効率を上げていきたいなと思います。

こちらからは以上です。

# 参考URL

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-cdk-getting-started.html