top of page
Search

Simplifying AWS Lambda Development with a Stateless Stack Pattern

  • Writer: Mark Kendall
    Mark Kendall
  • Sep 10
  • 3 min read

Simplifying AWS Lambda Development with a Stateless Stack Pattern

Introduction


When teams scale out with dozens—or even hundreds—of AWS Lambdas, a recurring pain emerges:

• Developers must manage not just their handler code, but also the AWS CDK stack definitions.

• Each Lambda starts looking different, with drift in runtime versions, IAM policies, observability, and tagging.


A stateless stack pattern solves this. Developers write only the Lambda business code, while a shared “stateless” CDK stack wires everything together consistently.


This article explains why this works, how to structure it, and provides code you can use today.



Why Use a Stateless Stack?

• Focus on business logic: Devs build just the handler. No CDK boilerplate.

• Centralized governance: IAM roles, alarms, log retention, tags, and VPC config applied once in the infra repo.

• Consistency at scale: Every Lambda gets uniform defaults (memory, timeout, tracing, Powertools).

• Easier onboarding: New function = new folder + one manifest entry.

• Future-proofing: Runtime upgrades, observability, or org-wide policy changes happen in one place.


Trade-off: Infra changes (like a new trigger or new permissions) require updates to the shared stack. But that’s by design—it enforces review and safety.



Repository Structure


/services

  /orders

    /lambdas

      /create-order

        handler.ts

        package.json

      /cancel-order

        handler.ts

    /infra

      functions.manifest.ts

      cdk-app.ts

      stacks/

        stateless-functions.stack.ts




Lambda Developer Experience


Example Handler


// services/orders/lambdas/create-order/handler.ts

import { APIGatewayProxyHandlerV2 } from "aws-lambda";


export const handler: APIGatewayProxyHandlerV2 = async (event) => {

  const body = event.body ? JSON.parse(event.body) : {};

  

  // Use shared environment variables

  const table = process.env.TABLE_NAME;


  // Business logic only

  return {

    statusCode: 201,

    body: JSON.stringify({ id: "abc123", ...body })

  };

};


That’s it. The developer doesn’t touch CDK.



Defining Lambdas in a Manifest


// services/orders/infra/functions.manifest.ts

export type LambdaDef = {

  name: string;

  sourcePath: string;

  handler?: string;

  http?: { method: "GET"|"POST", path: string };

  memoryMb?: number;

  timeoutSec?: number;

  environment?: Record<string,string>;

};


export const lambdas: LambdaDef[] = [

  {

    name: "orders-create",

    sourcePath: "../../lambdas/create-order",

    http: { method: "POST", path: "/orders" },

    environment: { TABLE_NAME: "orders" },

    timeoutSec: 10

  },

  {

    name: "orders-cancel",

    sourcePath: "../../lambdas/cancel-order",

    http: { method: "POST", path: "/orders/{id}/cancel" },

    timeoutSec: 6

  }

];


Each entry is declarative—just enough metadata for the infra to stitch it together.



The Stateless CDK Stack


// services/orders/infra/stacks/stateless-functions.stack.ts

import { Stack, StackProps, Duration } from "aws-cdk-lib";

import { Construct } from "constructs";

import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";

import { Runtime, Tracing } from "aws-cdk-lib/aws-lambda";

import * as apigw from "aws-cdk-lib/aws-apigateway";

import * as path from "path";

import { lambdas } from "../functions.manifest";


export class StatelessFunctionsStack extends Stack {

  constructor(scope: Construct, id: string, props?: StackProps) {

    super(scope, id, props);


    const api = new apigw.RestApi(this, "OrdersApi", {

      deployOptions: { stageName: "dev" }

    });


    for (const def of lambdas) {

      const fn = new NodejsFunction(this, def.name, {

        entry: path.join(__dirname, def.sourcePath, "handler.ts"),

        handler: def.handler ?? "handler",

        runtime: Runtime.NODEJS_20_X,

        memorySize: def.memoryMb ?? 512,

        timeout: Duration.seconds(def.timeoutSec ?? 8),

        tracing: Tracing.ACTIVE,

        environment: def.environment ?? {}

      });


      if (def.http) {

        const res = api.root.resourceForPath(def.http.path);

        res.addMethod(def.http.method, new apigw.LambdaIntegration(fn));

      }

    }

  }

}




How

 
 
 

Recent Posts

See All

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
Post: Blog2_Post

Subscribe Form

Thanks for submitting!

©2020 by LearnTeachMaster DevOps. Proudly created with Wix.com

bottom of page