TypeScript の基礎から始める AWS CDK 開発入門 やってみた

AWS CDKのBlackBeltが出てきたのでこれを機にやってみました。  
先にBLEAをもとに書いててリソースはなんとなく出来上がるものの、ちょっと理解があやふやだったのでworkshopやってみようという流れでやりました。

https://catalog.workshops.aws/typescript-and-cdk-for-beginner/ja-JP

https://github.com/aws-samples/baseline-environment-on-aws


## 前提
なお、私はプログラミング言語は新人研修以来ほぼやっておらずterraformでECSをデプロイしたくらいです。  
terraformの方がインフラだけやっていた方にとって書きやすい印象でした。
しかし、BLEA基準で開発することでセキュリティ基準に一定の担保があるならそれをきちんと享受したいので手を出した次第です。

## 実際にCDKに触ってみて
### コンストラクタの話
> コンストラクタとは、クラスからインスタンスが生成された直後に自動実行される処理をあらかじめ定義できるものです。
> コンストラクタでは、デフォルト値を設定できます。

このデフォルト値の設定できるというのがいいなーと思いました。
下のコードはコンストラクタの有り無しを書いてみました。(workshoopで出てくるものです)
```typescript
class Person {
  firstName?: string;
  lastName?: string;
+  // コンストラクタを定義
+  constructor(firstName: string, lastName: string) {
+    this.firstName = firstName;
+    this.lastName = lastName;
+  }
  // greet メソッドを追加
  greet() {
    console.log(`私は ${this.firstName} ${this.lastName} です。`);
  }
}

+ // クラスからオブジェクトを生成する際、引数を渡してプロパティを初期化しています。
+ const taro = new Person("AWS", "太郎");
- const taro = new Person();
//次郎も生成してみましょう。
const jiro = new Person("Amazon","次郎");

- taro.firstName = "AWS";
- taro.lastName = "太郎";

// greet メソッドを使用
taro.greet();
+ jiro.greet();
```
デフォルト値が入ったものを呼び出し、必要があればこちらで定義して呼び出せます。  
同じように下記にある通り大量にデフォルトとなるものが準備されており、定義されているコンストラクトを呼び出すことでリソースを定義できます。

https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts

このコンストラクトを呼び出すだけで、デフォルト値が既に入ったものが呼び出せます。  
仮に下記のコードを`cdk synth`コマンドでcloudformationテンプレートを出力します。
```typescript
import { Duration, Stack, StackProps } from 'aws-cdk-lib';
import * as ec2 from "aws-cdk-lib/aws-ec2";
import { Construct } from 'constructs';

export class CdkWorkshopStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    const vpc = new ec2.Vpc(this, "BlogVpc", {
      ipAddresses: ec2.IpAddresses.cidr('10.30.0.0/16'),
    });
  }
}
```

