CDK TypeScript Profile

Concept → CDK Mapping

ABC Concept

Meaning

CDK Mapping

ABC‑C0

Construct

Construct subclass

ABC‑C1

Application Stack

Stack subclass

ABC‑C2

Logical Unit (LU)

Construct subclass with LU semantics

ABC‑C3

Resource Group (RG)

Construct subclass with RG semantics

ABC‑C4

Input Contract

TypeScript interface (InputContract)

ABC‑C5

Output Contract

TypeScript interface (OutputContract)

ABC‑C6

Instantiation Interface

Constructor (scope, id, inputs)

ABC‑C7

Capturing Down

Passing inputs to children

ABC‑C8

Bubbling Up

Exposing outputs upward

ABC itself remains naming‑agnostic; this profile defines CDK‑idiomatic conventions.

Rules

These are profile‑specific rules for CDK-Typescript users.

ABC-PROFILE-CDKTS‑R1 (SHOULD)

The directory structure SHOULD reflect the ABC hierarchy:

Application Stack → Logical Units → Resource Groups.

ABC-PROFILE-CDKTS‑R2 (SHOULD)

Each Logical Unit SHOULD reside in its own top‑level directory:

src/
  app-stack.ts
  data/
  logic/
  presentation/

ABC-PROFILE-CDKTS‑R3 (SHOULD)

Each Resource Group SHOULD reside inside its parent Logical Unit:

src/data/storage/
src/data/database/

ABC-PROFILE-CDKTS‑R4 (SHOULD)

Each construct directory SHOULD contain an index.ts exporting the construct.

ABC-PROFILE-CDKTS‑R5 (SHOULD)

File and directory names SHOULD follow CDK naming conventions.

ABC-PROFILE-CDKTS‑R6 (MAY)

Modules MAY split into multiple files if needed, as long as they remain in the same directory.

ABC-PROFILE-CDKTS‑R7 (SHOULD)

Each construct module SHOULD define its own InputContract and OutputContract.

ABC-PROFILE-CDKTS‑R8 (SHOULD)

Within a module, contracts SHOULD be named simply InputContract and OutputContract.

ABC-PROFILE-CDKTS‑R9 (SHOULD)

When importing contracts from another module, they SHOULD be aliased:

ABC-PROFILE-CDKTS‑R10 (MUST)

Constructs MUST be instantiated using a single InputContract object.

Also see ABC‑R30/32.

ABC-PROFILE-CDKTS‑R11 (MUST)

All cross‑construct wiring MUST occur in the parent construct.

Also see ABC‑R44.

ABC-PROFILE-CDKTS‑R12 (SHOULD)

Imported contracts SHOULD be aliased to descriptive names.

ABC-PROFILE-CDKTS‑R13 (SHOULD)

Contracts SHOULD be co‑located with their construct modules.

ABC-PROFILE-CDKTS‑R14 (SHOULD)

Generated code SHOULD mirror the ABC hierarchy in structure and composition.

ABC-PROFILE-CDKTS‑R15 (SHOULD)

Construct internals SHOULD NOT be accessed except through outputs.

ABC-PROFILE-CDKTS‑R16 (SHOULD)

Constructs SHOULD be generated in ABC order: Resource Groups → Logical Units → Application Stack.

Base ABC Classes for Typescript-CDK

Core Types

src/abc/core-types.ts
export interface InputContract {}
export interface OutputContract {}

export interface HasOutputs<TOutputs extends OutputContract> {
  readonly outputs: TOutputs;
}

Application Stack Base

src/abc/application-stack.ts
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

src/abc/logical-unit.ts
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

src/abc/resource-group.ts
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 3‑tier example implemented in CDK.

Data Logical Unit

src/data/index.ts
src/data/storage/index.ts
src/data/database/index.ts
src/data/index.ts
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

src/data/storage/index.ts
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

src/data/database/index.ts
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

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

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

src/app-stack.ts
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,
    };
  }
}