Extract Diagrams from AWS CDK Stacks

During last years i tried to find a simple way to assure the runtime architecture and to visualise the running application design.

My way of working

Principally in all companies i worked we try to solve a business problem using technical decisions, we settle around a table or virtually listening and discussing about the requirements and finally define a design proposal.

Design decision are mostly based on requirements but as well considering the edge cases, race conditions and best practices. putting all these together, the final solution design will be defined.

Why this solution design is not sufficient ?

Often when the time constraints become important and staffs dont have time to communicate or in some cases a urgent change is required is minutes or hours the teams evolve their designs to solve the problem but forget to modify the base approved design. in best case the changes are an improvement and cab be forgotten. As source versioning and source controlling systems are on top of their power these days every release of Infrastructure or software code relies on these tools so the most trustable source of reality to visualize the running solution will be the Infra/App as Code.

AWS CDK Power

AWS CDK is by design Object Oriented and by this design it will be logic and feasible to extract objects and put them all together and drawing the lines between those boxes.

Lets code out application

This application is a Simple Api using AWS Serverless Services to do some CRUD operations and send final notifications via a message broker to form an event driven design.

The Design will be as bellow using Api Gateway , Lambda Function , Dynamodb Table and SNS Topic.

We use aws CDK stack as bellow

export class ApiStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const api = new apigateway.RestApi(this, "ItemApi");

    const itemFunctionRole = new Role(this, 'ItemFunctionRole', {
      assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
    });

    const addItem = new NodejsFunction(this, "AddItem", {
      role: itemFunctionRole,
      entry: resolve(`src/handlers/add-item/index.ts`),
      architecture: lambda.Architecture.ARM_64,
      runtime: lambda.Runtime.NODEJS_20_X,
      timeout: cdk.Duration.seconds(10),
      handler: 'handler',
      bundling: {
        minify: true,
        externalModules: [ '@aws-sdk/client-dynamodb' ]
      },
    });

    const deleteItem = new NodejsFunction(this, "DeleteItem", {
      role: itemFunctionRole,
      entry: resolve(`src/handlers/delete-item/index.ts`),
      architecture: lambda.Architecture.ARM_64,
      runtime: lambda.Runtime.NODEJS_20_X,
      timeout: cdk.Duration.seconds(10),
      handler: 'handler',
      bundling: {
        minify: true,
        externalModules: [ '@aws-sdk/client-dynamodb' ]
      },
    });

    const addlambdaProxyIntegration = new apigateway.LambdaIntegration(addItem);
    const addlambdaApiResource = api.root.addResource('add');
    addlambdaApiResource.addMethod(
        'POST', 
        addlambdaProxyIntegration);   

    const deletelambdaProxyIntegration = new apigateway.LambdaIntegration(deleteItem);
    const deletelambdaApiResource = api.root.addResource('delete');
    deletelambdaApiResource.addMethod(
        'DELETE', 
        deletelambdaProxyIntegration);  

    const transformFunctionRole = new Role(this, 'TransformFunctionRole', {
      assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
    });

    const transformItemEvent = new NodejsFunction(this, "TransformItemEvent", {
      role: transformFunctionRole,
      entry: resolve(`src/handlers/transform-event/index.ts`),
      architecture: lambda.Architecture.ARM_64,
      runtime: lambda.Runtime.NODEJS_20_X,
      timeout: cdk.Duration.seconds(10),
      handler: 'handler',
      bundling: {
        minify: true,
        externalModules: [ '@aws-sdk/client-sns' ]
      },
    });

    const table = new dynamodb.Table(this, "ItemTable", {
      partitionKey: { name: "PK", type: dynamodb.AttributeType.STRING },
      stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES,
    });

    table.grantReadWriteData(itemFunctionRole);
    table.grantStreamRead(transformFunctionRole);

    new Topic(this, 'ItemTopic', {
      topicName: 'ItemTopic'
    }).grantPublish(transformFunctionRole);

  }
}

Using CDK dia

CDK dia is an open source library that is useful for our usecase. first install the cdk dia in your project

npm install -D cdk-dia

To discover more about cdk-dia refer to the documentation

To use CDK-DIA we need to instalml also the graphViz https://graphviz.org

brew install graphviz

Generating the diagrams

To generate the diagram we need to folow the following commands

> cdk synth
> npx sck-dia

The above command generates a PNG export as below

if you prefer an html result run the following command

npx cdk-dia --rendering cytoscape-html

Conclusion

We need always reduce the time consuming tasks and gain the time to better provide the business value, one of this tasks are documentation and diagraming , so whenever possible to generate the diagrams and documentations from code withour wasting the time and often maintain an outdated documentation and application digram. trust the trustable parts of your solutions and use them to extract diagrams , documentation and analytics.