[SERVERLESS PATTERNS] Beyond Single-Purpose vs. Lambda-lith: The Benefits of Read-Write Separation (CQRS Prelude)

Published Date: July 4, 2026

I recently read a deep-dive analysis on the AWS Compute Blog by two Principal Solutions Architects regarding architectural patterns for Serverless Microservices. It addresses the exact pain points developers face when structuring AWS Lambda functions. Here is a summary of the technical insights and architectural tradeoffs.

When designing RESTful APIs with AWS Lambda and Amazon API Gateway, the classic question is: How granular should my Lambda functions be?

Developers typically find themselves caught between two extremes:

1. Single Responsibility Lambda (One Function per Endpoint)

  • Approach: Every HTTP route and method (GET /users, POST /users, DELETE /users/{id}) maps to a separate Lambda function.
  • Pros:
    • Isolated source code and very small deployment bundle size, which minimizes Cold Start times.
    • Strict application of the Principle of Least Privilege (e.g., the GET function only needs dynamodb:GetItem permissions, while the POST function requires dynamodb:PutItem).
    • Independent memory/timeout settings.
  • Cons: High Infrastructure-as-Code (IaC) management overhead. You can easily hit the CloudFormation 500-resource limit. Infrequently used endpoints (like DELETE) suffer from frequent cold starts.

2. The Lambda-lith (All-in-One Function)

  • Approach: All API routes for a service are packed into a single Lambda function, often using web framework adapters like aws-serverless-express or Spring Boot.
  • Pros: Centralized codebase, easy sharing of entities/DTOs, and reusable database connection pools. Warm start rate is close to 100%.

Comparison of Serverless Lambda Microservices Approaches

  • Cons: Bloated bundle size leading to longer cold starts, over-permissioned IAM roles, and shared resource limits.

The Middle Ground: Read-Write Separation (CQRS Prelude)

Read-Write Separation (CQRS Prelude) Architecture

Instead of choosing between these extremes, a highly effective pattern is separating read and write workloads into two distinct Lambda functions:

  • Read Lambda: Handles all queries (GET /items, GET /items/{id}). It can be optimized for high performance, memory-efficient data retrieval, and caching.
  • Write Lambda: Handles all commands (POST, PUT, DELETE). It can be configured with higher timeouts, database transaction support, and specialized security permissions.

This approach balances bundle sizes, simplifies IAM security configurations, reduces cold starts on read endpoints, and makes local database connection pooling more manageable.