Building new constructs
AWS DSF Constructs follow the best practices detailed below. We recommend you follow them for building any new construct.
Construct design
Refer to these best practices when designing a new construct:
-
Use AWS CDK L2 Constructs instead of L1 when available. Alpha constructs should not be used because there may be some breaking changes in the API.
-
Resources naming should be optional and CDK should generate them when not provided. CDK ensures names uniqueness. If the name of the resource is provided as part of the constructor props, the name should be unique at least in the stack, sometimes in the AWS account or globally (like S3 bucket names) depending on the type of the resource.
-
Delete retention policy should be
RETAINby default but the possibility toDESTROYresources when destroying the CDK stack should be provided in the construct Props. This DESTROY parameter should only be considered (resources to be deleted) if the global guardrail is configured accordingly (global CDK parameter to be defined). The overall guard rail approach is explained in theDataLakeStorageand code example is available here. -
Extend
Constructvs L2 Resource:- If the construct you want to create contains only one resource and it’s a specific implementation/configuration, then you should go with extending the base resource. An example is the
AnalyticsBucket. - If the resource contains more than one resource, it should extend
Construct. It allows to properly scope resources created within the construct (usingthisparameter as the scope) and avoid collisions in resource naming.
- If the construct you want to create contains only one resource and it’s a specific implementation/configuration, then you should go with extending the base resource. An example is the
-
Expose all the resources created by your construct so you provide a way for end-users to customize them. Sometimes, it's more user friendly to expose a resource directly to avoid long resources chaining. For example, in the
DataLakeStorageconstruct that contains a KMSKeyattached to theAnalyticsBucketpart of theDataLakeStorage, we re-expose the key so you can access it directly viadataLakeStorage.keyinstead ofdataLakeStorage.bronze.key. -
If a Construct is creating a role, provide a props to get the role as a parameter. Some AWS customers create roles within a specific process outside of any infrastructure as code. A code example is available
-
Bucket encryption: if no business data is store in the bucket, you can use KMS_MANAGED which uses a default KMS key for the account. If the bucket will contain business data, use KMS and create a key.
-
Use AWS CDK interfaces (whenever it exists) as input/output of your construct instead of the AWS CDK resource. For example, use
IBucketinstead ofBucketas the property or public member of your construct. -
Avoid using generic
stringparameters for passing values that are attributes of an AWS resource. Because there is no check during synthetize time, it's very error prone. Prefer passing the entire object (or interface). For examples:- Do not use an IAM Role ARN as a
stringproperty for your construct. Instead, use anIRoletype in the properties and get the ARN from the attributes. - Do not use S3 URI in the form of
s3://MYBUCKET/MYPREFIX. Instead, use anIBuckettype and a prefix instringtype.
- Do not use an IAM Role ARN as a
-
Extend the
TrackedConstructinstead of the base CDKConstructto measure the number of deployments. See the dedicated documentation. -
Tag all resources that are created by the construct and support tagging. Extending the
TrackedConstructwill automatically add a tag to all AWS resources that are created by CDK. Some constructs will create resources outside of CDK like theSparkEmrServerlessJobthat triggers EMR Serverless jobs from a Step Function. For this kind of situation, use the resource parameters to tag it withDSF_OWNED_TAG: truelike in this example.
Coding style
Follow this coding style when submitting code to AWS DSF constructs:
- Use 2 spaces indentation and 120 characters wide
- ATX style headings in markdown (e.g. ## H2 heading)
- Import libraries directly
- Use
import { Construct } from '@aws-cdk/core' - Instead of
import '@aws-cdk/core' as cdk
- Use
- Do not use Typescript code that is falling into these JSII limitations because JSII won’t be able to translate Typescript code in other languages. See documentation
- Do not use your own
Interfacein the construct properties because they are translated in PythonProtocolsby JSII and then cannot be referenced when consuming the construct. - Set the defaults values for props at the beginning of the construct constructor (first lines of code) so it's easy to find and maintain.
- Write the props interface for a Construct class in a dedicated file.
- Resources IDs use Pascal Case. For example
BronzeAnalyticsBucket. - Resource IDs should contain the name of construct they use. For example the ID
BronzeAnalyticsBucketin theDataLakeStorageconstruct containsAnalyticsBucketso we can easily identify which resource it is. - Resources IDs should not repeat the name of the construct they are part of. For example in
DataLakeStorageconstruct, the ID of the bronze bucket isBronzeBucketnotDataLakeStorageBronzeBucket. - Do not use any property value in construct IDs because it can be a reference to another resource only resolved at deploy time.
Unit testing
Implement unit tests by validating CloudFormation templates that are generated by CDK. CDK provides helpers for this approach with fine-grained assertions.
- Implement one test suite for default configuration and one test suite for custom configuration. A test suite is a group of
testclauses within adescribeblock. - Test one feature per
testclause so we can easily identify what is failing. You can find an example here - Every change requires a unit test.
Integration test
We run integration tests (also call end-to-end tests) in our AWS account on a regular basis or manually when a Pull Request is approved. Integration tests are provisioning constructs, then checking output values from the CDK deployment, and finally destroying the stack.
Be sure to follow these recommendations when implementing end-to-end tests:
- Create a stack using the
TestStackconstruct. - Configure the deployment timeout according to the construct (see the
DataLakeStorageexample). - Test the deployment of 2 resources in the same account to validate uniqueness of names.
- Set the global AND the construct removal policies to DESTROY, then check in the CloudFormation console if there is no resource skipped when the stack is deleted.
Documentation
See the compiled documentation guide.