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
RETAIN
by default but the possibility toDESTROY
resources 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 theDataLakeStorage
and code example is available here. -
Extend
Construct
vs 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 (usingthis
parameter 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
DataLakeStorage
construct that contains a KMSKey
attached to theAnalyticsBucket
part of theDataLakeStorage
, we re-expose the key so you can access it directly viadataLakeStorage.key
instead 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
IBucket
instead ofBucket
as the property or public member of your construct. -
Avoid using generic
string
parameters 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
string
property for your construct. Instead, use anIRole
type in the properties and get the ARN from the attributes. - Do not use S3 URI in the form of
s3://MYBUCKET/MYPREFIX
. Instead, use anIBucket
type and a prefix instring
type.
- Do not use an IAM Role ARN as a
-
Extend the
TrackedConstruct
instead of the base CDKConstruct
to measure the number of deployments. See the dedicated documentation. -
Tag all resources that are created by the construct and support tagging. Extending the
TrackedConstruct
will automatically add a tag to all AWS resources that are created by CDK. Some constructs will create resources outside of CDK like theSparkEmrServerlessJob
that triggers EMR Serverless jobs from a Step Function. For this kind of situation, use the resource parameters to tag it withDSF_OWNED_TAG: true
like 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
Interface
in the construct properties because they are translated in PythonProtocols
by 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
BronzeAnalyticsBucket
in theDataLakeStorage
construct containsAnalyticsBucket
so 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
DataLakeStorage
construct, the ID of the bronze bucket isBronzeBucket
notDataLakeStorageBronzeBucket
. - 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
test
clauses within adescribe
block. - Test one feature per
test
clause 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
TestStack
construct. - Configure the deployment timeout according to the construct (see the
DataLakeStorage
example). - 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.