Organizing AWS CDK with Separated Lambda and Fargate Code in a Monorepo

ohanhaliuk

Oleksandr Hanhaliuk

Posted on October 19, 2024

Organizing AWS CDK with Separated Lambda and Fargate Code in a Monorepo

AWS Cloud Development Kit (CDK) offers a great solution for packaging Lambda functions with NodeJsFunction, which automatically bundles the required packages for your Lambda handler during deployment.

However, a common practice is to place your Lambda handler code in the same repository as your CDK infrastructure code, often sharing the same package.json file. It might not be ideal for organizing large projects, especially when dealing with multiple resources like Lambdas and Fargate containers.

In this article, we’ll explore an alternative approach: separating your business logic (Lambda handler) and infrastructure code (CDK) into different folders, mimicking a monorepo structure.

Step 1: Using Business Code in Separate Folders

Let’s start by separating your Lambda function’s business logic from the CDK infrastructure code. We’ll create two distinct folders: /cdk for CDK code and /lambda for your Lambda handler's code.

1.1 Folder Structure

/cdk
  └── cdk-stack.ts
/lambda
  └── src
      └── handlers
          └── index.ts
  └── package.json
  └── tsconfig.json
Enter fullscreen mode Exit fullscreen mode

1.2 CDK Code for Lambda Deployment

In your CDK stack, define the Lambda using NodejsFunction from the aws-cdk-lib/aws-lambda-nodejs module. In this setup, we'll define the entry point to the Lambda, its handler, and where to find the necessary dependencies like the package-lock.json and tsconfig.json files.

new lambdaNode.NodejsFunction(stack, 'example-service-lambda', {
  entry: path.resolve(__dirname, '../lambda/src/handlers/index.ts'), // path to your Lambda handler
  handler: 'handler', // the handler function
  depsLockFilePath: path.resolve(__dirname, '../lambda/package-lock.json'), // package-lock file
  projectRoot: path.resolve(__dirname, '../lambda'), // set project root to the Lambda folder
  bundling: {
    tsconfig: path.resolve(__dirname, '../lambda/tsconfig.json'), // tsconfig file for TypeScript support
  },
});
Enter fullscreen mode Exit fullscreen mode

Now, your business logic is separated from your CDK code, making it easier to manage and scale as needed.

Step 2: Adding Fargate Support

What if you want to deploy more than just Lambda functions, such as Fargate tasks? We can extend this monorepo structure to include Fargate services as well. Let’s create a new folder called /fargate for storing the Fargate-related Dockerfiles and configuration.

2.1 Folder Structure

/cdk
  └── cdk-stack.ts
/lambda
  └── src
      └── handlers
          └── index.ts
  └── package.json
  └── tsconfig.json
/fargate
  └── Dockerfile
  └── package.json
  └── tsconfig.json
Enter fullscreen mode Exit fullscreen mode

2.2 CDK Code for Fargate Task Definition

In your CDK stack, define a Fargate task by pointing it to the Dockerfile inside the /fargate folder.

taskDefinition.addContainer('containerId', {
  image: ContainerImage.fromAsset('../fargate', { file: 'Dockerfile' }),
  portMappings: [
    {
      containerPort: 80,
      hostPort: 80,
    },
  ],
});
Enter fullscreen mode Exit fullscreen mode

Now you have a Fargate service defined in a separate folder, just like the Lambda service. This structure keeps your infrastructure organized as you add more services.

Step 3: Using Shared Code Between Resources

In many cases, you may have shared logic that you want to use across multiple resources, such as both Lambda functions and Fargate containers. We can organize this shared code into a /shared folder and set up proper imports to access this code in your Lambda or Fargate services.

3.1 Folder Structure

/cdk
  └── cdk-stack.ts
/lambda
  └── src
      └── handlers
          └── index.ts
  └── package.json
  └── tsconfig.json
/fargate
  └── Dockerfile
  └── package.json
  └── tsconfig.json
/shared
  └── utils.ts
  └── package.json
  └── tsconfig.json
Enter fullscreen mode Exit fullscreen mode

3.2 Configuring CDK to Use Shared Code

To access the shared code from your Lambda or Fargate service, modify your /cdk/tsconfig.json to define the path to the shared folder:Copy code

{
  "compilerOptions": {
    "paths": {
      "shared/*": ["../shared/*"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, you can import shared utilities into your Lambda code like this:

import { util } from 'shared/utils';
Enter fullscreen mode Exit fullscreen mode

3.3 Adding Shared Code to the Lambda Bundle

In order to include the shared code in the Lambda bundle, update the NodejsFunction definition to handle the bundling process. We’ll use the volumes and commandHooks options to copy the shared folder into the Lambda container before bundling and clean it up afterward.

new lambdaNode.NodejsFunction(stack, 'example-service-lambda', {
  entry: path.resolve(__dirname, '../lambda/src/handlers/index.ts'),
  handler: 'handler',
  depsLockFilePath: path.resolve(__dirname, '../lambda/package-lock.json'),
  projectRoot: path.resolve(__dirname, '../lambda'),
  bundling: {
    volumes: [
      {
        // Mount the shared folder to the Lambda bundling container
        hostPath: path.resolve(__dirname, '../../../shared'),
        containerPath: '/mnt/shared',
      },
    ],
    commandHooks: {
      beforeBundling(inputDir: string): string[] {
        // Copy the shared folder into the Lambda bundling directory
        return [`cp -r /mnt/shared ${inputDir}/shared`];
      },
      afterBundling(inputDir: string): string[] {
        // Clean up the shared folder after bundling
        return [`rm -rf ${inputDir}/shared`];
      },
    },
    tsconfig: path.resolve(__dirname, '../lambda/tsconfig.json'),
  },
});
Enter fullscreen mode Exit fullscreen mode

This ensures that the shared code is included in the Lambda bundle and available at runtime.

Conclusion

By organizing your Lambda, Fargate, and shared code in a monorepo structure, you gain greater flexibility in managing and scaling your AWS infrastructure. Separating your business logic from your CDK code not only keeps your repository clean but also enables easier testing, faster deployments, and better long-term maintainability.

💖 💪 🙅 🚩
ohanhaliuk
Oleksandr Hanhaliuk

Posted on October 19, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related