The AWS CDK is for most of the people I interact with the first-time developers who get to write Infrastructure as Code in a proper programming language (sorry HCL, we were never friends).
This sounds like a game changer but when you look at a lot of the examples it doesn’t really feel like programming. Instantiating classes with the same attributes you used in YAML or JSON doesn’t feel like a big gain.
However, when you give it a shot there are some pretty neat things you can do.
What is the AWS CDK
The AWS CDK is a library for multiple languages and a cli to provision infrastructure on AWS and more.
You use one of the major programming languages like TypeScript/Python/Java/C# etc. to define your infrastructure as instantiated classes. The CLI can turn these objects of the respective programming languages and turn them into CloudFormation templates and deploy them via the CloudFormation service.
I won’t go into more detail here than this little tldr; use your favorite search engine to find out more if you feel like it.
Tips and tricks
When you use the CDK out of the box, I don’t find it fun to use. Most classes have lots of parameters and some constructs depend on other constructs that then have more parameters and there is no logic flow.
The classes are modelled after the AWS services rather than the way someone might approach building an app.
It’s a good thing you can use proper programming languages to do something about it.
Trick 1: Build your own constructs with sensible defaults
Are you using lambda functions in Python with tracing? Build a PythonFunction that enables tracing with a fixed layer for lambda Powertools.
Don’t want to forget to encrypt a S3 bucket and make it private? Build an EncryptedBucket construct.
For any resource that is used multiple times, it’s probably a good idea to build your own construct and set some defaults.
*Trick 2: Co-locate application and infrastructure code
*
You could technically put application and infrastructure code in the same file but that might not be a very good idea.
What I like to do is e.g. co-locate my lambda function code with the infrastructure code for that lambda in different files but in the same folder. And by that I mean the function definition and all other components exclusive to that feature like e.g. a path in the API gateway and the DynamoDB table the lambda writes to.
This way, if I’m working on a feature, I have everything that makes up the feature in one place which makes updates much easier. This leads to the nice property that my CDK stack definition is entirely made up of a collection of features that form the stack.
*Tip 3: Abstract your constructs the way you would think about your architecture
*
I think this one needs a little more explaining.
What I mean is that the way the AWS CDK constructs are built is by following the topology of AWS services and service boundaries. This might not be how you would want to design systems.
E.g. When I write a function for a feature, I would think about the architecture from the viewpoint of the function.
I would think this way. “This function is being triggered from a POST request from an API. It also writes to this database.”
The AWS constructs make you declare it as follows. “This API has this path with this method and this integration to this lambda function. This table grants write access to this function.”
I’ll write it down in pseudo-code (assuming you’re using Trick 1 for both my and AWS’ way)
`# my way
my_api = RestAPI(…)
my_table = EncryptedTable(…)
my_function = PythonFunction(…)
my_function.is_triggered_by_api(my_api, “/my_feature”, “POST”)
my_function.writes_to(my_table)`
`# AWS CDK way
my_api = RestAPI(…)
my_table = EncryptedTable(…)
my_function = PythonFunction(…)
my_api.add_method(“/my_feature”, “POST”, my_function)
my_table.grants_readwrite(my_function)`
If you think in integration patterns or even in your domain language, you can communicate that via your constructs. The code above is just a simple example.
Trick 4: don’t duplicate schemas
Sometimes the boundaries in what is application code and infrastructure code are blurry. That’s not necessarily a problem as it allows developers to get things done in multiple ways. However, it can be a problem when both sides have definitions that have to match.
A good example would be DynamoDB. Most developers would likely use an abstraction layer in their application code to enforce a schema to be able to work with types. But you might not want all the config to be purely in your application code (like streams, backups, billing, etc.).
What you can do is take your application code schema and feed it into your infrastructure as code. But please note, that this might require reflection or some other technique to access the metadata of a class. Here is some pseudo code to elaborate what I mean
`# Application code ORM DynamoDB table definition
@Table
class Flight
fight_id: str
takeoff: daytime
origin: str
destination: str
takeoff_index: SecondaryIndex
# Infrastructure code (in a different file)
flight_table = EncryptedTable.from_orm_class(Flight, Mode.OnDemand)`
Final Tip: CDK synth and is your friend
If you’re using IaC, chances are you’re deploying via some sort of pipeline. And likely deployment is one of the last steps. This means that anytime you get something wrong in CDK your feedback loop is slow. To speed things up, you can first check that your CDK code can be synthesized to CloudFormation by running CDK synth (either via your test runner or the CLI).
I hope my tips and tricks have helped you in one way or another. If you have any other ones you’d like to share, I’d be grateful for any comments!
Photo by John Fornander on Unsplash
Top comments (0)