はじめに
皆さん初めまして。長谷川です。
1本目の投稿になります。
突然ですが、皆さんはAWSのリソース構築をどのように行っていますか?
AWS CloudFormationをはじめ様々なIaCツールが活用できるAWSですが、上手く活用できておりますでしょうか?
かくいう私も細かな検証作業などではマネコンからポチポチっとやってしまうことが多く、中々活用できていないと感じています。
今回は案件での活用も増えている「AWS CDK」を利用したリソース構築について着目したいと思います。
具体的にはCDKコンストラクトの特徴について焦点を当てて見ていきます。
AWS CDKとは?
AWS公式サイトで確認すると以下のような記述になっています。
AWS Cloud Development Kit (AWS CDK) は、コードでクラウドインフラストラクチャを定義して AWS CloudFormation を通じてプロビジョニングするオープンソースのソフトウェア開発フレームワークです。
—AWS CDK とはより引用
TypeScript、JavaScript、Python、Java、C#/.Net、Goといったプログラミング言語でクラウドコンポーネントを定義できることがAWS CDKの大きな特徴ですが、実際に裏で動くのはAWS CloudFormationであることも大事なポイントですね。
リソース作成失敗時の自動ロールバックやリソース設定の不整合を防ぐドリフト検出といったCloudFormationの強みをそのまま活用することができます。
AWS CDK コンストラクト
本記事ではAWS CDKを利用する強みの一つである「抽象化」を支えるAWS CDK コンストラクトについて注目したいと思います。
こちらについてもAWS公式サイトの定義を紹介します。
コンストラクトは、AWS Cloud Development Kit (AWS CDK) アプリケーションの基本的な構成要素です。コンストラクトは、1 つ以上の AWS CloudFormation リソースとその設定を表すアプリケーション内のコンポーネントです。コンストラクトをインポートして設定することで、アプリケーションを 1 つずつ構築していきます。
—AWS CDK コンストラクトより引用
AWS CDKではコンストラクトを基にコードの記述を行うことで、リソースの定義をしていきます。
そして、コンストラクトのレベルとして次の3段階が用意されています。
コンストラクトレベル | 説明(AWS CDK コンストラクトより引用) |
レベル1(L1) | L1 コンストラクトは、CFN リソース とも呼ばれ、最下位のコンストラクトであり、抽象化は行いません。各 L1 コンストラクトは、単一の AWS CloudFormation リソースに直接マッピングされます。 |
レベル2(L2) | L1 コンストラクトと比較して、L2 コンストラクトは直感的なインテントベースの API を通してより高度な抽象化を提供します。L2 コンストラクトには、適切なデフォルトプロパティ設定、ベストプラクティスのセキュリティポリシー、および多くのボイラープレートコードやグルーロジックを生成する機能が含まれています。 |
レベル3(L3) | L3 コンストラクトは、パターンとも呼ばれ、最も高度な抽象レベルです。各 L3 コンストラクトには、アプリケーション内の特定のタスクまたはサービスを達成するために連携するように設定された、複数リソースの集合を含めることができます。L3 コンストラクトは、アプリケーション内の特定のユースケースの AWS アーキテクチャ全体を作成するために使用されます。 |
表内で強調している「抽象化の有無」について気になりました。
AWS CDKというと必要なパラメータのみ設定し、残りはベストプラクティスに基づき「よしなに」やってくれるものと捉えていましたが、そうではないのでしょうか?
実際にレベル1、レベル2コンストラクトのそれぞれを使用してリソース作成(S3,VPC)を試してみました。
上記のコマンドを使用することで、実際にリソースをデプロイする前にCloudFormationのテンプレートに落とし込んだ結果を確認することができます。
S3
まずはS3バケットの作成について、両者の結果の違いを示します。
実行環境は以下の通りです。
Node.js | 10.9.2 |
TypeScript | 5.7.3 |
AWS CDK | 2.178.0 |
また、実行環境の構築やコードの記載には「TypeScript の基礎から始める AWS CDK 開発入門」を参考にしました。
まだまだTypeScriptを使用した経験が浅いので、ここで紹介されているハンズオンについても一度じっくりと試してみたいと考えています。
L1コンストラクト
L1コンストラクトを使用したS3作成のTypeScriptコードは以下の通りです。
class CfnBucket (construct)
import { Duration, Stack, StackProps } from 'aws-cdk-lib';
import * as s3 from "aws-cdk-lib/aws-s3";
import { Construct } from 'constructs';
export class WorkStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// s3 を宣言
const s3Bucket = new s3.CfnBucket(this, "s3-L1-Construct", {
});
}
}
cdk synthコマンドを使用し、CloudFormationテンプレートを出力すると以下の通りです。
[Properties]の設定等は特になく、真っ新なS3バケットが一つ作成されました。
L2コンストラクト
続いてL2コンストラクトを使用してS3バケットを作成する場合です。
class Bucket (construct)
import { Duration, Stack, StackProps } from 'aws-cdk-lib';
import * as s3 from "aws-cdk-lib/aws-s3";
import { Construct } from 'constructs';
export class WorkStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// s3 を宣言
const s3Bucket = new s3.Bucket(this, "s3-L2-Construct", {
});
}
}
cdk synthコマンドを使用し、CloudFormationテンプレートを出力すると以下の通りです。
[Properties]の設定等は同じくありませんが、以下の項目が追加されているようです。
- UpdateReplacePolicy: Retain
- DeletionPolicy: Retain
これらはスタックの変更・削除時に対象のリソースを保持させるポリシーです。
誤った変更などをしてしまった際の保険になりますね。
しかし、L1とL2の違いとしてはそれほど大きくないですね。
続いてVPCの例を見てみましょう。
VPC
L1コンストラクト
L1コンストラクトを使用したVPC作成のTypeScriptコードは以下の通りです。
パラメータとしてCIDR情報が必須のため、その設定のみ記載しています。
import { Duration, Stack, StackProps } from 'aws-cdk-lib';
import * as ec2 from "aws-cdk-lib/aws-ec2";
import { Construct } from 'constructs';
export class WorkStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// vpc を宣言
const vpc = new ec2.CfnVPC(this, "VPC-L1-Construct", {
cidrBlock: '10.0.0.0/16',
});
}
}
cdk synthコマンドを使用し、CloudFormationテンプレートを出力すると以下の通りです。
[Properties]について確認すると、[CidrBlock]の情報のみが保持されていることが分かります。
ただVPCとしての枠だけが用意される形ですね。
結局何か作業をするには、サブネットやゲートウェイなどまだまだ要素が足りていません。
L2コンストラクト
次に、L2コンストラクトです。
なんとパラメータとして必須のものが存在しません!
コードとして実行可能な最小限の記載をしています。
import { Duration, Stack, StackProps } from 'aws-cdk-lib';
import * as ec2 from "aws-cdk-lib/aws-ec2";
import { Construct } from 'constructs';
export class WorkStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// vpc を宣言
const vpc = new ec2.Vpc(this, "VPC-L2-Construct", {
});
}
}
cdk synthコマンドを使用し、CloudFormationテンプレートを出力すると以下の通りです。
かなり長いですので、飛ばしていただいて大丈夫です。
Resources:
VPCL2ConstructEE410864:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: Name
Value: WorkStack/VPC-L2-Construct
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/Resource
VPCL2ConstructPublicSubnet1SubnetCC74A439:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
CidrBlock: 10.0.0.0/18
MapPublicIpOnLaunch: true
Tags:
- Key: aws-cdk:subnet-name
Value: Public
- Key: aws-cdk:subnet-type
Value: Public
- Key: Name
Value: WorkStack/VPC-L2-Construct/PublicSubnet1
VpcId:
Ref: VPCL2ConstructEE410864
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet1/Subnet
VPCL2ConstructPublicSubnet1RouteTable88DC6194:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: WorkStack/VPC-L2-Construct/PublicSubnet1
VpcId:
Ref: VPCL2ConstructEE410864
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet1/RouteTable
VPCL2ConstructPublicSubnet1RouteTableAssociation731F896E:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: VPCL2ConstructPublicSubnet1RouteTable88DC6194
SubnetId:
Ref: VPCL2ConstructPublicSubnet1SubnetCC74A439
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet1/RouteTableAssociation
VPCL2ConstructPublicSubnet1DefaultRoute5581F2A1:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: VPCL2ConstructIGW1021B62D
RouteTableId:
Ref: VPCL2ConstructPublicSubnet1RouteTable88DC6194
DependsOn:
- VPCL2ConstructVPCGW6FBE15DE
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet1/DefaultRoute
VPCL2ConstructPublicSubnet1EIP1D9D41B6:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: WorkStack/VPC-L2-Construct/PublicSubnet1
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet1/EIP
VPCL2ConstructPublicSubnet1NATGateway9B0E92D1:
Type: AWS::EC2::NatGateway
Properties:
AllocationId:
Fn::GetAtt:
- VPCL2ConstructPublicSubnet1EIP1D9D41B6
- AllocationId
SubnetId:
Ref: VPCL2ConstructPublicSubnet1SubnetCC74A439
Tags:
- Key: Name
Value: WorkStack/VPC-L2-Construct/PublicSubnet1
DependsOn:
- VPCL2ConstructPublicSubnet1DefaultRoute5581F2A1
- VPCL2ConstructPublicSubnet1RouteTableAssociation731F896E
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet1/NATGateway
VPCL2ConstructPublicSubnet2Subnet9562D1FC:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
CidrBlock: 10.0.64.0/18
MapPublicIpOnLaunch: true
Tags:
- Key: aws-cdk:subnet-name
Value: Public
- Key: aws-cdk:subnet-type
Value: Public
- Key: Name
Value: WorkStack/VPC-L2-Construct/PublicSubnet2
VpcId:
Ref: VPCL2ConstructEE410864
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet2/Subnet
VPCL2ConstructPublicSubnet2RouteTable24B41773:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: WorkStack/VPC-L2-Construct/PublicSubnet2
VpcId:
Ref: VPCL2ConstructEE410864
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet2/RouteTable
VPCL2ConstructPublicSubnet2RouteTableAssociation8AEA9A7C:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: VPCL2ConstructPublicSubnet2RouteTable24B41773
SubnetId:
Ref: VPCL2ConstructPublicSubnet2Subnet9562D1FC
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet2/RouteTableAssociation
VPCL2ConstructPublicSubnet2DefaultRouteC8976C2A:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: VPCL2ConstructIGW1021B62D
RouteTableId:
Ref: VPCL2ConstructPublicSubnet2RouteTable24B41773
DependsOn:
- VPCL2ConstructVPCGW6FBE15DE
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet2/DefaultRoute
VPCL2ConstructPublicSubnet2EIPB8510F82:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: WorkStack/VPC-L2-Construct/PublicSubnet2
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet2/EIP
VPCL2ConstructPublicSubnet2NATGatewayEAF78599:
Type: AWS::EC2::NatGateway
Properties:
AllocationId:
Fn::GetAtt:
- VPCL2ConstructPublicSubnet2EIPB8510F82
- AllocationId
SubnetId:
Ref: VPCL2ConstructPublicSubnet2Subnet9562D1FC
Tags:
- Key: Name
Value: WorkStack/VPC-L2-Construct/PublicSubnet2
DependsOn:
- VPCL2ConstructPublicSubnet2DefaultRouteC8976C2A
- VPCL2ConstructPublicSubnet2RouteTableAssociation8AEA9A7C
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet2/NATGateway
VPCL2ConstructPrivateSubnet1SubnetE5C7047B:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
CidrBlock: 10.0.128.0/18
MapPublicIpOnLaunch: false
Tags:
- Key: aws-cdk:subnet-name
Value: Private
- Key: aws-cdk:subnet-type
Value: Private
- Key: Name
Value: WorkStack/VPC-L2-Construct/PrivateSubnet1
VpcId:
Ref: VPCL2ConstructEE410864
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet1/Subnet
VPCL2ConstructPrivateSubnet1RouteTable9ABEBB2E:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: WorkStack/VPC-L2-Construct/PrivateSubnet1
VpcId:
Ref: VPCL2ConstructEE410864
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet1/RouteTable
VPCL2ConstructPrivateSubnet1RouteTableAssociationAFAD682E:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: VPCL2ConstructPrivateSubnet1RouteTable9ABEBB2E
SubnetId:
Ref: VPCL2ConstructPrivateSubnet1SubnetE5C7047B
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet1/RouteTableAssociation
VPCL2ConstructPrivateSubnet1DefaultRoute592FE75C:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId:
Ref: VPCL2ConstructPublicSubnet1NATGateway9B0E92D1
RouteTableId:
Ref: VPCL2ConstructPrivateSubnet1RouteTable9ABEBB2E
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet1/DefaultRoute
VPCL2ConstructPrivateSubnet2SubnetD1EAC9D8:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
CidrBlock: 10.0.192.0/18
MapPublicIpOnLaunch: false
Tags:
- Key: aws-cdk:subnet-name
Value: Private
- Key: aws-cdk:subnet-type
Value: Private
- Key: Name
Value: WorkStack/VPC-L2-Construct/PrivateSubnet2
VpcId:
Ref: VPCL2ConstructEE410864
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet2/Subnet
VPCL2ConstructPrivateSubnet2RouteTable0C21B8AE:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: WorkStack/VPC-L2-Construct/PrivateSubnet2
VpcId:
Ref: VPCL2ConstructEE410864
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet2/RouteTable
VPCL2ConstructPrivateSubnet2RouteTableAssociation91E37B6F:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: VPCL2ConstructPrivateSubnet2RouteTable0C21B8AE
SubnetId:
Ref: VPCL2ConstructPrivateSubnet2SubnetD1EAC9D8
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet2/RouteTableAssociation
VPCL2ConstructPrivateSubnet2DefaultRoute99DCB01B:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId:
Ref: VPCL2ConstructPublicSubnet2NATGatewayEAF78599
RouteTableId:
Ref: VPCL2ConstructPrivateSubnet2RouteTable0C21B8AE
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet2/DefaultRoute
VPCL2ConstructIGW1021B62D:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: WorkStack/VPC-L2-Construct
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/IGW
VPCL2ConstructVPCGW6FBE15DE:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId:
Ref: VPCL2ConstructIGW1021B62D
VpcId:
Ref: VPCL2ConstructEE410864
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/VPCGW
VPCL2ConstructRestrictDefaultSecurityGroupCustomResource76DD52E2:
Type: Custom::VpcRestrictDefaultSG
Properties:
ServiceToken:
Fn::GetAtt:
- CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E
- Arn
DefaultSecurityGroupId:
Fn::GetAtt:
- VPCL2ConstructEE410864
- DefaultSecurityGroup
Account:
Ref: AWS::AccountId
UpdateReplacePolicy: Delete
DeletionPolicy: Delete
Metadata:
aws:cdk:path: WorkStack/VPC-L2-Construct/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:
- VPCL2ConstructEE410864
- DefaultSecurityGroup
Metadata:
aws:cdk:path: WorkStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Role
CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E:
Type: AWS::Lambda::Function
Properties:
Code:
S3Bucket:
Fn::Sub: cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}
S3Key: 7fa1e366ee8a9ded01fc355f704cff92bfd179574e6f9cfee800a3541df1b200.zip
Timeout: 900
MemorySize: 128
Handler: __entrypoint__.handler
Role:
Fn::GetAtt:
- CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0
- Arn
Runtime: nodejs20.x
Description: Lambda function for removing all inbound/outbound rules from the VPC default security group
DependsOn:
- CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0
Metadata:
aws:cdk:path: WorkStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler
aws:asset:path: asset.7fa1e366ee8a9ded01fc355f704cff92bfd179574e6f9cfee800a3541df1b200
aws:asset:property: Code
CDKMetadata:
Type: AWS::CDK::Metadata
Properties:
Analytics: v2:deflate64:H4sIAAAAAAAA/82UTWvCQBCGf4t7lHVbU7DFWwylBEoNWjxUpEx2R11NdsPuJCLify8mfoDQU3vIaWbeHYaH2ZcJRP/5RTx2YOd7Um17mU7FYUogtxx2/htlIA6zQs5Zly14tDSzJOJJmWZaTsvUIM0PDCrQGaQ607T/sgbZkHUZZ1UhY3XOpVZulFm5Pdc5FM2QuBibdyiNXLMhuRI500U1iO7awXu9MnFRDUKlHHo/NpFDIG1N3XHkLaGoQZT6AHoDwh3s2bDeXGsA20Hx65pqizXGOmUTWxJ+QprhTb9pofdW6nrwtfmUvMbJKdym88TpCgj/3bFLyHwL/uJvGO0haQlG48LYEDqDVw81t+9chUQg1zkaOvIJels6ifMFj0pPNr8Kl5t5Ee7eE2crrdCNwCMPvUeaEqy0WR25sQrFxj9UQSD6T6Lf2Xite640pHMUkyb+AI/mB4K7BQAA
Metadata:
aws:cdk:path: WorkStack/CDKMetadata/Default
Condition: CDKMetadataAvailable
Conditions:
--以下省略--
作成されたリソースについてまとめると以下の通りです。
-
- VPC(10.0.0.0/16)
-IGWの紐付け
-
- PublicSubnet1(10.0.0.0/18)
-RouteTable,EIP,NAT Gatewayの紐付け
-
- PublicSubnet2(10.0.64.0/18)
-RouteTable,EIP,NAT Gatewayの紐付け
-
- PrivateSubnet1(10.0.128.0/18)
-RouteTableの紐付け
-
- PrivateSubnet2(10.0.192.0/18)
-RouteTableの紐付け
- デフォルトセキュリティグループ
-
- IAMロール
-Lambda実行
-
- Lambda Function
-デフォルトセキュリティグループからインバウンド・アウトバウンドルールを削除
詳細な設定をしなくても一気にリソースが作成されるのはすごいですね!
ネットワークへの接続経路が確保され、Public/Privateサブネットも用意されたため、すぐにサーバ等を立てて作業を開始することが出来そうです。
L2コンストラクトを使用することで「よしなに」色々とリソースがされることが分かりました。
まとめ
AWS公式サイトにて「L1コンストラクトは、単一のAWS CloudFormationリソースに直接マッピングされます。」と記載があるように、L1コンストラクトはあくまで設定されたパラメータに忠実にリソースを作成することが分かりました。
また、L2コンストラクトを使用することで、あまりAWSに詳しくなくても簡単に必要なリソースが作成されるのは面白い点だと思いました。
しかし、今回のVPCの場合のように、「そこまで作らなくても...」と感じることもあるかもしれません。
「よしなに」やってくれるのは大変便利ですが、最低限必要なパラメータを設定するスキルは必要ですね。
ただ、やはりリソースの作成負担の軽減や変更のしやすさは大きなメリットになるかと思いますので、上手く使っていきたいですね。
最後まで読んでいただきありがとうございます。
もし参考になりましたら幸いです。