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.