|
| 1 | +# CDK Handler Framework |
| 2 | + |
| 3 | +The CDK handler framework is an internal framework used to code generate constructs that extend a lambda `Function`, lambda `SingletonFunction`, or core `CustomResourceProvider` construct and prohibit the user from directly configuring the `handler`, `runtime`, `code`, and `codeDirectory` properties. In doing this, we are able to establish best practices, runtime enforcement, and consistency across all handlers we build and vend within the aws-cdk. |
| 4 | + |
| 5 | +## CDK Handler Framework Concepts |
| 6 | + |
| 7 | +This framework allows for the creation of three component types: |
| 8 | +1. `ComponentType.FUNCTION` - This is a wrapper around the lambda `Function` construct. It offers the same behavior and performance as a lambda `Function`, but it restricts the consumer from configuring the `handler`, `runtime`, and `code` properties. |
| 9 | +2. `ComponentType.SINGLETON_FUNCTION` - This is a wrapper around the lambda `SingletonFunction` construct. It offers the same behavior and performance as a lambda `SingletonFunction`, but it restricts the consumer from configuring the `handler`, `runtime`, and `code` properties. |
| 10 | +3. `ComponentType.CUSTOM_RESOURCE_PROVIDER` - This is a wrapper around the core `CustomResourceProvider` construct. It offers the same behavior and performance as a `CustomResourceProvider` and can be instantiated via the `getOrCreate` or `getOrCreateProvider` methods. This component restricts the consumer from configuring the `runtime` and `codeDirectory` properties. |
| 11 | + |
| 12 | +Code generating one of these three component types requires adding the component properties to the [config](./config.ts) file by providing `ComponentProps`. The `ComponentProps` are responsible for code generating the specified `ComponentType` with the `handler`, `runtime`, `code`, and `codeDirectory` properties set internally. `ComponentProps` includes the following properties: |
| 13 | +- `type` - the framework component type to generate. |
| 14 | +- `sourceCode` - the source code that will be excuted by the framework component. |
| 15 | +- `runtime` - the runtime that is compatible with the framework component's source code. This is an optional property with a default node runtime maintained by the framework. |
| 16 | +- `handler` - the name of the method with the source code that the framework component will call. This is an optional property and the default is `index.handler`. |
| 17 | +- `minifyAndBundle` - whether the source code should be minified and bundled. This an optional property and the default is `true`. This should only be set to `false` for python files or for typescript/javascript files with a require import. |
| 18 | + |
| 19 | +The [config](./config.ts) file is structured with the top level mapping to an aws-cdk module, i.e., aws-s3, aws-dynamodb, etc. Each service can contain one or more component modules. Component modules are containers for handler framework components and will be rendered as a code generated file. Each component module can contain one or more `ComponentProps` objects. The following example shows a more structural breakdown of how the [config](./config.ts) file is configured: |
| 20 | + |
| 21 | +```ts |
| 22 | +const config = { |
| 23 | + 'aws-s3': { // the aws-cdk-lib module |
| 24 | + 'replica-provider': [ // the component module |
| 25 | + // handler framework component defined as a `ComponentProps` object |
| 26 | + { |
| 27 | + // the handler framework component type |
| 28 | + type: ComponentType.FUNCTION, |
| 29 | + // the source code that the component will use |
| 30 | + sourceCode: path.resolve(__dirname, '..', 'aws-dynamodb', 'replica-handler', 'index.ts'), |
| 31 | + // the handler in the source code that the component will execute |
| 32 | + handler: 'index.onEventHandler', |
| 33 | + }, |
| 34 | + ], |
| 35 | + }, |
| 36 | + 'aws-stepfunctions-tasks': { |
| 37 | + // contains multiple component modules |
| 38 | + 'eval-nodejs-provider': [ |
| 39 | + { |
| 40 | + type: ComponentType.SINGLETON_FUNCTION, |
| 41 | + sourceCode: path.resolve(__dirname, '..', 'aws-stepfunctions-tasks', 'eval-nodejs-handler', 'index.ts'), |
| 42 | + }, |
| 43 | + ], |
| 44 | + 'role-policy-provider': [ |
| 45 | + { |
| 46 | + type: ComponentType.SINGLETON_FUNCTION, |
| 47 | + sourceCode: path.resolve(__dirname, '..', 'aws-stepfunctions-tasks', 'role-policy-handler', 'index.py'), |
| 48 | + runtime: Runtime.PYTHON_3_9, |
| 49 | + // prevent minify and bundle since the source code is a python file |
| 50 | + minifyAndBundle: false, |
| 51 | + }, |
| 52 | + ], |
| 53 | + }, |
| 54 | +}; |
| 55 | +``` |
| 56 | + |
| 57 | +Code generation for the component modules is triggered when this package - `@aws-cdk/custom-resource-handlers` - is built. Importantly, this framework is also responsible for minifying and bundling the custom resource providers' source code and dependencies. A flag named `minifyAndBundle` can be configured as part of the `ComponentProps` to prevent minifying and bundling the source code for a specific provider. This flag is only needed for python files or for typescript/javascript files containing require imports. |
| 58 | + |
| 59 | +Once built, all generated code and bundled source code will be written to `@aws-cdk/custom-resource-handlers/dist`. The top level field in the [config](./config.ts) file defining individual aws-cdk modules will be used to create specific directories within `@aws-cdk/custom-resource-handlers/dist` and each component module will be a separate code generated file within these directories named `<component-module>.generated.ts`. As an example, the sample [config](./config.ts) file above would create the following file structure: |
| 60 | + |
| 61 | +|--- @aws-cdk |
| 62 | +| |--- custom-resource-handlers |
| 63 | +| | |--- dist |
| 64 | +| | | |--- aws-s3 |
| 65 | +| | | | |--- replica-handler |
| 66 | +| | | | | |--- index.js |
| 67 | +| | | | |--- replica-provider.generated.ts |
| 68 | +| | | |--- aws-stepfunctions-tasks |
| 69 | +| | | | |--- eval-nodejs-handler |
| 70 | +| | | | | |--- index.js |
| 71 | +| | | | |--- role-policy-handler |
| 72 | +| | | | | |--- index.py |
| 73 | +| | | | |--- eval-nodejs-provider.generated.ts |
| 74 | +| | | | |--- role-policy-provider.generated.ts |
| 75 | + |
| 76 | +The code generated handler framework components are consumable from `aws-cdk-lib/custom-resource-handlers/dist` once `aws-cdk-lib` is built. The file structure of `aws-cdk-lib/custom-resource-handlers/dist` will have the same structure as `@aws-cdk/custom-resource-handlers/dist` with the exception of `core`. To prevent circular dependencies, all handler framework components defined in `core`and any associated source code will be consumable from `aws-cdk-lib/core/dist/core`. |
| 77 | + |
| 78 | +## Creating a Handler Framework Component |
| 79 | + |
| 80 | +Creating a new handler framework component involves three steps: |
| 81 | +1. Add the source code to `@aws-cdk/custom-resource-handlers/lib/<aws-cdk-lib-module>` |
| 82 | +2. Update the [config](./config.ts) file by specifying all required `ComponentProps`. |
| 83 | +3. At this point you can directly build `@aws-cdk/custom-resource-handlers` with `yarn build` to view the generated component in `@aws-cdk/custom-resource-handlers/dist`. Alternatively, you can build `aws-cdk-lib` with `npx lerna run build --scope=aws-cdk-lib --skip-nx-cache` to make the generated component available for use within `aws-cdk-lib` |
0 commit comments