name: construct-development description: CDK construct development patterns, design principles, and type-driven development. Use when building or modifying AWS CDK constructs.
Construct Development Guidelines
Construct Pattern
Use factory functions that return CDK resources:
import {Construct} from 'constructs';
import {RemovalPolicy} from 'aws-cdk-lib';
import {Bucket, BucketEncryption, BlockPublicAccess} from 'aws-cdk-lib/aws-s3';
export type BucketProps = {
bucketName: string;
env: {
name: string;
region: string;
account: string;
};
enableVersioning?: boolean;
};
export const createBucket = (scope: Construct, props: BucketProps): Bucket => {
return new Bucket(scope, `${props.bucketName}-bucket`, {
bucketName: `${props.bucketName}-${props.env.name}-${props.env.region}`,
enforceSSL: true,
encryption: BucketEncryption.S3_MANAGED,
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
removalPolicy: props.env.name === 'prod' ? RemovalPolicy.RETAIN : RemovalPolicy.DESTROY,
versioned: props.enableVersioning ?? props.env.name === 'prod',
});
};
Design Principles
1. Secure by Default
- Enable encryption (S3_MANAGED or KMS)
- Enforce SSL
- Block public access
- Use private subnets
2. Environment-Aware
- Gate expensive features to production
- Use
RemovalPolicy.RETAINin prod,DESTROYin dev - Enable enhanced monitoring only where needed
// Performance Insights only in prod
performanceInsightRetention: props.env.name === 'prod' ? 7 : undefined,
// Reader instances only in prod
const createReaders = props.env.account === Account.PROD && props.enableReaders;
3. Cost-Efficient
// Performance Insights only in prod
performanceInsightRetention: props.env.name === 'prod' ? 7 : undefined,
// Reader instances only in prod
const createReaders = props.env.account === Account.PROD && props.enableReaders;
4. Observable
- Include CloudWatch log groups
- Set appropriate retention periods
- Enable metrics where applicable
Type-Driven Development
Use types, not interfaces. This codebase follows type-driven development where we define all data structures using type declarations.
Why Types Over Interfaces?
- More flexible for unions and intersections
- Consistent pattern across the codebase
- Better for functional programming patterns
- Clearer intent for data structures
Define all props types in src/types/:
// src/types/bucket-types.ts
import {EnvironmentConfig} from '@cdk-constructs/cdk';
export type BucketProps = {
bucketName: string;
env: EnvironmentConfig['env'];
kmsKeyArn?: string;
lifecycleRules?: BucketLifecycleRule[];
};
export type BucketLifecycleRule = {
id: string;
expiration?: number;
transitions?: StorageTransition[];
};
Export Pattern
Export all public APIs from src/index.ts:
// src/index.ts
// Constructs
export {createBucket} from './constructs/bucket';
export {createLambda} from './constructs/lambda';
// Types
export type {BucketProps, BucketLifecycleRule} from './types/bucket-types';
export type {LambdaProps} from './types/lambda-types';
// Enums
export {StorageClass} from './enums/storage';
// Utilities
export {getAbsoluteLambdaPath} from './util/paths';
JSDoc Requirements
All public functions, types, and enums should have JSDoc comments:
/**
* Creates a CodeArtifact domain.
*
* @param scope - The parent construct
* @param id - The construct ID
* @param props - The domain properties
* @returns The created CodeArtifact domain
*
* @public
*/
export const createCodeArtifactDomain = (scope: Construct, id: string, props: CodeArtifactDomainProps): CfnDomain => {
// ...
};