【AWS CDK v2】Aurora DatabaseClusterからCfnDbInstanceを取り出し設定を変更する

# はじめに
AWS CDKは、L1/L2/L3コンストラクトという概念があり、異なる抽象化のレベルでAWSリソースを扱うことが出来ます。
L1はCloudFormation templateの定義そのものに対応しており、L2/L3はより抽象化されたものとなっています。

通常はL2/L3のものを用いるのが良いですが、L2ではサポートされていないプロパティがある場合、L1のレベルでの操作が必要となります。

特に、RDS関連のL2コンストラクトはきめ細かなプロパティが制御できないため、適宜L1コンストラクトのオブジェクトを扱うこととなります。
本稿ではその方法を簡単に説明します。


# 概要
L2オブジェクトは `node` プロパティを持っており、ここから対応するL1オブジェクトを取得することが出来ます。
L1オブジェクトを取得することが出来れば、L2コンストラクトでは指定できないプロパティを指定することが可能になります。

https://docs.aws.amazon.com/cdk/v2/guide/cfn_layer.html#cfn_layer_raw

> ```typescript:Raw overrides
> // Get the CloudFormation resource
> const cfnBucket = bucket.node.defaultChild as s3.CfnBucket;
> 
> // Use dot notation to address inside the resource template fragment
> cfnBucket.addOverride('Properties.VersioningConfiguration.Status', 'NewStatus');
> cfnBucket.addDeletionOverride('Properties.VersioningConfiguration.Status');
> 
> // use index (0 here) to address an element of a list
> cfnBucket.addOverride('Properties.Tags.0.Value', 'NewValue');
> cfnBucket.addDeletionOverride('Properties.Tags.0');
> 
> // addPropertyOverride is a convenience function for paths starting with "Properties."
> cfnBucket.addPropertyOverride('VersioningConfiguration.Status', 'NewStatus');
> cfnBucket.addPropertyDeletionOverride('VersioningConfiguration.Status');
> cfnBucket.addPropertyOverride('Tags.0.Value', 'NewValue');
> cfnBucket.addPropertyDeletionOverride('Tags.0');
> ```

上記公式ドキュメントにもありますように `{L2オブジェクト}.node.defaultChild` プロパティで、L1オブジェクトを取得することが出来ます。

これを利用して、RDSのL2オブジェクトである `DatabaseCluster` から、
L1オブジェクトである `CfnDBCluster` および `CfnDBInstance` オブジェクトを取り出すことが出来ます。

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds.DatabaseCluster.html#node
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds.CfnDBCluster.html
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds.CfnDBInstance.html

# サンプルコード
## CfnDBCluster の取り出し
最初に、RDS WriterインスタンスとしてClusterInstanceを作り、DatabaseClusterオブジェクトを生成します。

```typescript:ClusterInstanceの生成
    // RDS instance
    // https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds.ClusterInstance.html
    // https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds.ProvisionedClusterInstanceProps.html
    const writer = ClusterInstance.provisioned('Dev1LoremRdsWriter1', {
        instanceIdentifier: 'dev1-lorem-rds-writer-1',
    });

```

```typescript:DatabaseClusterの生成
    // RDS Aurora PostgreSQL cluster
    // https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds.DatabaseCluster.html
    const databaseCluster = new DatabaseCluster(this, 'Dev1LoremRdsCluster'), {
        clusterIdentifier: 'dev1-lorem-rds-cluster',
        engine: DatabaseClusterEngine.auroraPostgres({
            version: AuroraPostgresEngineVersion.VER_15_2,
        }),
        writer: writer,
        vpc: vpc,
        subnetGroup: subnetGroup,
          securityGroups: [sgForRds],
    });
```

下記のように、 `DatabaseCluster` 型のオブジェクトから `CfnDBCluster` 型のオブジェクトを取り出すことが出来ます。
なお、 `defaultChild` プロパティは常に `IConstruct` 型となるので、必要な型（今回は `CfnDBCluster`）にキャストします。
L1コンストラクトオブジェクトは `addPropertyOverride` メソッドを持っているので、これを用いて必要な設定を上書きします。

```typescript:CfnDBClusterの取り出し・設定の上書き
    const dbClusterL1 = databaseCluster.node.defaultChild as CfnDBCluster;
    dbClusterL1.addPropertyOverride('AutoMinorVersionUpgrade', false);
```


## CfnDBInstance の取り出し
次に、DBクラスタ内のDBインスタンスのオブジェクトを、 `CfnDBInstance` 型オブジェクトとして取り出します。

まず、先ほどと同様に、ClusterInstanceを作り、DatabaseClusterオブジェクトを生成します。

```typescript:DatabaseInstance
    // RDS instance
    // https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds.ClusterInstance.html
    // https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds.ProvisionedClusterInstanceProps.html
    const writer = ClusterInstance.provisioned('Dev1LoremRdsWriter1', {
        instanceIdentifier: 'dev1-lorem-rds-writer-1',
    });

```

```typescript:DatabaseCluster
    // RDS Aurora PostgreSQL cluster
    // https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds.DatabaseCluster.html
    const databaseCluster = new DatabaseCluster(this, 'Dev1LoremRdsCluster'), {
        clusterIdentifier: 'dev1-lorem-rds-cluster',
        engine: DatabaseClusterEngine.auroraPostgres({
            version: AuroraPostgresEngineVersion.VER_15_2,
        }),
        writer: writer,
        vpc: vpc,
        subnetGroup: subnetGroup,
          securityGroups: [sgForRds],
    });
```

`DatabaseCluster` 型オブジェクトから、 `node` プロパティをたどって、配下にいるDBインスタンスのオブジェクトも取得することが出来ます。
指定が少し複雑になりますが、まず `DatabaseCluster.node.findChild('ClusterInstance名')` で、DBインスタンスのオブジェクトを取得することが出来ます。
このDBインスタンスのオブジェクトから、 `node.defaultChild` をたどることで、L1オブジェクトである`CfnDBInstance` 型オブジェクトを取り出すことが出来ます。

下記がサンプルコードとなります。

```typescript:L1オブジェクトCfnDbInstanceを取り出し、プロパティを上書き
    // Make the RDS writer instance on a specific AZ.
    // https://docs.aws.amazon.com/cdk/v2/guide/cfn_layer.html#cfn_layer_raw
    // https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds.CfnDBInstance.html
    const writerL1 = databaseCluster.node.findChild('Dev1LoremRdsWriter1')?.node.defaultChild as CfnDBInstance;
    writerL1.addPropertyOverride("AvailabilityZone", "us-west-1a");

```

# おわりに
上記、抽象化の概念や考え方、escape hatchesの考え方はAWS CDKのドキュメントに記載されています。
実装する上で参考になるので、一通り目を通しておくと良いでしょう。

https://docs.aws.amazon.com/cdk/v2/guide/cfn_layer.html