aws / aws-cdk-rfcs

RFCs for the AWS CDK

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CDK Refactoring Tools

Gtofig opened this issue · comments

At the moment, when I move constructs around in CDK, this results in logical IDs changing and resources being replaced during deployment, even stateful ones. This causes major pain, especially if it's a production service.

I would like CDK to offer a simpler way of refactoring code without having to worry about resource replacement.

Use Case

Refactoring code is a natural part of development, including CDK development. I don't want to have to worry about breaking production due to moving constructs around.

Proposed Solution

Option 1) Work with CloudFormation team to support resource logical ID renaming without replacement (maybe use CDK metadata as a key to identify that it's the same resource)

Option 2) Make it easier to hardcode logical IDs in CDK. At the moment it only works via L1 resources, but most constructs we deal with are not L1, therefore it becomes painful to manually set logical IDs. Perhaps Construct could expose a method to hardcode prefix for logical IDs

Other

  • 👋 I may be able to implement this feature request
  • ⚠️ This feature might incur a breaking change

This is a 🚀 Feature Request

Are you familiar with renameLogicalId?

Transferring this to the RFC repo as this requires further discussion.

Are you familiar with renameLogicalId?

Yes, but this only works at L1 level, so if I have a high level construct which I move around, it becomes more complicated. Perhaps you have a pattern in mind for this?

It also seems like a big ask to have to drop down to renameLogicalId every time I want to move/refactor a construct around my CDK app. Also, how would this function for composed constructs? If I have a construct that I vend to multiple apps, how would renameLogicalId help me move around some of the sub-constructs within the construct and not have impact on the users of the construct?

Any updates here? It seems untenable to not allow folks to refactor CDK code after the first deployment without causing breakages.

I think the better solution is to allow remapping construct paths at any point in the tree.

As long as that's a hierarchical operation we should be fine.

I use this https://gist.github.com/foriequal0/f1f4ea279fb64836e5fb38efefa133d7 while waiting for a feature that rename logical id in a stack

I have just refactored a DynamoDB Table from a Stack into its own Construct within the Stack. My colleagues and I have already deployed copies of the Stack before this change into our respective AWS Accounts. How do I ensure the refactor I've just checked in will cleanly deploy, without my colleagues and I all having to jump through hoops, rename things, mess with the cdk.context.json, etc.?

It would be great if CDK had a solution for such a refactor.

I really like how this is handled in pulumi via aliasing.
There's a great blog post explaining how it works here

Would CDK context be an appropriate tool to help here?

I'm thinking if we had a method like freezeLogicalId() on a Construct, which would check CDK context for existing logical IDs for this construct, and if not found, it would store current logical IDs into the context.

I think this is pretty big gap in aws-cdk functionality that really hinders the cdk's main goal of allowing composability and higher level logic for creating cloud infrastructure. The lack of built-in support for refactoring without resource recreation implicitly influences developers to create giant flat stacks of resources to ensure logicalId stability, which ends up just looking like CFN templates.

A tighter integration with CloudFormation resource import functionality could probably close the gap - @foriequal0 may be onto something with their comment here: aws/aws-cdk#4733 (comment)

But until the cdk addresses it, this will always be a sharp edge. One possible workaround is to add an optional logicalId override value in the props of your reusable constructs. This will allow consumers of the construct to pin the logicalId if they desire, or omit it if they'd rather have the cdk automatically generate it. With the current state of things I'd actually recommend that for stateful named resources such as DynamoDb tables, s3 buckets, etc. users should always provide a logicalId override from the get-go, in order to allow for later refactors without replacement of the underlying resource. Otherwise you'll eventually end up with a bunch of random (table.node.defaultChild as CfnTable).overrideLogicalId("MyStackAlarmedTableTable5E3CHJS") all over your codebase.

A dummy example:

// alarmed-table.ts - a reusable construct that spins up a table and an alarm monitoring its usage

import { Construct } from "constructs";
import { aws_dynamodb as dynamodb, aws_cloudwatch as cloudwatch } from "aws-cdk-lib";

export interface MyAlarmedTableProps {
  tableName: string;
  tableLogicalId?: string;
}

export class MyAlarmedTable extends Construct {
  readonly table: dynamodb.Table;
  readonly usageAlarm: cloudwatch.Alarm;

  constructor(scope: Construct, id: string, props: MyAlarmedTableProps) {
    super(scope, id);

    this.table = new dynamodb.Table(this, 'Table', {
      tableName: props.tableName,
      billingMode: BillingMode.PAY_PER_REQUEST,
    });
    if (props.tableLogicalId) {
      (this.table.node.defaultChild as CfnTable).overrideLogicalId(props.tableLogicalId);
    }

    this.alarm = new cloudwatch.Alarm(this, 'UsageAlarm', {
      // metric for throttled requests, latency, or something
    });
  }
}

and its usage:

// stack.ts

import { Stack, StackProps } from "aws-cdk-lib";
import { MyAlarmedTable } from "./alarmed-table";

class MyStack extends Stack {
  constructor(scope: App, id: string, props: StackProps) {
    super(scope, id, props);

    new MyAlarmedTable(this, 'UnitOrdersTable', {
      tableName: `UnitOrders-${props.env.region}`,
      tableLogicalId: "UnitOrdersDDB",
    });
  }
}

With this approach, no matter where you refactor and move that UnitOrdersTable resource within the CDK app, it will always refer to the same logical resource.