Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
CDK引数設計道場100本ノック
Search
kazuho cryer-shinozuka
July 10, 2025
Programming
990
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
CDK引数設計道場100本ノック
kazuho cryer-shinozuka
July 10, 2025
More Decks by kazuho cryer-shinozuka
See All by kazuho cryer-shinozuka
俺的CDKアップデートin2025 〜Enhanced L1にアラカルト添え〜
badmintoncryer
2
92
EC2 Instance Connect Endpoint をCDKで使い倒そう
badmintoncryer
0
350
CDKコントリビュートの最初の壁を越えよう! -簡単issueの見つけ方-
badmintoncryer
2
1.3k
Other Decks in Programming
See All in Programming
Signal Forms: Beyond the Basics @ngBaguette 2026 in Paris
manfredsteyer
PRO
0
250
脅威をエンジニアリングの糧にして――現場編 / Turning Threats into Engineering Fuel — Field Edition
nrslib
0
280
エージェンティックRAGにAWSで入門しよう!
har1101
8
1.5k
不変条件と整合性境界—ビジネスが決める設計判断と実現パターン / Invariants and Consistency Boundaries
nrslib
13
4.1k
Mujeres en SEO Summit 2026 - Greatest Disaster Hits en Web Performance
guaca
0
180
メソッドのジェネリクスでGoの夢は広がるか? / Kyoto.go #65
utgwkk
3
760
Lessons from Spec-Driven Development
simas
PRO
0
190
Snowflake Summitでの新機能 CoCo / CoWork / snowflake-summit-2026-overall-what-new-coco
tatsuhiro
1
130
軽量Java基盤の設計 DIコンテナに頼らない、長期保守と1秒起動の実現 JJUG CCC 2026 Spring
macha64
0
510
生成AI時代にこそ効くGo | Why Go Works in the Age of Generative AI
mom0tomo
8
3.2k
Observability in Practice:Grafana 與 Edge Device SRE 的那些事
blueswen
0
160
「エンジニアインターン、どうやって取った?」準備のリアルを語るLT会 Progate BAR
akiomatic
0
130
Featured
See All Featured
Building a Scalable Design System with Sketch
lauravandoore
463
34k
The Pragmatic Product Professional
lauravandoore
37
7.3k
The Cult of Friendly URLs
andyhume
79
6.9k
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.2k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
55k
The untapped power of vector embeddings
frankvandijk
2
1.8k
Rebuilding a faster, lazier Slack
samanthasiow
85
9.5k
Self-Hosted WebAssembly Runtime for Runtime-Neutral Checkpoint/Restore in Edge–Cloud Continuum
chikuwait
0
590
Practical Orchestrator
shlominoach
191
11k
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
240
Skip the Path - Find Your Career Trail
mkilby
1
150
How to train your dragon (web standard)
notwaldorf
97
6.7k
Transcript
CDK引数設計道場100本ノック AWS CDK Conference Japan 2025 クライヤー篠塚 一帆 1
自作 ニキシー管温湿度気圧計 (シュタゲのダイバージェンスメータです) クライヤー篠塚 一帆 @nixieminton @badmintoncryer @ New Zealand
(Mount Cook) AWS CDK Top Contributor & Community Reviewer 144 contributions 19 / 1605 contributors AWS Community Builder (Dev Tools) AWS SAPro / IPA SC, ES, NW バドミントンと電子工作が好きです! キャリア20年! 2024年は年代別(30歳以上) 関東ベスト4でした🥉
04 03 02 01 Introduction 100本ノック (基礎編) 100本ノック (応用編) まとめ
3
おさらい https://aws.amazon.com/jp/blogs/aws/boost-your-infrastructure-with-cdk/ CDKアプリケーションの構成 - App - Stack - Construct Constructの構成
- L1 (Low level) - Cloudformationリソースと1対1対応 - 自動生成 - Cloudformation更新に自動追従 - L2 (High level) - L1を抽象化 (独自の引数を定義) - 型、関数、引数チェック、etc.. を提供 - L3 (Pattern) - 複数のL1, L2を更に抽象化 4
export interface CfnQueueProps { readonly messageRetentionPeriod?: number; readonly fifoQueue?: boolean;
readonly fifoThroughputLimit?: string; } export class CfnQueue extends cdk.CfnResource { public constructor(scope: Construct, id: string, props: CfnQueueProps = {}) { super(scope, id); this.messageRetentionPeriod = props.messageRetentionPeriod; this.fifoQueue = props.fifoQueue; this.fifoThroughputLimit = props.fifoThroughputLimit; } } Cfnの各propertyがCfnQueuePropsに列挙されている (ただし、型情報は心もとない) L1の基本構成 (SQS Queue) aws-cdk-lib/aws-sqs/lib/sqs.generated.ts (一部改変) AWS::SQS::Queue の Cloudformation document boolean (true / false) ‘perQueue’ or ‘perMessageGroupId’の文字列 5 60-1,209,600の整数 使い方 new sqs.CfnQueue(this, 'Resource', { messageRetentionPeriod: 60, fifoQueue: true, fifoThroughputLimit: ‘perQueue’, });
export interface QueueProps { readonly retentionPeriod?: Duration; readonly fifo?: boolean;
readonly fifoThroughputLimit?: FifoThroughputLimit; } export class Queue extends QueueBase { constructor(scope: Construct, id: string, props: QueueProps = {}) { const queue = new CfnQueue(this, 'Resource', { messageRetentionPeriod: props.retentionPeriod.toSeconds(), fifoQueue: props.fifo, fifoThroughputLimit: props.fifoThroughputLimit }); } } L2の基本構成 (SQS Queue) aws-cdk-lib/aws-sqs/lib/queue.ts (一部改変) 6 L2用のQueuePropsを定義 (L2独自の型) QueuePropsを受けとり 独自型をL1引数に合わせて変換しながら L1(new CfnQueue())を呼び出す
L2引数とL1引数の関係 export interface QueueProps { readonly retentionPeriod?: Duration; readonly fifo?:
boolean; readonly fifoThroughputLimit?: FifoThroughputLimit; } L2 (sqs.QueueProps) export interface CfnQueueProps { readonly retentionPeriod?: number; readonly fifoQueue?: boolean; readonly fifoThroughputLimit?: string; } L1 (sqs.CfnQueueProps) L2独自の引数型定義を行うことで使いやすさが向上 QueueProps CfnQueueProps L1 (CfnQueue) L2 (Queue) fifoThroughputLimit (FifoThroughputLimit) fifoQueue (boolean) fifoThroughputLimit (string) fifoQueue (boolean) retentionPeriod (number) 7 retentionPeriod (Duration) そのまま 渡す enum を渡す Duration .toSeconds() を実行
8 どんな引数の型が好ましいのか? 具体例を伝授します
注意点 9 便宜上「L2」の引数と表現しますが、 適宜「L3」や「カスタムコンストラクト」の引数と読み替えてください 実際のCDK実装から一部改変されている点があります
04 03 02 01 Introduction 100本ノック (基礎編) 100本ノック (応用編) まとめ
10
boolean 11 export interface CfnQueueProps { readonly fifoQueue?: boolean; }
L2 export interface QueueProps { readonly fifoQueue?: boolean; } boolean boolean L1実装 (SQS Queue) const queue = new CfnQueue(this, 'Resource', { fifoQueue: props.fifoQueue, }); L2実装 そのまま渡せばOK L1 new CfnQueue(this, 'Queue', { fifoQueue: true, // FIFOキューを作成 }); L1呼び出し new Queue(this, 'Queue', { fifoQueue: true, // FIFOキューを作成 }); L2呼び出し
L2 L1 データサイズ 12 export interface CfnVolumeProps { readonly size?:
number; // ボリュームサイズの指定 } export interface VolumeProps { readonly size?: Size; } new CfnVolume(this, 'Resource', { size: props.size?.toGibibytes() }); データサイズを表すnumber Size L1実装 (EBS Volume) L2実装 Size.toXxxbytes()関数で任意の単位への 変換を行ったうえでL1に渡す const volume = new Volume(this, 'Volume', { size: Size.mebibytes(1024), }); L2呼び出し Byte, kiB, MiB, GiB単位などの ファクトリーメソッドで作成可能 const volume = new CfnVolume(this, 'Volume', { size: 1, // 1GiBのボリュームを作成 }); L1呼び出し GiB単位の整数を記述するという制約あり number型では制約として不十分
L2 L1 期間 13 export interface CfnClusterProps { // バックアップ保持期間の指定
readonly backupRetention?: number; } export interface ClusterProps { readonly backupRetention?: Duration; } new CfnVolume(this, 'Resource', { backupRetention: props.backupRetention?.toDays() }); 期間を表すnumber Duration L1実装 (Neptune Cluster) L2実装 Duration.toXxxs()関数で任意の単位への 変換を行ったうえでL1に渡す const volume = new Cluster(this, 'Cluster', { backupRetention: Duration.hours(48), }); L2呼び出し ミリ秒, 秒, 分, 時間, 日単位での ファクトリーメソッドで作成可能 const volume = new CfnCluster(this, 'Cluster', { backupRetention: 1, // 1日 }); L1呼び出し 日数単位の整数を記述するという制約あり number型では制約として不十分
L2 L1 パターンが決まった文字列 14 export interface CfnCertificateProps { readonly validationMethod?:
string; } export enum ValidationMethod { EMAIL = 'EMAIL', DNS = 'DNS', HTTP = 'HTTP', } export interface CertificationValidationProps { readonly method?: ValidationMethod; } new CfnCertificate(this, 'Resource', { validationMethod: props.method, }); 2 〜 数十種類の文字列 enum L1実装 (ACM Certificate) L2実装 そのまま渡せばOK ACM の証明書検証方法は ‘EMAIL’, ‘DNS’, ‘HTTP’ の3種類の文字列が許容 許容される文字列だけの enumを定義! new Certificate(this, 'Certificate', { method: ValidationMethod.EMAIL, }); L2呼び出し enumで型安全に設定 new CfnCertificate(this, 'Certificate', { method: ’EMAIL’, }); L1呼び出し typoや存在しない検証方法の指定が頻発! string型では制約として不十分
L2 L1 パターンが決まった文字列 2 15 export interface CfnDomainProps { readonly
instanceType?: string; } export class InstanceType { public static readonly T2_XLARGE = InstanceType.of('ml.t2.xlarge'); public static of(version: string) { return new InstanceType(version); } private constructor(public readonly instanceType: string) { } } new CfnDomain(this, 'Resource', { elasticSearchVersion: props.instanceType.instanceType, }); 2 〜 数十種類の文字列 (頻繁に増加する) enum-like class L1実装 (OpenSearch Domain) L2実装 .of()で任意の値を持った インスタンスを作成可能 new Domain(this, 'Domain', { version: InstanceType.T2_XLARGE,// 推奨 version: InstanceType.of(‘ml.t2.xlarge’), // これでもOK }); L2呼び出し 未実装のインスタンスタイプであっても InstanceType.of()で対応可能 OpenSearchのインスタンスタイプは随時追加される string型では制約として不十分 enumだと更新に追いつけない
L2 L2がもつattributeの文字列 16 export interface VolumeProps { readonly encryptionKey?: kms.IKey;
} new CfnVolume(this, 'Resource', { kmsKeyId: props.encryptionKey?.kmsKeyArn, }); L2コンストラクトのInterface L2実装 kms.Key L2コンストラクトの Interfaceであるkms.IKeyを受け取る declare const kmsKey: kms.IKey; new Domain(this, 'Domain', { kmsKey, }); L2呼び出し IKeyなので、KmsKey.fromAttributes()などで importしたKey Constructも渡すことができる IConstructのもつattributeに アクセスして L1へ渡す L1 export interface CfnVolumeProps { // ボリュームを暗号化するCMK情報 readonly kmsKeyId?: string; } ARNなどの文字列 L1実装 (EC2 EBS Volume) L1呼び出し new Domain(this, 'Domain', { kmsKeyId: ‘arn:aws:kms:ap-northeast-1:12345 6789012:key/abcd1234-a1b2-3c4d-5e6f-7890abcdef12’, }); ARNのtypoが頻発! string型では制約として不十分
L2 L1 アカウント, リージョン 17 export interface FleetProps { readonly
peerVpc?: ec2.IVpc; } new CfnFleet(this, 'Resource', { // Interfaceのもつattributeにアクセス peerVpcId: props.peerVpc?.vpcId, peerVpcAwsAccountId: props.peerVpc?.env.account, peerVpcRegionId: props.peerVpc?.env.region, }); アカウント, リージョンを表す文字列 L2コンストラクトのInterface L2実装 ec2.Vpc L2コンストラクトの Interfaceであるec2.IVpcを受け取る declare const vpc: ec2.IVpc; new Fleet(this, 'Fleet', { peerVpc: vpc, }); L2呼び出し Interface(IResource)がもつ ResourceEnvironment(env)情報にアクセス export interface CfnFleetProps { // peeringするVPCのID readonly peerVpcId?: string; // peeringするVPCを持つアカウント ID readonly peerVpcAwsAccountId?: string; } L1実装 (GameLift Fleet) new CfnFleet(this, 'Fleet', { peerVpcId: 'vpc-0abc123de456fgh78', peerVpcAwsAccountId: '123456789012', }); L1呼び出し クロスアカウント連携設定時に (i) リソースの識別情報 (ii) (i)を持つアカウント情報 を同時に設定することがある VPC IDやアカウント番号の typoが頻発! string型では制約として不十分
L2 L1 失効日時 18 export interface CfnApiKeyProps { readonly expires?:
number; } export interface ApiKeyConfig { readonly expires?: Expiration; } new CfnFleet(this, 'Resource', { expires: props.expires.toEpoch(), }); (失効日時を表す) unixtime Expiration L1実装 (AppSync API Key) L2実装 Expirationを受け取る new ApiKey(this, 'ApiKey', { expires: Expiration.after(Duration.days(90)), }); L2呼び出し Expiration.toEpoch()でunixtimeに変換 new CfnApiKey(this, 'ApiKey', { expires: 1751328000, }); L1呼び出し 現時点から90日後のunixtimeを設定 unixtimeの計算ミスが頻発! いつ失効するのかも分かりづらい
L2 L1 シークレット 19 export interface CfnClusterProps { readonly masterUserPassword?:
string; } export interface ApiKeyConfig { readonly masterUserPassword?: SecretValue } new CfnCluster(this, 'Resource', { masterUserPassword: props.masterUserPassword?.unsafeUnwrap() }) (パスワードなどの)文字列 SecretValue L1実装 (Redshift Cluster Master Password) L2実装 SecretValueを受け取る new Cluster(this, 'Cluster', { masterUserPassword: SecretValue.secretsManager(‘ARN’), // SecretsManager SecretValue.ssmSecure(‘ParameterName’), // ParameterStore }); L2呼び出し SecretValue.unsafeUnwrap()で文字列に変 換 パスワードは必ずSecresManager, ParameterStore (SecureString) 経由でSecretValueに渡す!!!!! Cloudformationの動的参照で解決されるので、パスワードが Cfnテンプレートに現れない 平文の記述は好ましくない new CfnCluster(this, 'Cluster', { masterUserPassword: ’unsafe-plain-password’, }); L1呼び出し
L2 L1 タイムゾーン 20 export interface ScheduledActionProperty { readonly timezone?:
string; } export interface ScalingSchedule { readonly timeZone?: TimeZone; } new CfnScalableTarget(this, 'Resource', { timezone: props.timeZone?.timezoneName, }); タイムゾーンを表す文字列 TimeZone L1実装 (Application Auto Scaling Action) L2実装 TimeZoneを受け取る new ScalableTarget(this, 'Target', { timezone: TimeZone.ASIA_TOKYO, }); L2呼び出し TimeZone.timezoneNameでタイムゾーンを取得 new CfnScalableTarget(this, 'Target', { timezone: ‘Asia/Tokyo’, }); L1呼び出し staticメソッドでタイムゾーンを指定 TimeZoneのtypoが頻発
L2 L1 サブネット情報 21 export interface CfnServerlessClusterProperty { readonly subnetIds?:
string[]; } export interface ServerlessClusterProps { readonly vpcSubnets?: SubnetSelection; readonly vpc?: IVpc; } new CfnServerlessCluster(this, 'Resource', { subnetIds: props.vpc.selectSubnets(props.vpcSubnets).subnetIds }); サブネットID (文字列) IVpc & SubnetSelection L1実装 (MSK Serverless Cluster) L2実装 new ServerlessCluster(this, 'Cluster', { vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, vpc, }); L2呼び出し IVpcとSubnetSelection を受け取る new CfnServerlessCluster(this, 'Resource', { subnetIds: [ ‘subnet-0123456789abcdef0’, ’subnet-abcdef0123456789a’, ], }); L1呼び出し IVpc.selectSubnets(SubnetSelection)で 条件を満たす subnet情報を取得 タイプ, AZ, 名前, onePerAz, etc… 多様な条件でのサブネットフィルタが可能 サブネット IDの調査が面倒 IDのtypoも頻発
L2 L1 ここまで紹介されていない文字列 22 export interface CfnClusterSubnetGroupProps { readonly description:
string; } 文字列 文字列 L1実装 (Redshift Cluster Subnet Group) L2実装 L2呼び出し new CfnClusterSubnetGroup(this, 'SubnetGroup', { description: ‘my subnet group’, }); L1呼び出し export interface ClusterSubnetGroupProps { readonly description: string; } new ClusterSubnetGroup(this, 'SubnetGroup', { description: ‘my subnet group’, }); 型の恩恵にあずかるべく、 可能な限り string型の引数は回避する
stringが好ましいケース 23 渡したい情報をL1コンストラクトがattributeとして持つ場合があるが、 L1を引数とするより文字列を渡す方が好ましい(気がする)(要出典) interface GlueStartCrawlerRunOptions { readonly crawler: glue.CfnCrawler;
} const crawler = new glue.CfnCrawler(this, 'Crawler'); const crawlerTask = new tasks.GlueStartCrawlerRun(this, 'GlueCrawlerTask', { crawler: crawler, }); こういった実装も考えられる L1コンストラクトを受け取る CfnCrawlerをそのまま渡せる interface GlueStartCrawlerRunOptions { readonly crawlerName: string; } L2実装 (Stepfunctions glue-start-crawler-job) L2呼び出し const crawler = new glue.CfnCrawler(this, 'Crawler'); const crawlerTask = new tasks.GlueStartCrawlerRun(this, 'GlueCrawlerTask', { crawlerName: crawler.ref, }); CfnCrawlerのattributeからクローラ名を取得 CrawlerのL2コンストラクトが存在しないため、 ICrawler(Interface)を引数に取ることができない
04 03 02 01 Introduction 100本ノック (基礎編) 100本ノック (応用編) まとめ
24
25 応用編では時と場合により判断が分かれるケースを紹介します
L2 L1 複数引数をまとめたオブジェクト 26 export interface DatabaseClusterProperty { readonly serverlessV2ScalingConfiguration?:
{ readonly minCapacity?: number, readonly maxCapacity?: number, }; } Interface Interface or 個別引数定義 L1実装 (RDS Database Cluster) L2実装 L2呼び出し new CfnDBCluster(this, 'Cluster', { serverlessV2ScalingConfiguration: { minCapacity: 0, maxCapacity: 2, }, }); L1呼び出し export interface DatabaseClusterProps { readonly serverlessV2ScalingConfiguration?: { readonly minCapacity?: number, readonly maxCapacity?: number, }; } new Cluster(this, 'Cluster', { serverlessV2ScalingConfiguration: { serverlessV2MinCapacity: 0, serverlessV2MaxCapacity: 2, }, }); L1と同じInterface
L2 L1 複数引数をまとめたオブジェクト 27 export interface DatabaseClusterProperty { readonly serverlessV2ScalingConfiguration?:
{ readonly minCapacity?: number, readonly maxCapacity?: number, }; } Interface Interface or 個別引数定義 L1実装 (RDS Database Cluster) L2実装 L2呼び出し new CfnDBCluster(this, 'Cluster', { serverlessV2ScalingConfiguration: { minCapacity: 0, maxCapacity: 2, }, }); L1呼び出し export interface DatabaseClusterProps { readonly serverlessV2MinCapacity?: number; readonly serverlessV2MaxCapacity?: number; } new Cluster(this, 'Cluster', { serverlessV2MinCapacity: 0, serverlessV2MaxCapacity: 2, }); ネストが解消されている
28 どちらで実装すればよいの? CDKのデザインガイドでは個別引数定義 を推奨 (しかしメンテナは大抵Interface定義を推奨してくる) https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md#flat
L2 L1 密接に関連した複数の引数 29 export interface ReplicationConfiguration { readonly fileSystemId:
string, readonly region: string, readonly availabilityZoneName: string, } 独立した個別引数 L1踏襲 or クラス定義 L1実装 (EFS FileSystem Replication Configuration) L1呼び出し レプリケーション タイプ fileSystemId region availability ZoneName リージョナル undefined リージョン名 undefined ワンゾーン undefined リージョン名 AZ名 既存FileSystem 既存のファイル システムID undefined undefined 各引数の関係性 // リージョナル const regionalConfig = { region: 'ap-northeast-1' }; // ワンゾーン const oneZoneConfig = { region: 'ap-northeast-1', availabilityZoneName: 'ap-northeast-1a' }; // 既存FileSystem const existingConfig = { fileSystemId: 'file-system-id' }; ユーザが作成する必要のある引数オブジェクト 使いやすいものであるとは言えなさそう ...
L2 L1 密接に関連した複数の引数 30 独立した個別引数 L1踏襲 or クラス定義 export class
ReplicationConfiguration { public static existingFileSystem( destinationFileSystem: IFileSystem, ): ReplicationConfiguration { return new ReplicationConfiguration({ destinationFileSystem }); } public static regionalFileSystem( region?: string, ): ReplicationConfiguration { return new ReplicationConfiguration({ region }); } public static oneZoneFileSystem( region: string, availabilityZone: string, ): ReplicationConfiguration { return new ReplicationConfiguration({ region, availabilityZone }); } public readonly destinationFileSystem?: IFileSystem; public readonly region?: string; public readonly availabilityZone?: string; private constructor(options: ReplicationConfigurationProps) { this.destinationFileSystem = options.destinationFileSystem; this.region = options.region; this.availabilityZone = options.availabilityZone; } } // Regional const regionalCofig = // 自動的に複製元ファイルシステムと同一リージョンに設定 efs.ReplicationConfiguration.regionalFileSystem(); // One Zone const oneZoneCofig = efs.ReplicationConfiguration.oneZoneFileSystem( 'us-east-1', // リージョン名 'us-east-1a', // AZ名 ); // 既存ファイルシステム declare const destinationFileSystem: efs.FileSystem; const userManagedConfig = efs.ReplicationConfiguration.existingFileSystem( destinationFileSystem, ); 直感的なファクトリーメソッドで設定可能
04 03 02 01 Introduction 100本ノック (基礎編) 100本ノック (応用編) まとめ
31
まとめ 32 どんな引数設計を行えばよいのか? - string型は可能な限り避けよう! - 意図しない文字列を渡してしまい、デプロイエラーに繋がりやすくなります - ✕ ‘arn:aws:iam::123456789012:role/IamRoleName
‘ - ◯ iam.IRoleを渡し、L2内でIRole.roleArnを呼び出す - number型もDuration, Sizeあたりを使えるケースは多いです 結局は状況次第 - 使い勝手とカスタマイズ性は表裏一体 - ベストな定義よりもベターな定義を目指そう - L1より僅かでも使いやすくなっていれば十分だと思います カスタムコンストラクト作成や CDKコントリビュートに役立てて頂けると嬉しいです