Things in AWS frequently have names. Things like AWS S3 buckets or AWS Lambda Functions. And when using the AWS CDK, Constructs
have names so that they can be referenced elsewhere in code or error messages.
I rely on four scopes to name things, what scope applies to what resource is only discovered in docs or trial-and-error.
For example, S3 bucket names are global. If two users each in unique accounts in unique regions attempt to create a bucket with the same name, the first to try wins. Sometimes these names are called “physical ids”.
On the otherside of the spectrum, if you are creating a new AWS CloudFormation stack, it must have a unique name in the account and region (us-west-1
). The name could be used in another region concurrently.
The naming scopes I work with are:
- global: e.g. S3 buckets
- account: e.g. Elastic Container Registry (ECR)
- region: e.g. Virtual Private Cloud (VPC)
- stack: e.g. any
Construct
I sometimes throw in an orthogonal name for the current lifecycle stage of the deployment. For example, “dev”, “uat”, or “prod”. This further prevents crossover or issues within a shared account (for those at companies that limit the number of AWS accounts granted to developers).
To further confuse matters, names have a kind of de facto format. When naming a Construct
, CamelCase is frequently used. And when naming a bucket, lower-hyphen format is used.
If you consider many names are contextual, making them hierarchical, it makes sense to have a simple API that lets you build a name, then apply it where it’s needed.
The project clusterless-commons
provides a Label
interface for building labels.
Label label = Label.of("lower").with("case");
Assertions.assertEquals("LowerCase", label.camelCase());
Assertions.assertEquals("lowerCase", label.lowerCamelCase());
Assertions.assertEquals("lower-case", label.lowerHyphen());
Assertions.assertEquals("lower/case", label.lowerHyphenPath());
Sometimes you may not want any formatting applied, the Fixed
class locks an element of the final label.
Label label = Label.of("DataRepo").with(Fixed.of("us-west-2"));
Assertions.assertEquals("data-repo-us-west-2", label.lowerHyphen());
Assertions.assertEquals("data-repo/us-west-2", label.lowerHyphenPath());
When using AWS regions in a label, it may be easier to simply use Region.of("us-west-2")
, this way a custom Construct
and require region information on the constructor by declaring a parameter type as Region
.
There are a few other useful Label
derivatives in the javadoc.
If you are willing to adopt the base classes ScopedApp
, ScopedStack
, and ScopedConstruct
, naming things becomes a little simpler.
The class ResourceNames
has a few helper methods that return a Label
with the requested scope.
ResourceNames#globalUniqueLabel()
ResoruceNames#accountUniqueLabel()
ResourceNames#regionUniqueLabel()
Each of these take a Construct
and attempt to find the parent ScopedApp
for the relevant metadata.
Respectively, you can end up with names like:
Globally unique:
Label region = Region.of(Stack.of(scope).getRegion());
Label account = Fixed.of(Stack.of(scope).getAccount());
Label stage = ScopedApp.scopedOf(scope).stage();
return stage.upperOnly()
.with(name)
.with(account)
.with(region);
Account unique:
Label region = Region.of(Stack.of(scope).getRegion());
Label stage = ScopedApp.scopedOf(scope).stage();
return stage.upperOnly()
.with(name)
.with(region);
Region unique:
Stage stage = ScopedApp.scopedOf(scope).stage();
return stage.upperOnly()
.with(name);
Note, most names may have a size limit. Lambda function names are limited to 64 characters. Label
currently does nothing to enforce this, but it could be added. But Label
does support abbreviations via Label#of("LongValue", "abb");
.
Links: