This document explains the design principles, patterns, and structure behind CDK Utils.
@gradientedge/cdk-utils (umbrella)
├── @gradientedge/cdk-utils-aws → AWS CDK
├── @gradientedge/cdk-utils-azure → Azure Pulumi (Azure Native provider)
├── @gradientedge/cdk-utils-cloudflare → Cloudflare Pulumi
└── @gradientedge/cdk-utils-common → Shared types, enums, utilities
Note: The Azure package uses the Pulumi Azure Native provider (
@pulumi/azure-native), which provides 100% API coverage of Azure Resource Manager. This is the recommended provider over the classic@pulumi/azureprovider.
Each cloud-specific package follows the same internal structure:
packages/<cloud>/src/
├── common/ Base constructs, stacks, types, resource naming
├── construct/ High-level patterns combining multiple resources
├── services/ Individual service manager classes
└── index.ts Public API exports
The core pattern flows from top to bottom:
Stack (entry point) creates a Construct (resource orchestrator), which injects Service Managers (resource creators):
CommonStack
└── CommonConstruct
├── LambdaManager
├── S3Manager
├── DynamodbManager
├── ApiManager
├── ... (23 managers total for AWS)
└── ResourceNameFormatter
The stack is responsible for:
CommonStackProps from the CDK contextCommonConstruct instanceThe construct is the base class all custom infrastructure extends. It:
ResourceNameFormatter for consistent namingisDevelopmentStage(), isProductionStage(), etc.)Service managers are stateless utility classes. Each one wraps a specific AWS/Azure/Cloudflare service:
// Managers are instantiated without parameters
this.lambdaManager = new LambdaManager()
this.s3Manager = new S3Manager()
Every manager method takes the construct scope as a parameter, giving it access to:
scope.props — all configuration propertiesscope.resourceNameFormatter — consistent namingscope.<managerName>Example method signature:
public createLambdaFunction(
id: string,
scope: CommonConstruct,
props: LambdaProps
): IFunction
All resources are named using the ResourceNameFormatter:
[globalPrefix]-[resourcePrefix]-resourceName-[resourceSuffix]-[globalSuffix]-stage
Per-resource overrides are available via resourceNameOptions in props.
Configuration loads in layers, with later layers overriding earlier ones:
1. cdk.json (base context)
↓ merged with
2. Extra context files (from extraContexts array)
↓ merged with
3. Stage-specific file (cdkEnv/{stage}.json)
↓ produces
4. CommonStackProps
_.merge)| Concept | AWS CDK | Azure Pulumi | Cloudflare Pulumi |
|---|---|---|---|
| Base class | Construct |
ComponentResource |
ComponentResource |
| Provider | AWS CDK | @pulumi/azure-native (Azure Native) |
@pulumi/cloudflare |
| Stack | CommonStack |
CommonAzureStack |
CommonCloudflareStack |
| Construct | CommonConstruct |
CommonAzureConstruct |
CommonCloudflareConstruct |
| Managers | 23 services | 16 services | 10 services |
| Naming | ResourceNameFormatter |
AzureResourceNameFormatter |
(uses Pulumi naming) |
All three share:
@gradientedge/cdk-utils-commonstage, domainName, subDomain)initResources())When creating a custom construct:
class MyConstruct extends CommonConstruct {
constructor(parent: Construct, id: string, props: MyProps) {
super(parent, id, props) // 1. Injects all service managers
this.props = props
this.id = id
this.initResources() // 2. Kicks off resource creation
}
protected initResources() {
this.resolveHostedZone() // 3. Resolve existing resources
this.createBucket() // 4. Create new resources
this.createFunction() // 5. Wire resources together
}
}
Pre-built constructs like RestApiLambda follow this pattern with a defined initialization pipeline: