This commit is contained in:
Tiara Rodney 2026-02-05 01:51:29 +01:00
commit 41481636d8
No known key found for this signature in database
GPG key ID: 5CD8EC1D46106723
12 changed files with 2126 additions and 0 deletions

378
profiles/cdk-ts.md Normal file
View file

@ -0,0 +1,378 @@
# CDK TypeScript Profile
## Concept → CDK Mapping
| ABC Concept | Meaning | CDK Mapping |
|---------------|-------------------------|-----------------------------------------|
| ABCC0 | Construct | `Construct` subclass |
| ABCC1 | Application Stack | `Stack` subclass |
| ABCC2 | Logical Unit (LU) | `Construct` subclass with LU semantics |
| ABCC3 | Resource Group (RG) | `Construct` subclass with RG semantics |
| ABCC4 | Input Contract | TypeScript interface (`InputContract`) |
| ABCC5 | Output Contract | TypeScript interface (`OutputContract`) |
| ABCC6 | Instantiation Interface | Constructor (`scope`, `id`, `inputs`) |
| ABCC7 | Capturing Down | Passing inputs to children |
| ABCC8 | Bubbling Up | Exposing outputs upward |
*ABC* itself remains namingagnostic; this profile defines CDKidiomatic
conventions.
## Rules
These are profilespecific rules for CDK-Typescript users.
### ABC-PROFILE-CDKTSR1 (SHOULD)
The directory structure SHOULD reflect the ABC hierarchy:
Application Stack → Logical Units → Resource Groups.
### ABC-PROFILE-CDKTSR2 (SHOULD)
Each Logical Unit SHOULD reside in its own toplevel directory:
```default
src/
app-stack.ts
data/
logic/
presentation/
```
### ABC-PROFILE-CDKTSR3 (SHOULD)
Each Resource Group SHOULD reside inside its parent Logical Unit:
```default
src/data/storage/
src/data/database/
```
### ABC-PROFILE-CDKTSR4 (SHOULD)
Each construct directory SHOULD contain an index.ts exporting the construct.
### ABC-PROFILE-CDKTSR5 (SHOULD)
File and directory names SHOULD follow CDK naming conventions.
### ABC-PROFILE-CDKTSR6 (MAY)
Modules MAY split into multiple files if needed, as long as they remain in the
same directory.
### ABC-PROFILE-CDKTSR7 (SHOULD)
Each construct module SHOULD define its own `InputContract` and
`OutputContract`.
### ABC-PROFILE-CDKTSR8 (SHOULD)
Within a module, contracts SHOULD be named simply `InputContract` and
`OutputContract`.
### ABC-PROFILE-CDKTSR9 (SHOULD)
When importing contracts from another module, they SHOULD be aliased:
### ABC-PROFILE-CDKTSR10 (MUST)
Constructs MUST be instantiated using a single InputContract object.
Also see ABCR30/32.
### ABC-PROFILE-CDKTSR11 (MUST)
All crossconstruct wiring MUST occur in the parent construct.
Also see ABCR44.
### ABC-PROFILE-CDKTSR12 (SHOULD)
Imported contracts SHOULD be aliased to descriptive names.
### ABC-PROFILE-CDKTSR13 (SHOULD)
Contracts SHOULD be colocated with their construct modules.
### ABC-PROFILE-CDKTSR14 (SHOULD)
Generated code SHOULD mirror the ABC hierarchy in structure and composition.
### ABC-PROFILE-CDKTSR15 (SHOULD)
Construct internals SHOULD NOT be accessed except through outputs.
### ABC-PROFILE-CDKTSR16 (SHOULD)
Constructs SHOULD be generated in ABC order: Resource Groups → Logical Units →
Application Stack.
## Base ABC Classes for Typescript-CDK
### Core Types
```javascript
export interface InputContract {}
export interface OutputContract {}
export interface HasOutputs<TOutputs extends OutputContract> {
readonly outputs: TOutputs;
}
```
### Application Stack Base
```javascript
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { OutputContract } from './core-types';
export abstract class ABCApplicationStack<
TOutputs extends OutputContract = OutputContract
> extends Stack {
public abstract readonly outputs: TOutputs;
protected constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
}
}
```
### Logical Unit Base
```javascript
import { Construct } from 'constructs';
import { InputContract, OutputContract, HasOutputs } from './core-types';
export abstract class ABCLogicalUnit<
TInputs extends InputContract,
TOutputs extends OutputContract
> extends Construct implements HasOutputs<TOutputs> {
public abstract readonly outputs: TOutputs;
protected readonly inputs: TInputs;
protected constructor(scope: Construct, id: string, inputs: TInputs) {
super(scope, id);
this.inputs = Object.freeze({ ...inputs }) as TInputs;
}
}
```
### Resource Group Base
```javascript
import { Construct } from 'constructs';
import { InputContract, OutputContract, HasOutputs } from './core-types';
export abstract class ABCResourceGroup<
TInputs extends InputContract,
TOutputs extends OutputContract
> extends Construct implements HasOutputs<TOutputs> {
public abstract readonly outputs: TOutputs;
protected readonly inputs: TInputs;
protected constructor(scope: Construct, id: string, inputs: TInputs) {
super(scope, id);
this.inputs = Object.freeze({ ...inputs }) as TInputs;
}
}
```
## Logical Units & Resource Groups (Canonical Example)
Below is the full 3tier example implemented in CDK.
### Data Logical Unit
```default
src/data/index.ts
src/data/storage/index.ts
src/data/database/index.ts
```
```javascript
import { Construct } from 'constructs';
import { ABCLogicalUnit } from '../abc/logical-unit';
import { StorageGroup } from './storage';
import { DatabaseGroup } from './database';
export interface InputContract {
environment: string;
region: string;
storageClass: string;
dbEngine: string;
dbInstanceSize: string;
}
export interface OutputContract {
storageBucketName: string;
databaseEndpoint: string;
}
export class DataUnit extends ABCLogicalUnit<InputContract, OutputContract> {
public readonly outputs: OutputContract;
constructor(scope: Construct, id: string, inputs: InputContract) {
super(scope, id, inputs);
const storage = new StorageGroup(this, 'Storage', {
environment: inputs.environment,
region: inputs.region,
storageClass: inputs.storageClass,
});
const database = new DatabaseGroup(this, 'Database', {
environment: inputs.environment,
dbEngine: inputs.dbEngine,
dbInstanceSize: inputs.dbInstanceSize,
});
this.outputs = {
storageBucketName: storage.outputs.storageBucketName,
databaseEndpoint: database.outputs.databaseEndpoint,
};
}
}
```
#### Storage Resource Group
```default
import { Construct } from 'constructs';
import { ABCResourceGroup } from '../../abc/resource-group';
export interface InputContract {
environment: string;
region: string;
storageClass: string;
}
export interface OutputContract {
storageBucketName: string;
}
export class StorageGroup extends ABCResourceGroup<InputContract, OutputContract> {
public readonly outputs: OutputContract;
constructor(scope: Construct, id: string, inputs: InputContract) {
super(scope, id, inputs);
this.outputs = {
storageBucketName: 'bucket-name-placeholder',
};
}
}
```
#### Database Resource Group
```default
import { Construct } from 'constructs';
import { ABCResourceGroup } from '../../abc/resource-group';
export interface InputContract {
environment: string;
dbEngine: string;
dbInstanceSize: string;
}
export interface OutputContract {
databaseEndpoint: string;
}
export class DatabaseGroup extends ABCResourceGroup<InputContract, OutputContract> {
public readonly outputs: OutputContract;
constructor(scope: Construct, id: string, inputs: InputContract) {
super(scope, id, inputs);
this.outputs = {
databaseEndpoint: 'db-endpoint-placeholder',
};
}
}
```
### Logic Logical Unit
```default
src/logic/index.ts
src/logic/compute/index.ts
src/logic/messaging/index.ts
```
Compute Resource Group and Messaging Resource Group follow the same pattern.
### Presentation Logical Unit
```default
src/presentation/index.ts
src/presentation/webapp/index.ts
src/presentation/cdn/index.ts
```
WebApp Resource Group and CDN Resource Group follow the same pattern.
## Application Stack
```javascript
import { Construct } from 'constructs';
import { ABCApplicationStack } from './abc/application-stack';
import { DataUnit } from './data';
import type { InputContract as DataUnitInput } from './data';
import { LogicUnit } from './logic';
import type { InputContract as LogicUnitInput } from './logic';
import { PresentationUnit } from './presentation';
import type { InputContract as PresentationUnitInput } from './presentation';
export interface InputContract {
environment: string;
region: string;
globalTags?: Record<string, string>;
}
export interface OutputContract {
frontendUrl: string;
apiEndpoint: string;
}
export class AppStack extends ABCApplicationStack<OutputContract> {
public readonly outputs: OutputContract;
constructor(scope: Construct, id: string, inputs: InputContract) {
super(scope, id);
const dataInputs: DataUnitInput = {
environment: inputs.environment,
region: inputs.region,
storageClass: 'STANDARD',
dbEngine: 'postgres',
dbInstanceSize: 'db.t3.micro',
};
const dataUnit = new DataUnit(this, 'DataUnit', dataInputs);
const logicInputs: LogicUnitInput = {
environment: inputs.environment,
region: inputs.region,
computeSize: 'small',
messageRetention: 1209600,
databaseEndpoint: dataUnit.outputs.databaseEndpoint,
};
const logicUnit = new LogicUnit(this, 'LogicUnit', logicInputs);
const presentationInputs: PresentationUnitInput = {
environment: inputs.environment,
region: inputs.region,
frontendAssetsBucket: dataUnit.outputs.storageBucketName,
apiEndpoint: logicUnit.outputs.apiEndpoint,
};
const presentationUnit = new PresentationUnit(this, 'PresentationUnit', presentationInputs);
this.outputs = {
frontendUrl: presentationUnit.outputs.frontendUrl,
apiEndpoint: logicUnit.outputs.apiEndpoint,
};
}
}
```