-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
DESIGN_GUIDELINES.md
1613 lines (1255 loc) · 62.1 KB
/
DESIGN_GUIDELINES.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# AWS Construct Library Design Guidelines
The AWS Construct Library is a rich class library of CDK constructs which
represent all resources offered by the AWS Cloud and higher-level constructs for
achieving common tasks.
The purpose of this document is to provide guidelines for designing the APIs in
the AWS Construct Library in order to ensure a consistent and integrated
experience across the entire AWS surface area.
* [Preface](#preface)
* [What's Included](#what-s-included)
* [API Design](#api-design)
* [Modules](#modules)
* [Construct Class](#construct-class)
* [Construct Interface](#construct-interface)
* [Owned vs. Unowned Constructs](#owned-vs-unowned-constructs)
* [Abstract Base](#abstract-base)
* [Props](#props)
* [Types](#types)
* [Defaults](#defaults)
* [Flat](#flat)
* [Concise](#concise)
* [Naming](#naming)
* [Property Documentation](#property-documentation)
* [Enums](#enums)
* [Unions](#unions)
* [Attributes](#attributes)
* [Configuration](#configuration)
* [Prefer Additions](#prefer-additions)
* [Dropped Mutations](#dropped-mutations)
* [Factories](#factories)
* [Imports](#imports)
* [“from” Methods](#-from--methods)
* [From-attributes](#from-attributes)
* [Roles](#roles)
* [Resource Policies](#resource-policies)
* [VPC](#vpc)
* [Grants](#grants)
* [Metrics](#metrics)
* [Events](#events)
* [Connections](#connections)
* [Integrations](#integrations)
* [State](#state)
* [Physical Names - TODO](#physical-names---todo)
* [Tags](#tags)
* [Secrets](#secrets)
* [Project Structure](#project-structure)
* [Code Organization](#code-organization)
* [Implementation](#implementation)
* [General Principles](#general-principles)
* [Construct IDs](#construct-ids)
* [Errors](#errors)
* [Avoid Errors If Possible](#avoid-errors-if-possible)
* [Error reporting mechanism](#error-reporting-mechanism)
* [Throwing exceptions](#throwing-exceptions)
* [Never Catch Exceptions](#never-catch-exceptions)
* [Attaching (lazy) Validators](#attaching--lazy--validators)
* [Attaching Errors/Warnings](#attaching-errors-warnings)
* [Error messages](#error-messages)
* [Tokens](#tokens)
* [Documentation](#documentation)
* [Inline Documentation](#inline-documentation)
* [Readme](#readme)
* [Testing](#testing)
* [Unit tests](#unit-tests)
* [Integration tests](#integration-tests)
* [Versioning](#versioning)
* [Naming & Style](#naming---style)
* [Naming Conventions](#naming-conventions)
* [Coding Style](#coding-style)
## Preface
As much as possible, the guidelines in this document are enforced using the
[**awslint** tool](https://www.npmjs.com/package/awslint) which reflects on the
APIs and verifies that the APIs adhere to the guidelines. When a guideline is
backed by a linter rule, the rule name will be referenced like this:
_[awslint:resource-class-is-construct]_.
For the purpose of this document, we will use "Foo" to denote the official name
of the resource as defined in the AWS CloudFormation resource specification
(i.e. "Bucket", "Queue", "Topic", etc). This notation allows deriving names from
the official name. For example, `FooProps` would be `BucketProps`, `TopicProps`,
etc, `IFoo` would be `IBucket`, `ITopic` and so forth.
The guidelines in this document use TypeScript (and npm package names) since
this is the source programming language used to author the library, which is
later packaged and published to all programming languages through
[jsii](https:/awslabs/jsii).
When designing APIs for the AWS Construct Library (and these guidelines), we
follow the tenets of the AWS CDK:
* **Meet developers where they are**: our APIs are based on the mental model of
the user, and not the mental model of the service APIs, which are normally
designed against the constraints of the backend system and the fact that these
APIs are used through network requests. It's okay to enable multiple ways to
achieve the same thing, in order to make it more natural for users who come from
different mental models.
* **Full coverage**: the AWS Construct Library exposes the full surface area of
AWS. It is not opinionated about which parts of the service API should be
used. However, it offers sensible defaults to allow users to get started quickly
with best practices, but allows them to fully customize this behavior. We use a
layered architecture so that users can choose the level of abstraction that fits
their needs.
* **Designed for the CDK**: the AWS Construct Library is primarily optimized for
AWS customers who use the CDK idiomatically and natively. As much as possible,
the APIs are non-leaky and do not require that users understand how AWS
CloudFormation works. If users wish to “escape” from the abstraction, the APIs
offer explicit ways to do that, so that users won't be blocked by missing
capabilities or issues.
* **Open**: the AWS Construct Library is an open and extensible framework. It is
also open source. It heavily relies on interfaces to allow developers to extend
its behavior and provide their own custom implementations. Anyone should be able
to publish constructs that look & feel exactly like any construct in the AWS
Construct Library.
* **Designed for jsii**: the AWS Construct Library is built with jsii. This
allows the library to be used from all supported programming languages. jsii
poses restrictions on language features that cannot be idiomatically represented
in target languages.
## What's Included
The AWS Construct Library, which is shipped as part of the AWS CDK constructs
representing AWS resources.
The AWS Construct Library has multiple layers of constructs, beginning
with low-level constructs, which we call _CFN Resources_ (short for
CloudFormation resources), or L1 (short for "level 1"). These constructs
directly represent all resources available in AWS CloudFormation. CFN Resources
are periodically generated from the AWS CloudFormation Resource
Specification. They are named **Cfn**_Xyz_, where _Xyz_ is name of the
resource. For example, CfnBucket represents the AWS::S3::Bucket AWS
CloudFormation resource. When you use Cfn resources, you must explicitly
configure all resource properties, which requires a complete understanding of
the details of the underlying AWS CloudFormation resource model.
The next level of constructs, L2, also represent AWS resources, but with a
higher-level, intent-based API. They provide similar functionality, but provide
the defaults, boilerplate, and glue logic you'd be writing yourself with a CFN
Resource construct. L2 constructs offer convenient defaults and reduce the need
to know all the details about the AWS resources they represent, while providing
convenience methods that make it simpler to work with the resource. For example,
the `s3.Bucket` class represents an Amazon S3 bucket with additional properties
and methods, such as `bucket.addLifeCycleRule()`, which adds a lifecycle rule to
the bucket.
Examples of behaviors that an L2 commonly include:
- Strongly-typed modeling of the underlying L1 properties
- Methods for integrating other AWS resources (e.g., adding an event notification to
an S3 bucket).
- Modeling of permissions and resource policies
- Modeling of metrics
In addition to the above, some L2s may introduce more complex and
helpful functionality, either part of the original L2 itself, or as part of a
separate construct. The most common form of these L2s are integration constructs
that model interactions between different services (e.g., SNS publishing to SQS,
CodePipeline actions that trigger Lambda functions).
The next level of abstraction present within the CDK are what we designate as
"L2.5s": a step above the L2s in terms of abstraction, but not quite at the
level of complete patterns or applications. These constructs still largely
focus on a single logical resource -- in constrast to "patterns" which combine
multiple resources -- but are customized for a specific common usage scenario of
an L2. Examples of L2.5s in the CDK are `aws-apigateway.LambdaRestApi`,
`aws-lambda-nodejs.NodeJsFunction`, `aws-rds.ServerlessCluster` and `eks.FargateCluster`.
L2.5 constructs will be considered for inclusion in the CDK if they...
- cover a common usage scenario that can be used by a significant portion of
the community;
- provide significant ease of use over the base L2 (via usage-specific defaults
convenience methods or improved strong-typing);
- simplify or enable another L2 within the CDK
The CDK also currently includes some even higher-level constructs, which we call
patterns. These constructs often involve multiple kinds of resources and are
designed to help you complete common tasks in AWS or represent entire
applications. For example, the
`aws-ecs-patterns.ApplicationLoadBalancedFargateService` construct represents an
architecture that includes an AWS Fargate container cluster employing an
Application Load Balancer (ALB). These patterns are typically difficult to
design to be one-size-fits-all and are best suited to be published as separate
libraries, rather than included directly in the CDK. The patterns that currently
exist in the CDK will be removed in the next CDK major version (CDKv2).
## API Design
### Modules
AWS resources are organized into modules based on their AWS service. For
example, the "Bucket" resource, which is offered by the Amazon S3 service will
be available under the **aws-cdk-lib/aws-s3** module. We will use the “aws-” prefix
for all AWS services, regardless of whether their marketing name uses an
“Amazon” prefix (e.g. “Amazon S3”). Non-AWS services supported by AWS
CloudFormation (like the Alexa::ASK namespace) will be **@aws-cdk/alexa-ask**.
The name of the module is based on the AWS namespace of this service, which is
consistent with the AWS SDKs and AWS CloudFormation _[awslint:module-name]_.
All major versions of an AWS namespace will be mastered in the AWS Construct
Library under the root namespace. For example resources of the **ApiGatewayV2**
namespace will be available under the **aws-cdk-lib/aws-apigateway** module (and
not under “v2) _[awslint:module-v2]_.
In some cases, it makes sense to introduce secondary modules for a certain
service (e.g. aws-s3-notifications, aws-lambda-event-sources, etc). The name of
the secondary module will be
**aws-cdk-lib/aws-xxx-\<secondary-module\>**_[awslint:module-secondary]_.
Documentation for how to use secondary modules should be in the main module. The
README file should refer users to the central module
_[awslint:module-secondary-readme-redirect]_.
### Construct Class
Constructs are the basic building block of CDK applications. They represent
abstract cloud components of any complexity. Constructs in the AWS Construct
Library normally represent physical AWS resources (such as an SQS queue) but
they can also represent abstract composition of other constructs (such as
**LoadBalancedFargateService**).
Most of the guidelines in this document apply to all constructs in the AWS
Construct Library, regardless of whether they represent concrete AWS resources
or abstractions. However, you will notice that some sections explicitly call out
guidelines that apply only to AWS resources (and in many cases
enforced/implemented by the **Resource** base class).
AWS services are modeled around the concept of *resources*. Services normally
expose one or more resources through their APIs, which can be provisioned
through the APIs control plane or through AWS CloudFormation.
Every resource available in the AWS platform will have a corresponding resource
construct class to represents it. For example, the **s3.Bucket** construct
represents Amazon S3 Buckets, the **dynamodb.Table** construct represents an
Amazon DynamoDB table. The name of resource constructs must be identical to the
name of the resource in the AWS API, which should be consistent with the
resource name in the AWS CloudFormation spec _[awslint:resource-class]_.
> The _awslint:resource-class_ rule is a **warning** (instead of an error). This
allows us to gradually expand the coverage of the library.
Classes which represent AWS resources are constructs and they must extend the
**cdk.Resource** class directly or indirectly
_[awslint:resource-class-extends-resource]_.
> Resource constructs are normally implemented using low-level CloudFormation
(“CFN”) constructs, which are automatically generated from the AWS
CloudFormation resource specification.
The signature (both argument names and types) of all construct initializers
(constructors) must be as follows _[awslint:construct-ctor]_:
```ts
constructor(scope: cdk.Construct, id: string, props: FooProps)
```
The **props** argument must be of type FooProps
[_awslint:construct-ctor-props-type_].
If all props are optional, the `props` argument must also be optional
_[awslint:construct-ctor-props-optional]_.
```ts
constructor(scope: cdk.Construct, id: string, props: FooProps = {})
```
> Using `= {}` as a default value is preferable to using an optional qualifier
(`?`) since it will ensure that props will never be `undefined` and therefore
easier to parse in the method body.
As a rule of thumb, most constructs should directly extend the **Construct** or
**Resource** instead of another construct. Prefer representing polymorphic
behavior through interfaces and not through inheritance.
Construct classes should extend only one of the following classes
[_awslint:construct-inheritence_]:
* The **Resource** class (if it represents an AWS resource)
* The **Construct** class (if it represents an abstract component)
* The **XxxBase** class (which, in turn extends **Resource**)
All constructs must define a static type check method called **isFoo** with the
following implementation [_awslint:static-type-check_]:
```ts
const IS_FOO = Symbol.for('@aws-cdk/aws-foo.Foo');
export class Foo {
public static isFoo(x: any): x is Foo {
return IS_FOO in x;
}
constructor(scope: Construct, id: string, props: FooProps) {
super(scope, id);
Object.defineProperty(this, IS_FOO, { value: true });
}
}
```
### Construct Interface
One of the important tenets of the AWS Construct Library is to use strong-types
when referencing resources across the library. This is in contrast to how AWS
backend APIs (and, consequently, AWS CloudFormation) model reference via one of
their *runtime attributes* (such as the resource's ARN). Since the AWS CDK is a
client-side abstraction, we can offer developers a much richer experience by
using *object references* instead of *attribute references*.
Using object references instead of attribute references allows consumers of
these objects to have a richer interaction with the consumed object. They can
reference runtime attributes such as the resource's ARN, but also utilize logic
encapsulated by the target object.
Here's an example: when a user defines an S3 bucket, they can pass in a KMS key
that will be used for bucket encryption:
```ts
new s3.Bucket(this, 'MyBucket', { encryptionKey: key });
```
The **Bucket** class can now use **key.keyArn** to obtain the ARN for the key,
but it can also call the **key.grantEncrypt** method as a result of a call to
**bucket.grantWrite**. Separation of concerns is a basic OO design principle:
the fact that the Bucket class needs the ARN or that it needs to request
encryption permissions are not the user's concern, and the API of the Bucket
class should not “leak” these implementation details. In the future, the Bucket
class can decide to interact differently with the **key** and this won't require
expanding its surface area. It also allows the **Key** class to change its
behavior (i.e. add an IAM action to enable encryption of certain types of keys)
without affecting the API of the consumer.
#### Owned vs. Unowned Constructs
Using object references instead of attribute references provides a richer API,
but also introduces an inherent challenge: how do we reference constructs that
are not defined inside the same app (“**owned**” by the app)? These could be
resources that were created by some other AWS CDK app, via the AWS console,
etc. We call these **“unowned” constructs.**
In order to model this concept of owned and unowned constructs, all constructs
in the AWS Construct Library should always have a corresponding **construct
interface**. This interface includes the API of the construct
_[awslint:construct-interface]_.
Therefore, when constructs are referenced ***anywhere*** in the API (e.g. in
properties or methods of other resources or higher-level constructs), the
resource interface (`IFoo`) should be used over concrete resource classes
(`Foo`). This will allow users to supply either internal or external resources
_[awslint:ref-via-interface]_.
Construct interfaces must extend the **IConstruct** interface in order to allow
consumers to take advantage of common resource capabilities such as unique IDs,
paths, scopes, etc _[awslint:construct-interface-extends-iconstruct]_.
Constructs that directly represent AWS resources (most of the constructs in the
AWS Construct Library) should extend **IResource** (which, transitively, extends
**IConstruct**) _[awslint:resource-interface-extends-resource]_.
#### Abstract Base
It is recommended to implement an abstract base class **FooBase** for each
resource **Foo**. The base class would normally implement the entire
construct interface and leave attributes as abstract properties.
```ts
abstract class FooBase extends Resource implements IFoo {
public abstract fooName: string;
public abstract fooArn: string;
// .. concrete implementation of IFoo (grants, metrics, factories),
// should only rely on "fooName" and "fooArn" theoretically
}
```
The construct base class can then be used to implement the various
deserialization and import methods by defining an ad-hoc local class which
simply provides an implementation for the attributes (see “Serialization” below
for an example).
The abstract base class should be internal and not exported in the module's API
_[awslint:construct-base-is-private]_. This is only a recommended (linter
warning).
### Props
Constructs are defined by creating a new instance and passing it a set of
**props** to the constructor. Throughout this document, we will refer to these
as “props” (to distinguish them from JavaScript object properties).
The props argument for the **Foo** construct should be a struct (interface with
only readonly properties) named **FooProps** _[awslint:props-struct-name]_.
> Even if a construct props simply extends from some other Props struct and does
not add any new properties, you should still define it, so it will be
extensible in the future without breaking users in languages like Java where
the props struct name is explicitly named.
Props are the most important aspect of designing a construct. Props are the
entry point of the construct. They should reflect the entire surface area of the
service through semantics that are intuitive to how developers perceive the
service and its capabilities.
When designing the props of an AWS resource, consult the AWS Console experience
for creating this resource. Service teams spend a lot of energy thinking about
this experience. This is a great resource for learning about the mental model of
the user. Aligning with the console also makes it easier for users to jump back
and forth between the AWS Console (the web frontend of AWS) and the CDK (the
“programmatic frontend” of AWS).
AWS constructs should *not* have a “props” property
[_awslint:props-no-property_].
Construct props should expose the *full set* of capabilities of the AWS service
through a declarative interface [_awslint:props-coverage_].
This section describes guidelines for construct props.
#### Types
Use **strong types** (and specifically, construct interfaces) instead of
physical attributes when referencing other resources. For example, instead of
**keyArn**, use **kms.IKey** [_awslint:props-no-arn-refs_].
Do not “leak” the details or types of the CFN layer when defining your construct
API. In almost all cases, a richer object-oriented API can be exposed to
encapsulate the low-level surface [_awslint:props-no-cfn-types_].
Do not use the **Token** type. It provides zero type safety, and is a functional
interface that may not translate cleanly in other JSII runtimes. Therefore, it should
be avoided wherever possible [_awslint:props-no-tokens_].
**deCDK** allows users to synthesize CDK stacks through a CloudFormation-like
template, similar to SAM. CDK constructs are represented in deCDK templates
like CloudFormation resources. Technically, this means that when a construct
is defined, users supply an ID, type and a set of properties. In order to
allow users to instantiate all AWS Construct Library constructs through the
deCDK syntax, we impose restrictions on prop types _[awslint:props-decdk]_:
* Primitives (string, number, boolean, date)
* Collections (list, map)
* Structs
* Enums
* Enum-like classes
* Union-like classes
* References to other constructs (through their construct interface)
* Integration interfaces (interfaces that have a “**bind**” method)
#### Defaults
A prop should be *required* only if there is no possible sensible default value
that can be provided *or calculated*.
Sensible defaults have a tremendous impact on the developer experience. They
offer a quick way to get started with minimal cognitive load, but do not limit users
from harnessing the full power of the resource, and customizing its behavior.
> A good way to determine what's the right sensible default is to refer to the
AWS Console resource creation experience. In many cases, there will be
alignment.
The **@default** documentation tag must be included on all optional properties
of interfaces.
In cases where the default behavior can be described by a value (typically the
case for booleans and enums, sometimes for strings and numbers), the value immediately
follows the **@default** tag and should be a valid JavaScript value (as in:
`@default false`, or `@default "stringValue"`).
In the majority of cases, the default behavior is not a specific value but
rather depends on circumstances/context. The default documentation tag must
begin with a “**-**" and then include a description of the default behavior
_[awslint:props-default-doc]_. This is specially true if the property
is a complex value or a reference to an object: don't write `@default
undefined`, describe the behavior that happens if the property is not
supplied.
Describe the default value or default behavior, even if it's not CDK that
controls the default. For example, if an absent value does not get rendered
into the template and it's ultimately the AWS *service* that determines the
default behavior, we still describe it in our documentation.
Examples:
```ts
// ✅ DO - uses a '-' and describes the behavior
/**
* External KMS key to use for bucket encryption.
*
* @default - if encryption is set to "Kms" and this property is undefined, a
* new KMS key will be created and associated with this bucket.
*/
encryptionKey?: kms.IEncryptionKey;
```
```ts
/**
* External KMS key to use for bucket encryption.
*
* @default undefined
* ❌ DO NOT - that the value is 'undefined' by default is implied. However,
* what will the *behavior* be if the value is left out?
*/
encryptionKey?: kms.IEncryptionKey;
```
```ts
/**
* Minimum capacity of the AutoScaling resource
*
* @default - no minimum capacity
* ❌ DO NOT - there most certainly is. It's probably 0 or 1.
*
* // OR
* @default - the minimum capacity is the default minimum capacity
* ❌ DO NOT - this is circular and useless to the reader.
* Describe what will actually happen.
*/
minCapacity?: number;
```
#### Flat
Do not introduce artificial nesting for props. It hinders discoverability and
makes it cumbersome to use in some languages (like Java) [_awslint:props-flat_].
You can use a shared prefix for related properties to make them appear next to
each other in documentation and code completion:
For example, instead of:
```ts
new Bucket(this, 'MyBucket', {
bucketWebSiteConfiguration: {
errorDocument: '404.html',
indexDocument: 'index.html',
}
});
```
Prefer:
```ts
new Bucket(this, 'MyBucket', {
websiteErrorDocument: '404.html',
websiteIndexDocument: 'index.html'
});
```
#### Concise
Property names should be short and concise as possible and take into
consideration the ample context in which the property is used. Being concise
doesn't mean inventing new semantics. It just means that you can remove
redundant context from the property names.
Being concise doesn't mean you should invent new service semantics (see next
item). It just means that you can remove redundant context from the property
names. For example, there is no need to repeat the resource type, the property
type or indicate that this is a "configuration".
For example, prefer “readCapacity” versus “readCapacityUnits”.
#### Naming
We prefer the terminology used by the official AWS service documentation over
new terminology, even if you think it's not ideal. It helps users diagnose
issues and map the mental model of the construct to the service APIs,
documentation and examples. For example, don't be tempted to change SQS's
**dataKeyReusePeriod** with **keyRotation** because it will be hard for people
to diagnose problems. They won't be able to just search for “sqs dataKeyReuse”
and find topics on it.
> We can relax this guideline when this is about generic terms (like
`httpStatus` instead of `statusCode`). The important semantics to preserve are
for *service features*: I wouldn't want to rename "lambda layers" to "lambda
dependencies" just because it makes more sense because then users won't be
able to bind these terms to the service documentation.
Indicate units of measurement in property names that don't use a strong
type. Use “milli”, “sec”, “min”, “hr”, “Bytes”, “KiB”, “MiB”, “GiB” (KiB=1024
bytes, while KB=1000 bytes).
#### Property Documentation
Every prop must have detailed documentation. It is recommended to **copy** from
the official AWS documentation in English if possible so that language and style
will match the service.
#### Enums
When relevant, use enums to represent multiple choices.
```ts
export enum MyEnum {
OPTION1 = 'op21',
OPTION2 = 'opt2',
}
```
A common pattern in AWS is to allow users to select from a predefined set of
common options, but also allow the user to provide their own customized values.
A pattern for an "Enum-like Class" should be used in such cases:
```ts
export interface MyProps {
readonly option: MyOption;
}
export class MyOption {
public static COMMON_OPTION_1 = new MyOption('common.option-1');
public static COMMON_OPTION_2 = new MyOption('common.option-2');
public constructor(public readonly customValue: string) { }
}
```
Then usage would be:
```ts
new BoomBoom(this, 'Boom', {
option: MyOption.COMMON_OPTION_1
});
```
Suggestion for alternative syntax for custom options? Motivation: if we make
everything go through static factories, it will look more regular (I'm fine not
pursuing this, just popped into my head):
```ts
export class MyOption {
public static COMMON_OPTION_1 = new MyOption('common.option-1');
public static COMMON_OPTION_2 = new MyOption('common.option-2');
public static custom(value: string) {
return new MyOption(value);
}
// 'protected' iso. 'private' so that someone that really wants to can still
// do subclassing. But maybe might as well be private.
protected constructor(public readonly value: string) { }
}
// Usage
new BoomBoom(this, 'Boom', {
option: MyOption.COMMON_OPTION_1
});
new BoomBoom(this, 'Boom', {
option: MyOption.custom('my-value')
});
```
#### Unions
Do not use TypeScript union types in construct APIs (`string | number`) since
many of the target languages supported by the CDK cannot strongly-model such
types _[awslint:props-no-unions]_.
Instead, use a class with static methods:
```ts
new lambda.Function(this, 'MyFunction', {
code: lambda.Code.asset('/asset/path'), // or
code: lambda.Code.bucket(myBucket, 'bundle.zip'), // or
code: lambda.Code.inline('code')
// etc
})
```
### Attributes
Every AWS resource has a set of "physical" runtime attributes such as ARN,
physical names, URLs, etc. These attributes are commonly late-bound, which means
they can only be resolved during deployment, when AWS CloudFormation actually
provisions the resource.
AWS constructs must expose all resource attributes defined in the underlying
CloudFormation resource as readonly properties of the class
_[awslint:resource-attribute]_.
All properties that represent resource attributes must include the JSDoc tag
**@attribute** _[awslint:attribute-tag]_.
All attribute names must begin with the type name as a prefix
(e.g. ***bucket*Arn** instead of just **arn**) _[awslint:attribute-name]_. This
implies that if a property begins with the type name, it must have an
**@attribute** tag.
All resource attributes must be represented as readonly properties of the
resource interface _[awslint:attribute-readonly]_.
Resource attributes should use a type that corresponds to the resolved AWS
CloudFormation type (e.g. **string**, **string[]**) _[awslint:attribute-type]_.
> Resource attributes almost always represent string values (URL, ARN,
name). Sometimes they might also represent a list of strings. Since attribute
values can either be late-bound ("a promise to a string") or concrete ("a
string"), the AWS CDK has a mechanism called "tokens" which allows codifying
late-bound values into strings or string arrays. This approach was chosen in
order to dramatically simplify the type-system and ergonomics of CDK code. As
long as users treat these attributes as opaque values (e.g. not try to parse
them or manipulate them), they can be used interchangeably.
If needed, you can query whether an object includes unresolved tokens by using
the **Token.unresolved(x)** method.
To ensure users are aware that the value returned by attribute properties should
be treated as an opaque token, the JSDoc “@returns” annotation should begin with
“**@returns a $token representing the xxxxx**”
[_awslint:attribute-doc-returns-token_].
### Configuration
When an app defines a construct or resource, it specifies its provisioning
configuration upon initialization. For example, when an SQS queue is defined,
its visibility timeout can be configured.
Naturally, when constructs are imported (unowned), the importing app does not
have control over its configuration (e.g. you cannot change the visibility
timeout of an SQS queue that you didn't create). Therefore, construct interfaces
cannot include methods that require access/mutation of configuration.
One of the problems with configuration mutation is that there could be a race
condition between two parts of the app, trying to set contradicting values.
There are a number use cases where you'd want to provide APIs which expose or
mutate the construct's configuration. For example,
**lambda.Function.addEnvironment** is a useful method that adds an environment
variable to the function's runtime environment, and used occasionally to inject
dependencies.
> Note that there are APIs that look like they mutate the construct, but in fact
they are **factories** (i.e. they define resources on the user's stack). Those
APIs _should_ be exposed on the construct interface and not on the construct
class.
To help avoid the common mistake of exposing non-configuration APIs on the
construct class (versus the construct interface), we require that configuration
APIs (methods/properties) defined on the construct class will be annotated with
the **@config** jsdoc tag [_awslint:config-explicit_].
```ts
interface IFoo extends IConstruct {
bar(): void;
}
class Foo extends Construct implements IFoo {
public bar() { }
@config
public goo() { }
public mutateMe() { } // ERROR! missing "@config" or missing on IFoo
}
```
#### Prefer Additions
As a rule of thumb, “adding” items to configuration props of type unordered
array is normally considered safe as it will unlikely cause race conditions. If
the prop is a map (like in **addEnvironment**), write defensive code that will
throw if two values are assigned to the same key.
#### Dropped Mutations
Since all references across the library are done through a construct's
interface, methods that are only available on the concrete construct class will
not be accessible by code that uses the interface type. For example, code that
accepts a **lambda.IFunction** will not see the **addEnvironment** method.
In most cases, this is desirable, as it ensures that only the code the owns the
construct (instantiated it), will be able to mutate its configuration.
However, there are certain areas in the library, where, for the sake of
consistency and interoperability, we allow mutating methods to be exposed on the
interface. For example, **grant** methods are exposed on the construct interface
and not on the concrete class. In most cases, when you grant a permission on an
AWS resource, the *principal's* policy needs to be updated, which mutates the
consumer. However, there are certain cases where a *resource policy* must be
updated. However, if the resource is unowned, it doesn't make sense (or even
impossible) to update its policy (there is usually a 1:1 relationship between a
resource and a resource policy). In such cases, we decided that grant methods
will simply skip any changes to resource policies, but will attach a
**permission notice** to the app, which will be printed when the stack is
synthesized by the toolkit.
### Factories
In most AWS services, there are one or more resources which can be referred to as
“primary resources” (normally one), while other resources exposed by the service
can be considered “secondary resources”.
For example, the AWS Lambda service exposes the **Function** resource, which can
be considered the primary resource while **Layer**, **Permission**, **Alias**
are considered secondary. For API Gateway, the primary resource is **RestApi**,
and there are many secondary resources such as **Method**, **Resource**,
**Deployment**, **Authorizer**.
Secondary resources are normally associated with the primary resource (i.e. a
reference to the primary resource must be supplied upon initialization).
Users should be able to define secondary resources either by directly
instantiating their construct class (like any other construct), and passing in a
reference to the primary resource's construct interface *or* it is recommended
to implement convenience methods on the primary resource that will facilitate
defining secondary resources. This improves discoverability and ergonomics
_[awslint:factory-method]_.
For example, **lambda.Function.addLayer** can be used to add a layer to the
function, **apigw.RestApi.addResource** can be used to add to an API.
Methods for defining a secondary resource “Bar” associated with a primary
resource “Foo” should have the following signature:
```ts
export interface IFoo {
addBar(...): Bar;
}
```
Notice that:
* The method has an “add” prefix.
It implies that users are adding something to their stack.
* The method is implemented on the construct interface
(to allow adding secondary resources to unowned constructs).
* The method returns a “Bar” instance (owned).
In order to reuse the set of props used to configure the secondary resource,
define a base interface for **FooProps** called **FooOptions** to allow
secondary resource factory methods to reuse props
_[awslint:factory-method-options]_:
```ts
export interface LogStreamOptions {
logStreamName?: string;
}
export interface LogStreamProps extends LogStreamOptions {
logGroup: ILogGroup;
}
export interface ILogGroup {
addLogStream(id: string, options?: LogStreamOptions): LogStream;
}
```
### Imports
Construct classes should expose a set of static factory methods with a
“**from**” prefix that will allow users to import *unowned* constructs into
their app.
The signature of all “from” methods should adhere to the following rules
_[awslint:from-signature]_:
* First argument must be **scope** of type **Construct**.
* Second argument is a **string**. This string will be used to determine the
ID of the new construct. If the import method uses some value that is
promised to be unique within the stack scope (such as ARN, export name),
this value can be reused as the construct ID.
* Returns an object that implements the construct interface (**IFoo**).
#### “from” Methods
Resource constructs should export static “from” methods for importing unowned
resources given one or more of its physical attributes such as ARN, name, etc. All
constructs should have at least one `fromXxx` method _[awslint:from-method]_:
```ts
static fromFooArn(scope: Construct, id: string, bucketArn: string): IFoo;
static fromFooName(scope: Construct, id: string, bucketName: string): IFoo;
```
> Since AWS constructs usually export all resource attributes, the logic behind
the various “from\<Attribute\>” methods would normally need to convert one
attribute to another. For example, given a name, it would need to render the
ARN of the resource. Therefore, if **from\<Attribute\>** methods expect to be
able to parse their input, they must verify that the input (e.g. ARN, name)
doesn't have unresolved tokens (using **Token.unresolved**). Preferably, they
can use **Stack.parseArn** to achieve this purpose.
If a resource has an ARN attribute, it should implement at least a **fromFooArn**
import method [_awslint:from-arn_].
To implement **fromAttribute** methods, use the abstract base class construct as
follows:
<!-- markdownlint-disable MD013 -->
```ts
class Foo {
static fromArn(scope: Construct, fooArn: string): IFoo {
class _Foo extends FooBase {
public get fooArn() { return fooArn; }
public get fooName() { return this.node.stack.parseArn(fooArn).resourceName; }
}
return new _Foo(scope, fooArn);
}
}
```
<!-- markdownlint-enable MD013 -->
#### From-attributes
If a resource has more than a single attribute (“ARN” and “name” are usually
considered a single attribute since it's usually possible to convert one to the
other), then the resource should provide a static **fromAttributes** method to
allow users to explicitly supply values to all resource attributes when they
import an external (unowned) resource [_awslint:from-attributes_].
```ts
static fromFooAttributes(scope: Construct, id: string, attrs: FooAttributes): IFoo;
```
### Roles
If a CloudFormation resource has a **Role** property, it normally represents the
IAM role that will be used by the resource to perform operations on behalf of
the user.
Constructs that represent such resources should conform to the following
guidelines.
An optional prop called **role** of type **iam.IRole**should be exposed to allow
users to "bring their own role", and use either an owned or unowned role
_[awslint:role-config-prop]_.
```ts
interface FooProps {
/**
* The role to associate with foo.
* @default - a role will be automatically created
*/
role?: iam.IRole;
}
```
The construct interface should expose a **role** property, and extends
**iam.IGrantable** _[awslint:role-property]_:
```ts
interface IFoo extends iam.IGrantable {
/**
* The role associated with foo. If foo is imported, no role will be available.
*/
readonly role?: iam.IRole;
}
```
This property will be `undefined` if this is an unowned construct (e.g. was not
defined within the current app).
An **addToRolePolicy** method must be exposed on the construct interface to
allow adding statements to the role's policy _[awslint:role-add-to-policy]_:
```ts
interface IFoo {
addToRolePolicy(statement: iam.Statement): void;
}
```
If the construct is unowned, this method should no-op and issue a **permissions
notice** (TODO) to the user indicating that they should ensure that the role of
this resource should have the specified permission.
Implementing **IGrantable** brings an implementation burden of **grantPrincipal:
IPrincipal**. This property must be set to the **role** if available, or to a
new **iam.ImportedResourcePrincipal** if the resource is imported and the role
is not available.
### Resource Policies
Resource policies are IAM policies defined on the side of the resource (as
oppose to policies attached to the IAM principal). Different resources expose
different APIs for controlling their resource policy. For example, ECR
repositories have a **RepositoryPolicyText** prop, SQS queues offer a
**QueuePolicy** resource, etc.
Constructs that represents resources with a resource policy should encapsulate
the details of how resource policies are created behind a uniform API as
described in this section.
When a construct represents an AWS resource that supports a resource policy, it
should expose an optional prop that will allow initializing resource with a
specified policy _[awslint:resource-policy-prop]_:
```ts
resourcePolicy?: iam.PolicyStatement[]
```
Furthermore, the construct *interface* should include a method that allows users
to add statements to the resource policy