abc-ai/profiles/cdk-ts.md
Tiara Rodney 41481636d8
init
2026-02-05 01:51:29 +01:00

9.8 KiB
Raw Blame History

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:

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

ABC-PROFILE-CDKTSR3 (SHOULD)

Each Resource Group SHOULD reside inside its parent Logical Unit:

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

export interface InputContract {}
export interface OutputContract {}

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

Application Stack Base

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

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

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

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

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

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

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,
    };
  }
}