AWS CDK is often written in TypeScript, but can be written in a variety of programming languages.
In this article, I would like to write about the features of AWS CDK by Golang compared to TypeScript.
Assumptions
The Golang codes presented here use v1.18.7.
The AWS CDK version is v2.52.0.
The features of AWS CDK for Go that I will discuss in this article are mainly the differences from the TypeScript version.
The basic features can be found in the document here.
Features (differences from TypeScript)
Directory structure at init
The directory structure created after cdk init app --language go
, in the case of Go, is as follows.
The directory (repository) named cdk-go
.
❯ tree
.
├── README.md
├── cdk-go.go
├── cdk-go_test.go
├── cdk.json
└── go.mod
In the case of TypeScript, the bin
, lib
and test
directories are separated, and modules are installed based on package.json at the same time as init, and node_modules
are also generated.
Go is much simpler and has no hierarchical structure, which is probably due to the flat directory structure often used when developing in Go.
File contents at init
In Go, only cdk-go.go
and cdk-go_test.go
Go files are generated, as shown above.
In TypeScript, the stack generation code is written in the file generated in bin
and the stack definition code in the file generated in lib
, but in Go, both are written together in the same single file (cdk-go.go
).
This is the initial code of cdk-go.go
.
package main
import (
"github.com/aws/aws-cdk-go/awscdk/v2"
// "github.com/aws/aws-cdk-go/awscdk/v2/awssqs"
"github.com/aws/constructs-go/constructs/v10"
"github.com/aws/jsii-runtime-go"
)
type CdkGoStackProps struct {
awscdk.StackProps
}
func NewCdkGoStack(scope constructs.Construct, id string, props *CdkGoStackProps) awscdk.Stack {
var sprops awscdk.StackProps
if props != nil {
sprops = props.StackProps
}
stack := awscdk.NewStack(scope, &id, &sprops)
// The code that defines your stack goes here
// example resource
// queue := awssqs.NewQueue(stack, jsii.String("CdkGoQueue"), &awssqs.QueueProps{
// VisibilityTimeout: awscdk.Duration_Seconds(jsii.Number(300)),
// })
return stack
}
func main() {
defer jsii.Close()
app := awscdk.NewApp(nil)
NewCdkGoStack(app, "CdkGoStack", &CdkGoStackProps{
awscdk.StackProps{
Env: env(),
},
})
app.Synth(nil)
}
// env determines the AWS environment (account+region) in which our stack is to
// be deployed. For more information see: https://docs.aws.amazon.com/cdk/latest/guide/environments.html
func env() *awscdk.Environment {
// If unspecified, this stack will be "environment-agnostic".
// Account/Region-dependent features and context lookups will not work, but a
// single synthesized template can be deployed anywhere.
//---------------------------------------------------------------------------
return nil
// Uncomment if you know exactly what account and region you want to deploy
// the stack to. This is the recommendation for production stacks.
//---------------------------------------------------------------------------
// return &awscdk.Environment{
// Account: jsii.String("123456789012"),
// Region: jsii.String("us-east-1"),
// }
// Uncomment to specialize this stack for the AWS Account and Region that are
// implied by the current CLI configuration. This is recommended for dev
// stacks.
//---------------------------------------------------------------------------
// return &awscdk.Environment{
// Account: jsii.String(os.Getenv("CDK_DEFAULT_ACCOUNT")),
// Region: jsii.String(os.Getenv("CDK_DEFAULT_REGION")),
// }
}
The struct CdkGoStackProps
has functions NewCdkGoStack
, main
and env
.
NewXxxStack
is the stack definition (in lib
in TypeScript) and main
is the stack generation (in bin
in TypeScript).
The defer jsii.Close()
in the main()
function is to make the CDK application clean up automatically (see official reference above).
The function env
is not found in the TypeScript version, but is unique to the Go version (written from the beginning), and returns an awscdk.Environment
structure with Account
and Region
.
By the way, the test codes at initialization are here.
package main
// import (
// "testing"
// "github.com/aws/aws-cdk-go/awscdk/v2"
// "github.com/aws/aws-cdk-go/awscdk/v2/assertions"
// "github.com/aws/jsii-runtime-go"
// )
// example tests. To run these tests, uncomment this file along with the
// example resource in cdk-go_test.go
// func TestCdkGoStack(t *testing.T) {
// // GIVEN
// app := awscdk.NewApp(nil)
// // WHEN
// stack := NewCdkGoStack(app, "MyStack", nil)
// // THEN
// template := assertions.Template_FromStack(stack)
// template.HasResourceProperties(jsii.String("AWS::SQS::Queue"), map[string]interface{}{
// "VisibilityTimeout": 300,
// })
// }
Pointer conversion with jsii
Looking at the above code, in some places, especially when specifying parameters, functions such as jsii.Number
, jsii.String
surround the value specified by the parameter.
queue := awssqs.NewQueue(stack, jsii.String("CdkGoQueue"), &awssqs.QueueProps{
VisibilityTimeout: awscdk.Duration_Seconds(jsii.Number(300)),
})
The jsii.Number
and jsii.String
are functions that only return a pointer to the variable you give them.
In fact, this is a rule when specifying parameters in the Go version of CDK.
Because the Go language does not have a null-allowed type, and the only type that can contain nil
is the pointer type. (The word "only" may be misleading, but I am not going to go into Go here, but translate the above document.)
So, to realize "no property" for an optional parameter, I can pass a pointer type that can be nil
(see document above).
This is the main area of use.
- jsii.String
- jsii.Number
- jsii.Bool
- jsii.Time
This is one of the major and troublesome features of the Go version of the CDK, but once you get used to it, it is nothing(?). I think it's not a problem once you get used to it. (It will make the outlook worse, but...)
Field and method names in CDK module are Pascal case
In the TypeScript version, I think it is CamelCase, but in the Go version, basically the field and method names provided in CDK are PascalCase (beginning with a capital letter) (see the above document).
This is Go's language specification that Public (out-of-package) calls can only be invoked with Pascal case.
When coding in Go, if the scope is Private (properly, a package), it will be CamelCase.
Here's an example: be careful when you try to bring a sample written in TypeScript to Go by copying it.
cfnAppRunner.SetHealthCheckConfiguration(&awsapprunner.CfnService_HealthCheckConfigurationProperty{
Path: jsii.String("/"),
Protocol: jsii.String("HTTP"),
})
Cannot do escape hatch.
Yes, I am having trouble with the escape hatch. This is a very big problem unique to the Go version.
First, CDK has a convenient feature called L2 Construct that allows you to build resources according to best practices by simply specifying a few required values.
There is also L1 Construct, which allows you to build resources by specifying them in a manner consistent with CloudFormation.
While L2 Construct is convenient and easy to create resources, it also abstracts parameters, so there are some parameters that are not specified in L2.
Therefore, there is a way to cast a resource created with L2 Construct to an L1 Construct type and later override the parameters that can only be specified with L1, which is the "escape hatch".
However, the CDK for Go does not currently support escape hatch according to the workshop.
CDK has a concept called the escape hatch , which allows you to modify an L2 construct's underlying CFN Resource to access features that are supported by CloudFormation but not yet supported by CDK. Unfortunately, CDK for Go does not yet support this functionality, so I have to create the resource through the L1 construct. See this GitHub issue for more information and to follow progress on support for CDK escape hatches in Go.
So what do you do, if you want to specify a parameter that can't be specified in L2 Construct, it has to be L1 Construct?
In the meantime, there is a way to do an escape hatch. You can do it by using the method jsii.Get
.
var cfnAppRunner awsapprunner.CfnService
jsii.Get(apprunnerServiceL2.Node(), "defaultChild", &cfnAppRunner)
cfnAppRunner.SetAutoScalingConfigurationArn(autoScalingConfigurationArn)
However, this jsii.Get
is deprecated (see this page), so I feel like I have no choice but to use it.
Snapshot test
The "snapshot test" is a test that you will almost certainly see as a CDK test.
You can do it with jest
in TypeScript (javascript), but in Go, you can do it as follows.
Generate a template from the stack and pass the JSONized version to cupaloy.SnapshotT
to execute the snapshot.
stack := NewAppRunnerStack(app, "AppRunnerStack", appRunnerStackProps)
template := assertions.Template_FromStack(stack, nil)
templateJson := template.ToJSON()
t.Run("Snapshot Test", func(t *testing.T) {
cupaloy.SnapshotT(t, templateJson)
})
Extra
The following is not so much a difference from TypeScript, but more like a development method unique to Go.
Workspaces mode
When developing with CDK, you probably have multiple code groups.
First, there is the CDK code group, and then there is the code group used for Lambda and ECS.
In such cases, it is possible to prepare those directories in one repository and build them by sharing go.mod in the root directory, but you will want to prepare a go.mod that combines what is needed in each one (multi-module).
(By the way, in TypeScript, you can use aws-lambda-nodejs
to get a nice bundle of Lambda and CDK definitions in a single package.json file, respectively.)
Go introduced a feature called "Workspaces mode" in version 1.18 that allows multi-module (easier than before).
This allows for multi-module development and build with a go.mod for each subdirectory.
Hitting the following command will put you in Workspaces mode.
go work init moduleA moduleB
The following go.work file will then be created to enable multi-module development.
go 1.18
use (
./moduleA
./moduleB
)
Example Implementation
There is an actual example that I made with AWS CDK for Go, if you would like to see it. (In the example, I am making App Runner.)
Finally
I have summarized my experience with Go's CDK because I found it to be quite different from the TypeScript version.
To be honest, it is still in its infancy compared to the TypeScript version of CDK, but I am glad to be able to write in Go!
Top comments (0)