<details>
<summary>出力されるCloudformationテンプレート</summary>
```yaml
{
 "Resources": {
  "BlogVpc9559DF64": {
   "Type": "AWS::EC2::VPC",
   "Properties": {
    "CidrBlock": "10.30.0.0/16",
    "EnableDnsHostnames": true,
    "EnableDnsSupport": true,
    "InstanceTenancy": "default",
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdkWorkshopStack/BlogVpc"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/Resource"
   }
  },
  "BlogVpcPublicSubnet1Subnet4A2220C5": {
   "Type": "AWS::EC2::Subnet",
   "Properties": {
    "AvailabilityZone": {
     "Fn::Select": [
      0,
      {
       "Fn::GetAZs": ""
      }
     ]
    },
    "CidrBlock": "10.30.0.0/18",
    "MapPublicIpOnLaunch": true,
    "Tags": [
     {
      "Key": "aws-cdk:subnet-name",
      "Value": "Public"
     },
     {
      "Key": "aws-cdk:subnet-type",
      "Value": "Public"
     },
     {
      "Key": "Name",
      "Value": "CdkWorkshopStack/BlogVpc/PublicSubnet1"
     }
    ],
    "VpcId": {
     "Ref": "BlogVpc9559DF64"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PublicSubnet1/Subnet"
   }
  },
  "BlogVpcPublicSubnet1RouteTable70CAF219": {
   "Type": "AWS::EC2::RouteTable",
   "Properties": {
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdkWorkshopStack/BlogVpc/PublicSubnet1"
     }
    ],
    "VpcId": {
     "Ref": "BlogVpc9559DF64"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PublicSubnet1/RouteTable"
   }
  },
  "BlogVpcPublicSubnet1RouteTableAssociationDB2B9EA6": {
   "Type": "AWS::EC2::SubnetRouteTableAssociation",
   "Properties": {
    "RouteTableId": {
     "Ref": "BlogVpcPublicSubnet1RouteTable70CAF219"
    },
    "SubnetId": {
     "Ref": "BlogVpcPublicSubnet1Subnet4A2220C5"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PublicSubnet1/RouteTableAssociation"
   }
  },
  "BlogVpcPublicSubnet1DefaultRouteF15535D9": {
   "Type": "AWS::EC2::Route",
   "Properties": {
    "DestinationCidrBlock": "0.0.0.0/0",
    "GatewayId": {
     "Ref": "BlogVpcIGWD106C243"
    },
    "RouteTableId": {
     "Ref": "BlogVpcPublicSubnet1RouteTable70CAF219"
    }
   },
   "DependsOn": [
    "BlogVpcVPCGWAC37039A"
   ],
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PublicSubnet1/DefaultRoute"
   }
  },
  "BlogVpcPublicSubnet1EIPF2ED1F25": {
   "Type": "AWS::EC2::EIP",
   "Properties": {
    "Domain": "vpc",
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdkWorkshopStack/BlogVpc/PublicSubnet1"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PublicSubnet1/EIP"
   }
  },
  "BlogVpcPublicSubnet1NATGateway3C7A1491": {
   "Type": "AWS::EC2::NatGateway",
   "Properties": {
    "AllocationId": {
     "Fn::GetAtt": [
      "BlogVpcPublicSubnet1EIPF2ED1F25",
      "AllocationId"
     ]
    },
    "SubnetId": {
     "Ref": "BlogVpcPublicSubnet1Subnet4A2220C5"
    },
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdkWorkshopStack/BlogVpc/PublicSubnet1"
     }
    ]
   },
   "DependsOn": [
    "BlogVpcPublicSubnet1DefaultRouteF15535D9",
    "BlogVpcPublicSubnet1RouteTableAssociationDB2B9EA6"
   ],
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PublicSubnet1/NATGateway"
   }
  },
  "BlogVpcPublicSubnet2SubnetDFEB9EDC": {
   "Type": "AWS::EC2::Subnet",
   "Properties": {
    "AvailabilityZone": {
     "Fn::Select": [
      1,
      {
       "Fn::GetAZs": ""
      }
     ]
    },
    "CidrBlock": "10.30.64.0/18",
    "MapPublicIpOnLaunch": true,
    "Tags": [
     {
      "Key": "aws-cdk:subnet-name",
      "Value": "Public"
     },
     {
      "Key": "aws-cdk:subnet-type",
      "Value": "Public"
     },
     {
      "Key": "Name",
      "Value": "CdkWorkshopStack/BlogVpc/PublicSubnet2"
     }
    ],
    "VpcId": {
     "Ref": "BlogVpc9559DF64"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PublicSubnet2/Subnet"
   }
  },
  "BlogVpcPublicSubnet2RouteTable84500B61": {
   "Type": "AWS::EC2::RouteTable",
   "Properties": {
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdkWorkshopStack/BlogVpc/PublicSubnet2"
     }
    ],
    "VpcId": {
     "Ref": "BlogVpc9559DF64"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PublicSubnet2/RouteTable"
   }
  },
  "BlogVpcPublicSubnet2RouteTableAssociation5693651E": {
   "Type": "AWS::EC2::SubnetRouteTableAssociation",
   "Properties": {
    "RouteTableId": {
     "Ref": "BlogVpcPublicSubnet2RouteTable84500B61"
    },
    "SubnetId": {
     "Ref": "BlogVpcPublicSubnet2SubnetDFEB9EDC"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PublicSubnet2/RouteTableAssociation"
   }
  },
  "BlogVpcPublicSubnet2DefaultRouteDD57F5F7": {
   "Type": "AWS::EC2::Route",
   "Properties": {
    "DestinationCidrBlock": "0.0.0.0/0",
    "GatewayId": {
     "Ref": "BlogVpcIGWD106C243"
    },
    "RouteTableId": {
     "Ref": "BlogVpcPublicSubnet2RouteTable84500B61"
    }
   },
   "DependsOn": [
    "BlogVpcVPCGWAC37039A"
   ],
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PublicSubnet2/DefaultRoute"
   }
  },
  "BlogVpcPublicSubnet2EIPE44021FB": {
   "Type": "AWS::EC2::EIP",
   "Properties": {
    "Domain": "vpc",
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdkWorkshopStack/BlogVpc/PublicSubnet2"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PublicSubnet2/EIP"
   }
  },
  "BlogVpcPublicSubnet2NATGateway732D55AF": {
   "Type": "AWS::EC2::NatGateway",
   "Properties": {
    "AllocationId": {
     "Fn::GetAtt": [
      "BlogVpcPublicSubnet2EIPE44021FB",
      "AllocationId"
     ]
    },
    "SubnetId": {
     "Ref": "BlogVpcPublicSubnet2SubnetDFEB9EDC"
    },
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdkWorkshopStack/BlogVpc/PublicSubnet2"
     }
    ]
   },
   "DependsOn": [
    "BlogVpcPublicSubnet2DefaultRouteDD57F5F7",
    "BlogVpcPublicSubnet2RouteTableAssociation5693651E"
   ],
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PublicSubnet2/NATGateway"
   }
  },
  "BlogVpcPrivateSubnet1Subnet56A2E581": {
   "Type": "AWS::EC2::Subnet",
   "Properties": {
    "AvailabilityZone": {
     "Fn::Select": [
      0,
      {
       "Fn::GetAZs": ""
      }
     ]
    },
    "CidrBlock": "10.30.128.0/18",
    "MapPublicIpOnLaunch": false,
    "Tags": [
     {
      "Key": "aws-cdk:subnet-name",
      "Value": "Private"
     },
     {
      "Key": "aws-cdk:subnet-type",
      "Value": "Private"
     },
     {
      "Key": "Name",
      "Value": "CdkWorkshopStack/BlogVpc/PrivateSubnet1"
     }
    ],
    "VpcId": {
     "Ref": "BlogVpc9559DF64"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PrivateSubnet1/Subnet"
   }
  },
  "BlogVpcPrivateSubnet1RouteTableEB42F3A9": {
   "Type": "AWS::EC2::RouteTable",
   "Properties": {
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdkWorkshopStack/BlogVpc/PrivateSubnet1"
     }
    ],
    "VpcId": {
     "Ref": "BlogVpc9559DF64"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PrivateSubnet1/RouteTable"
   }
  },
  "BlogVpcPrivateSubnet1RouteTableAssociationD3C879DB": {
   "Type": "AWS::EC2::SubnetRouteTableAssociation",
   "Properties": {
    "RouteTableId": {
     "Ref": "BlogVpcPrivateSubnet1RouteTableEB42F3A9"
    },
    "SubnetId": {
     "Ref": "BlogVpcPrivateSubnet1Subnet56A2E581"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PrivateSubnet1/RouteTableAssociation"
   }
  },
  "BlogVpcPrivateSubnet1DefaultRoute2CF3EF85": {
   "Type": "AWS::EC2::Route",
   "Properties": {
    "DestinationCidrBlock": "0.0.0.0/0",
    "NatGatewayId": {
     "Ref": "BlogVpcPublicSubnet1NATGateway3C7A1491"
    },
    "RouteTableId": {
     "Ref": "BlogVpcPrivateSubnet1RouteTableEB42F3A9"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PrivateSubnet1/DefaultRoute"
   }
  },
  "BlogVpcPrivateSubnet2SubnetB653C383": {
   "Type": "AWS::EC2::Subnet",
   "Properties": {
    "AvailabilityZone": {
     "Fn::Select": [
      1,
      {
       "Fn::GetAZs": ""
      }
     ]
    },
    "CidrBlock": "10.30.192.0/18",
    "MapPublicIpOnLaunch": false,
    "Tags": [
     {
      "Key": "aws-cdk:subnet-name",
      "Value": "Private"
     },
     {
      "Key": "aws-cdk:subnet-type",
      "Value": "Private"
     },
     {
      "Key": "Name",
      "Value": "CdkWorkshopStack/BlogVpc/PrivateSubnet2"
     }
    ],
    "VpcId": {
     "Ref": "BlogVpc9559DF64"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PrivateSubnet2/Subnet"
   }
  },
  "BlogVpcPrivateSubnet2RouteTableF6F33DA8": {
   "Type": "AWS::EC2::RouteTable",
   "Properties": {
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdkWorkshopStack/BlogVpc/PrivateSubnet2"
     }
    ],
    "VpcId": {
     "Ref": "BlogVpc9559DF64"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PrivateSubnet2/RouteTable"
   }
  },
  "BlogVpcPrivateSubnet2RouteTableAssociationAEBD8FFC": {
   "Type": "AWS::EC2::SubnetRouteTableAssociation",
   "Properties": {
    "RouteTableId": {
     "Ref": "BlogVpcPrivateSubnet2RouteTableF6F33DA8"
    },
    "SubnetId": {
     "Ref": "BlogVpcPrivateSubnet2SubnetB653C383"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PrivateSubnet2/RouteTableAssociation"
   }
  },
  "BlogVpcPrivateSubnet2DefaultRouteB835D437": {
   "Type": "AWS::EC2::Route",
   "Properties": {
    "DestinationCidrBlock": "0.0.0.0/0",
    "NatGatewayId": {
     "Ref": "BlogVpcPublicSubnet2NATGateway732D55AF"
    },
    "RouteTableId": {
     "Ref": "BlogVpcPrivateSubnet2RouteTableF6F33DA8"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/PrivateSubnet2/DefaultRoute"
   }
  },
  "BlogVpcIGWD106C243": {
   "Type": "AWS::EC2::InternetGateway",
   "Properties": {
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdkWorkshopStack/BlogVpc"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/IGW"
   }
  },
  "BlogVpcVPCGWAC37039A": {
   "Type": "AWS::EC2::VPCGatewayAttachment",
   "Properties": {
    "InternetGatewayId": {
     "Ref": "BlogVpcIGWD106C243"
    },
    "VpcId": {
     "Ref": "BlogVpc9559DF64"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/VPCGW"
   }
  },
  "BlogVpcRestrictDefaultSecurityGroupCustomResource1A79C7D0": {
   "Type": "Custom::VpcRestrictDefaultSG",
   "Properties": {
    "ServiceToken": {
     "Fn::GetAtt": [
      "CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E",
      "Arn"
     ]
    },
    "DefaultSecurityGroupId": {
     "Fn::GetAtt": [
      "BlogVpc9559DF64",
      "DefaultSecurityGroup"
     ]
    },
    "Account": {
     "Ref": "AWS::AccountId"
    }
   },
   "UpdateReplacePolicy": "Delete",
   "DeletionPolicy": "Delete",
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/BlogVpc/RestrictDefaultSecurityGroupCustomResource/Default"
   }
  },
  "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0": {
   "Type": "AWS::IAM::Role",
   "Properties": {
    "AssumeRolePolicyDocument": {
     "Version": "2012-10-17",
     "Statement": [
      {
       "Action": "sts:AssumeRole",
       "Effect": "Allow",
       "Principal": {
        "Service": "lambda.amazonaws.com"
       }
      }
     ]
    },
    "ManagedPolicyArns": [
     {
      "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
     }
    ],
    "Policies": [
     {
      "PolicyName": "Inline",
      "PolicyDocument": {
       "Version": "2012-10-17",
       "Statement": [
        {
         "Effect": "Allow",
         "Action": [
          "ec2:AuthorizeSecurityGroupIngress",
          "ec2:AuthorizeSecurityGroupEgress",
          "ec2:RevokeSecurityGroupIngress",
          "ec2:RevokeSecurityGroupEgress"
         ],
         "Resource": [
          {
           "Fn::Join": [
            "",
            [
             "arn:",
             {
              "Ref": "AWS::Partition"
             },
             ":ec2:",
             {
              "Ref": "AWS::Region"
             },
             ":",
             {
              "Ref": "AWS::AccountId"
             },
             ":security-group/",
             {
              "Fn::GetAtt": [
               "BlogVpc9559DF64",
               "DefaultSecurityGroup"
              ]
             }
            ]
           ]
          }
         ]
        }
       ]
      }
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Role"
   }
  },
  "CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E": {
   "Type": "AWS::Lambda::Function",
   "Properties": {
    "Code": {
     "S3Bucket": {
      "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
     },
     "S3Key": "7f18a11296f35510ee16538afec983ed6312e12afbf81b777089a9f8e34e2474.zip"
    },
    "Timeout": 900,
    "MemorySize": 128,
    "Handler": "__entrypoint__.handler",
    "Role": {
     "Fn::GetAtt": [
      "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0",
      "Arn"
     ]
    },
    "Runtime": "nodejs18.x",
    "Description": "Lambda function for removing all inbound/outbound rules from the VPC default security group"
   },
   "DependsOn": [
    "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0"
   ],
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler",
    "aws:asset:path": "asset.7f18a11296f35510ee16538afec983ed6312e12afbf81b777089a9f8e34e2474",
    "aws:asset:property": "Code"
   }
  },
  "CDKMetadata": {
   "Type": "AWS::CDK::Metadata",
   "Properties": {
    "Analytics": "v2:deflate64:H4sIAAAAAAAA/21PwarCMBD8Fu9p1IIHj1JEvEio4lXSdNXVdiPJpiLiv78En+biaWZnh9nZUs7ncjLSd1+Y9lp02MjnlrW5iigdwJTyub8ZUR1pryqhQtOh2YaGgJOWWW0Dw043HWQ9awvvrUHNaOlrTmS5Vgk2mlea4a4fQjkcIs3Ba2JwkX8M7yb/04Jj1XMPxC9Rg7fBmZgbPNs+j/He75VydsAWnIj1gOPXJ6RT8leWWkxlX4JsC/Lix0M5kTM5HV08YuECMfYg6zf+AZU4vHlEAQAA"
   },
   "Metadata": {
    "aws:cdk:path": "CdkWorkshopStack/CDKMetadata/Default"
   },
   "Condition": "CDKMetadataAvailable"
  }
 },
 "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>

というように、サブネットもルートテーブルも自動で作成してくれます。   
Cloudformationテンプレートはyaml形式ファイルゆえに初心者でも書きやすいのですが、  
リソースを多く定義しているとどこからどこまでがこのリソースを定義しているのかわからなくなってくるのでいいですね。

### 静的解析の話
> プログラムを実行せずにコードの誤りを検出することを静的解析と呼びます。 TypeScript は、静的型付けを用いてエラーがないかどうかをチェックし、上記の JavaScript の課題を解決します。  
> 　- 『TypeScript の概要』の抜粋

実際にVSCodeで作業している際にこの威力を何度も思い知りました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3330185/932e60db-4ec7-0a39-8a62-b96597907db1.png)
Cloudformationテンプレートを書いていて困るのはちょっとした書き間違いに気づくのにコマンドが必要なんですが、  
コマンドを打つ前から静的解析で今書いているコードがあっているか簡単にチェックできるという点です。

## 感想
初めに書いた通りですが、インフラ周りをやっていた身としてはTerraformの方が最初のハードルは低いのかなとは思います。  
L２コンストラクトを使うときにどのリソースが作られるのか結局調べることになりますし、  
プログラミング言語あまり触らない人にとってオブジェクト指向が壁に感じます。  
が、L2やL3コンストラクトをうまく使うことで"わかりきった設定"を記述しなくてもよいのは明らかなメリットかなと思いますし、  
"分かりきった設定"を抽象化してアプリとインフラの壁をなるべく取り払って開発しようという意図は垣間見ることはできました。