Serverless Architecture Patterns and Implementation Strategies
Serverless Architecture Patterns and Implementation Strategies
Overview
Serverless architecture has become the foundation of modern web application development in 2024. By reducing infrastructure management overhead, enabling automatic scaling, and providing "pay-as-you-use" cost structures, developers can focus on creating core value. This article provides a comprehensive guide to implementation strategies, cost optimization techniques, and monitoring patterns using the latest serverless technologies (AWS Lambda, Azure Functions, Cloudflare Workers, Vercel Functions, Deno Deploy, Bun).
Fundamentals of Serverless Architecture
What is Serverless?
Serverless computing is a cloud computing model where cloud providers manage server infrastructure, allowing developers to focus on code execution. Despite the name "serverless," servers still exist, but the management responsibility shifts to the cloud provider.
Key Characteristics
- Event-driven execution: Functions automatically execute in response to requests or events
- Automatic scaling: Automatically scales up or down based on traffic volume
- Pay-per-use: Billing based on actual usage (execution time and request count)
- Stateless: Each execution is independent and maintains no state
- Short-duration execution: Typically optimized for executions lasting seconds to minutes
Major Platform Comparison
AWS Lambda
Features:
- Most mature serverless platform
- Extensive integration with AWS services
- Wide runtime support (Node.js, Python, Java, .NET, Go, Ruby, Rust)
Pricing Structure (2024):
- Free tier: 1 million requests/month, 400,000 GB-seconds
- Request pricing: $0.20 / 1 million requests
- Execution time pricing: Based on memory allocation and execution time
- Memory: Configurable from 128MB to 10,240MB
Code Example - Basic Lambda Function:
// AWS Lambda Handler (Node.js)
export const handler = async (event, context) => {
try {
const { httpMethod, path, body } = event;
// Business logic
const result = await processRequest(httpMethod, path, body);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(result)
};
} catch (error) {
console.error('Lambda execution error:', error);
return {
statusCode: 500,
body: JSON.stringify({ error: 'Internal Server Error' })
};
}
};
async function processRequest(method, path, body) {
// Database connection
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: { rejectUnauthorized: false }
});
switch(method) {
case 'GET':
return await handleGet(pool, path);
case 'POST':
return await handlePost(pool, JSON.parse(body));
default:
throw new Error(`Unsupported method: ${method}`);
}
}
Azure Functions
Features:
- Strong integration with Microsoft ecosystem
- Multiple hosting plans (Consumption, Premium, App Service)
- Stateful workflows with Durable Functions
Pricing Structure:
- Free tier: 1 million requests/month
- Request pricing: $0.20 / 1 million requests (equivalent to Lambda)
- Execution time pricing: Per GB-second billing
Code Example - Azure Functions with Durable Functions:
// Azure Durable Functions (C#)
[FunctionName("OrderProcessingOrchestrator")]
public static async Task<string> RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
var orderId = context.GetInput<string>();
try
{
// Step 1: Check inventory
var inventoryResult = await context.CallActivityAsync<bool>(
"CheckInventory", orderId);
if (!inventoryResult)
{
return "Out of stock";
}
// Step 2: Process payment
var paymentResult = await context.CallActivityAsync<bool>(
"ProcessPayment", orderId);
if (!paymentResult)
{
return "Payment failed";
}
// Step 3: Arrange shipping
await context.CallActivityAsync("ArrangeShipping", orderId);
return "Order processing completed";
}
catch (Exception ex)
{
// Error handling and compensation
await context.CallActivityAsync("HandleError", ex.Message);
throw;
}
}
[FunctionName("CheckInventory")]
public static async Task<bool> CheckInventory(
[ActivityTrigger] string orderId,
ILogger log)
{
log.LogInformation($"Starting inventory check: {orderId}");
// Integration with inventory management system
using var client = new HttpClient();
var response = await client.GetAsync(
$"{Environment.GetEnvironmentVariable("INVENTORY_API")}/check/{orderId}");
return response.IsSuccessStatusCode;
}
Cloudflare Workers
Features:
- Ultra-low latency with edge computing
- Fast startup with V8 JavaScript engine (near-zero cold starts)
- CPU time-based billing (no charges for wait time)
Pricing Structure:
- Free tier: 100,000 requests/day
- Paid plan: $5/month base, includes 10 million requests/month
- Additional requests: $0.30 / 1 million requests
- Memory: Fixed at 128MB
Code Example - Cloudflare Workers with KV Storage:
// Cloudflare Workers with KV
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const cacheKey = `cache:${url.pathname}`;
// Get cache from KV storage
let cached = await env.CACHE_KV.get(cacheKey, 'json');
if (cached && Date.now() - cached.timestamp < 3600000) { // 1 hour cache
return new Response(JSON.stringify(cached.data), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=3600',
'X-Cache': 'HIT'
}
});
}
try {
// Fetch data from external API
const apiResponse = await fetch(`${env.API_BASE_URL}${url.pathname}`, {
headers: {
'Authorization': `Bearer ${env.API_TOKEN}`
}
});
if (!apiResponse.ok) {
throw new Error(`API error: ${apiResponse.status}`);
}
const data = await apiResponse.json();
// Cache result in KV (async, doesn't block response)
ctx.waitUntil(env.CACHE_KV.put(cacheKey, JSON.stringify({
data,
timestamp: Date.now()
})));
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=3600',
'X-Cache': 'MISS'
}
});
} catch (error) {
console.error('Worker error:', error);
// Fallback handling
if (cached) {
return new Response(JSON.stringify(cached.data), {
headers: {
'Content-Type': 'application/json',
'X-Cache': 'STALE'
}
});
}
return new Response(JSON.stringify({ error: 'Service unavailable' }), {
status: 503,
headers: { 'Content-Type': 'application/json' }
});
}
}
};
Vercel Functions
Features:
- Complete integration with Next.js
- Edge Runtime support
- Automatic API Routes optimization
Code Example - Next.js API Routes with after():
// pages/api/analytics.ts or app/api/analytics/route.ts
import { after } from 'next/server';
export async function POST(request: Request) {
const body = await request.json();
// Main response processing
const result = await processMainRequest(body);
// Async processing executed after response
after(async () => {
// Send analytics data (doesn't block response)
await sendAnalytics({
userId: body.userId,
action: body.action,
timestamp: new Date(),
metadata: result.metadata
});
// Activity logging
await logActivity({
type: 'api_call',
endpoint: '/api/analytics',
duration: Date.now() - startTime,
success: true
});
});
return Response.json(result);
}
async function sendAnalytics(data: AnalyticsData) {
try {
await fetch(process.env.ANALYTICS_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
} catch (error) {
console.error('Analytics sending failed:', error);
}
}
Emerging Runtimes
Deno Deploy
Features:
- TypeScript/JavaScript edge runtime
- Web API standard compliance
- NPM compatibility with secure defaults
Code Example:
// Deno Deploy Function
import { serve } from "https://deno.land/[email protected]/http/server.ts";
interface RequestData {
message: string;
timestamp: number;
}
serve(async (request: Request): Promise<Response> => {
const url = new URL(request.url);
// CORS support
if (request.method === "OPTIONS") {
return new Response(null, {
status: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
},
});
}
if (url.pathname === "/api/data" && request.method === "POST") {
try {
const data: RequestData = await request.json();
// Data storage using Deno KV
const kv = await Deno.openKv();
const key = ["messages", Date.now()];
await kv.set(key, data);
// Response
return Response.json({
success: true,
id: key[1]
}, {
headers: {
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json",
},
});
} catch (error) {
return Response.json({
error: "Invalid request"
}, {
status: 400,
headers: {
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json",
},
});
}
}
return new Response("Not Found", { status: 404 });
});
Bun Runtime
Features:
- High-performance JavaScript runtime implementation
- Built-in bundler and package manager
- Node.js compatibility
Code Example:
// Bun Server
import { serve } from "bun";
const server = serve({
port: process.env.PORT || 3000,
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/api/health") {
return new Response(JSON.stringify({
status: "healthy",
runtime: "bun",
timestamp: Date.now(),
memory: process.memoryUsage()
}), {
headers: { "Content-Type": "application/json" }
});
}
if (url.pathname === "/api/file" && request.method === "POST") {
const formData = await request.formData();
const file = formData.get("file") as File;
if (!file) {
return new Response("No file uploaded", { status: 400 });
}
// Bun's fast file processing
const buffer = await file.arrayBuffer();
const filename = `uploads/${Date.now()}-${file.name}`;
await Bun.write(filename, buffer);
return Response.json({
filename,
size: buffer.byteLength,
type: file.type
});
}
return new Response("Not Found", { status: 404 });
}
});
console.log(`Bun server running on port ${server.port}`);
Serverless Architecture Patterns
1. Microservices Pattern
Implement each function as an independent serverless function to achieve loosely coupled architecture.
Implementation Example - API Gateway + Lambda:
# serverless.yml (Serverless Framework)
service: microservices-api
provider:
name: aws
runtime: nodejs18.x
stage: ${opt:stage, 'dev'}
region: us-east-1
environment:
USERS_TABLE: ${self:service}-users-${self:provider.stage}
ORDERS_TABLE: ${self:service}-orders-${self:provider.stage}
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.environment.USERS_TABLE}
- arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.environment.ORDERS_TABLE}
functions:
# User service
getUserById:
handler: src/users/get.handler
events:
- http:
path: users/{id}
method: get
cors: true
createUser:
handler: src/users/create.handler
events:
- http:
path: users
method: post
cors: true
# Order service
getOrderById:
handler: src/orders/get.handler
events:
- http:
path: orders/{id}
method: get
cors: true
createOrder:
handler: src/orders/create.handler
events:
- http:
path: orders
method: post
cors: true
# Async processing
processPayment:
handler: src/payments/process.handler
events:
- sqs:
arn: arn:aws:sqs:${self:provider.region}:*:payment-queue
resources:
Resources:
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:provider.environment.USERS_TABLE}
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
OrdersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:provider.environment.ORDERS_TABLE}
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
GlobalSecondaryIndexes:
- IndexName: UserIdIndex
KeySchema:
- AttributeName: userId
KeyType: HASH
Projection:
ProjectionType: ALL
2. Event-Driven Pattern
Build reactive systems by triggering functions based on events.
Implementation Example - EventBridge + Lambda:
// src/events/orderCreated.js
const { EventBridgeClient, PutEventsCommand } = require("@aws-sdk/client-eventbridge");
const eventBridge = new EventBridgeClient({ region: process.env.AWS_REGION });
exports.handler = async (event) => {
console.log('Order created event:', JSON.stringify(event, null, 2));
try {
const order = JSON.parse(event.Records[0].body);
// Publish multiple events in parallel
const events = [
{
Source: 'order.service',
DetailType: 'Order Created',
Detail: JSON.stringify({
orderId: order.id,
userId: order.userId,
amount: order.total,
timestamp: new Date().toISOString()
})
},
{
Source: 'inventory.service',
DetailType: 'Inventory Update Required',
Detail: JSON.stringify({
orderId: order.id,
items: order.items,
action: 'reserve'
})
}
];
const command = new PutEventsCommand({
Entries: events
});
await eventBridge.send(command);
console.log('Events published successfully');
} catch (error) {
console.error('Event processing failed:', error);
throw error;
}
};
// src/events/inventoryUpdater.js
exports.handler = async (event) => {
console.log('Inventory update event:', JSON.stringify(event, null, 2));
const { orderId, items, action } = event.detail;
try {
for (const item of items) {
if (action === 'reserve') {
await reserveInventory(item.productId, item.quantity);
} else if (action === 'release') {
await releaseInventory(item.productId, item.quantity);
}
}
// Publish inventory updated event
await publishInventoryUpdatedEvent(orderId, items, action);
} catch (error) {
console.error('Inventory update failed:', error);
// Send to dead letter queue or retry processing
throw error;
}
};
async function reserveInventory(productId, quantity) {
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const { DynamoDBDocumentClient, UpdateCommand } = require("@aws-sdk/lib-dynamodb");
const client = new DynamoDBClient({ region: process.env.AWS_REGION });
const docClient = DynamoDBDocumentClient.from(client);
const command = new UpdateCommand({
TableName: process.env.INVENTORY_TABLE,
Key: { productId },
UpdateExpression: 'SET availableQuantity = availableQuantity - :qty, reservedQuantity = reservedQuantity + :qty',
ConditionExpression: 'availableQuantity >= :qty',
ExpressionAttributeValues: {
':qty': quantity
},
ReturnValues: 'UPDATED_NEW'
});
return await docClient.send(command);
}
3. CQRS (Command Query Responsibility Segregation) Pattern
Separate read and write operations, implementing optimized processing for each.
Implementation Example - Lambda + DynamoDB + ElasticSearch:
// src/commands/createProduct.js
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const { DynamoDBDocumentClient, PutCommand } = require("@aws-sdk/lib-dynamodb");
const { SQSClient, SendMessageCommand } = require("@aws-sdk/client-sqs");
const dynamoClient = new DynamoDBClient({ region: process.env.AWS_REGION });
const docClient = DynamoDBDocumentClient.from(dynamoClient);
const sqsClient = new SQSClient({ region: process.env.AWS_REGION });
exports.handler = async (event) => {
try {
const product = JSON.parse(event.body);
const productId = generateId();
const productData = {
...product,
id: productId,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
// Command side (write-optimized): Save to DynamoDB
const putCommand = new PutCommand({
TableName: process.env.PRODUCTS_TABLE,
Item: productData
});
await docClient.send(putCommand);
// Queue query side update for async execution
const message = new SendMessageCommand({
QueueUrl: process.env.PRODUCT_SYNC_QUEUE,
MessageBody: JSON.stringify({
action: 'CREATE',
product: productData
})
});
await sqsClient.send(message);
return {
statusCode: 201,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
id: productId,
message: 'Product created successfully'
})
};
} catch (error) {
console.error('Product creation failed:', error);
return {
statusCode: 500,
body: JSON.stringify({ error: 'Internal server error' })
};
}
};
// src/queries/searchProducts.js
const { Client } = require('@elastic/elasticsearch');
const esClient = new Client({
node: process.env.ELASTICSEARCH_ENDPOINT,
auth: {
username: process.env.ES_USERNAME,
password: process.env.ES_PASSWORD
}
});
exports.handler = async (event) => {
try {
const { query, category, priceRange, sortBy } = event.queryStringParameters || {};
const searchBody = {
query: {
bool: {
must: [],
filter: []
}
},
sort: [],
size: 20
};
// Text search
if (query) {
searchBody.query.bool.must.push({
multi_match: {
query,
fields: ['name^2', 'description', 'tags'],
type: 'best_fields',
fuzziness: 'AUTO'
}
});
}
// Category filter
if (category) {
searchBody.query.bool.filter.push({
term: { category }
});
}
// Price range filter
if (priceRange) {
const [min, max] = priceRange.split('-').map(Number);
searchBody.query.bool.filter.push({
range: {
price: { gte: min, lte: max }
}
});
}
// Sorting
if (sortBy === 'price_asc') {
searchBody.sort.push({ price: 'asc' });
} else if (sortBy === 'price_desc') {
searchBody.sort.push({ price: 'desc' });
} else {
searchBody.sort.push({ _score: 'desc' });
}
const response = await esClient.search({
index: 'products',
body: searchBody
});
const products = response.body.hits.hits.map(hit => ({
id: hit._id,
...hit._source,
score: hit._score
}));
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'public, max-age=300' // 5 minute cache
},
body: JSON.stringify({
products,
total: response.body.hits.total.value,
took: response.body.took
})
};
} catch (error) {
console.error('Product search failed:', error);
return {
statusCode: 500,
body: JSON.stringify({ error: 'Search failed' })
};
}
};
// src/sync/productSyncProcessor.js
const { Client } = require('@elastic/elasticsearch');
const esClient = new Client({
node: process.env.ELASTICSEARCH_ENDPOINT,
auth: {
username: process.env.ES_USERNAME,
password: process.env.ES_PASSWORD
}
});
exports.handler = async (event) => {
const promises = event.Records.map(async (record) => {
try {
const { action, product } = JSON.parse(record.body);
switch (action) {
case 'CREATE':
case 'UPDATE':
await esClient.index({
index: 'products',
id: product.id,
body: {
name: product.name,
description: product.description,
price: product.price,
category: product.category,
tags: product.tags || [],
createdAt: product.createdAt,
updatedAt: product.updatedAt
}
});
break;
case 'DELETE':
await esClient.delete({
index: 'products',
id: product.id
});
break;
}
console.log(`Product ${action} synced to Elasticsearch:`, product.id);
} catch (error) {
console.error('Sync failed for record:', record, error);
throw error; // Will be retried by SQS
}
});
await Promise.all(promises);
};
Cost Optimization Strategies
1. Platform-Specific Cost Analysis
Runtime and Memory Optimization
AWS Lambda Optimization Example:
// Memory and performance relationship measurement
const AWS = require('aws-sdk');
// Memory usage monitoring
function getMemoryUsage() {
const used = process.memoryUsage();
return {
rss: Math.round(used.rss / 1024 / 1024 * 100) / 100,
heapTotal: Math.round(used.heapTotal / 1024 / 1024 * 100) / 100,
heapUsed: Math.round(used.heapUsed / 1024 / 1024 * 100) / 100,
external: Math.round(used.external / 1024 / 1024 * 100) / 100
};
}
exports.handler = async (event, context) => {
const startTime = Date.now();
const startMemory = getMemoryUsage();
try {
// Execute heavy processing
const result = await processLargeDataset(event.data);
const endTime = Date.now();
const endMemory = getMemoryUsage();
// Send performance metrics to CloudWatch
await sendMetrics({
executionTime: endTime - startTime,
memoryUsed: endMemory.heapUsed,
allocatedMemory: parseInt(context.memoryLimitInMB),
requestSize: JSON.stringify(event).length
});
return {
statusCode: 200,
body: JSON.stringify(result)
};
} catch (error) {
console.error('Processing error:', error);
throw error;
}
};
async function sendMetrics(metrics) {
const cloudwatch = new AWS.CloudWatch();
const params = {
Namespace: 'Lambda/Performance',
MetricData: [
{
MetricName: 'ExecutionTime',
Value: metrics.executionTime,
Unit: 'Milliseconds'
},
{
MetricName: 'MemoryUtilization',
Value: (metrics.memoryUsed / metrics.allocatedMemory) * 100,
Unit: 'Percent'
}
]
};
await cloudwatch.putMetricData(params).promise();
}
Cost Monitoring and Alerts
Terraform Configuration Example:
# cost-monitoring.tf
resource "aws_cloudwatch_metric_alarm" "lambda_cost_alarm" {
alarm_name = "lambda-monthly-cost-alarm"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = "EstimatedCharges"
namespace = "AWS/Billing"
period = "86400" # 1 day
statistic = "Maximum"
threshold = "100" # $100
alarm_description = "This metric monitors lambda monthly costs"
alarm_actions = [aws_sns_topic.cost_alerts.arn]
dimensions = {
Currency = "USD"
ServiceName = "AWSLambda"
}
}
resource "aws_sns_topic" "cost_alerts" {
name = "lambda-cost-alerts"
}
resource "aws_sns_topic_subscription" "email_notification" {
topic_arn = aws_sns_topic.cost_alerts.arn
protocol = "email"
endpoint = "[email protected]"
}
# Lambda function reserved concurrency settings (cost control)
resource "aws_lambda_function" "cost_optimized" {
filename = "function.zip"
function_name = "cost-optimized-function"
role = aws_iam_role.lambda_role.arn
handler = "index.handler"
runtime = "nodejs18.x"
# Memory optimization (based on performance tests)
memory_size = 256
timeout = 30
# Cost control with reserved concurrency
reserved_concurrent_executions = 50
environment {
variables = {
NODE_ENV = "production"
# Adjust connection pool size
DB_POOL_SIZE = "5"
}
}
}
# Provisioned concurrency (cold start mitigation, cost consideration required)
resource "aws_lambda_provisioned_concurrency_config" "example" {
function_name = aws_lambda_function.cost_optimized.function_name
provisioned_concurrent_executions = 2
qualifier = aws_lambda_function.cost_optimized.version
}
2. Cloudflare Workers Cost Efficiency
Leveraging CPU Time-Based Billing:
// Cloudflare Workers - I/O wait time is not billed
export default {
async fetch(request, env, ctx) {
const startTime = Date.now();
// Parallel API calls (I/O wait time not billed)
const [userPromise, ordersPromise, inventoryPromise] = await Promise.allSettled([
fetch(`${env.API_BASE}/users/${userId}`, {
headers: { 'Authorization': `Bearer ${env.API_TOKEN}` }
}),
fetch(`${env.API_BASE}/orders/${userId}`, {
headers: { 'Authorization': `Bearer ${env.API_TOKEN}` }
}),
fetch(`${env.API_BASE}/inventory/${productId}`, {
headers: { 'Authorization': `Bearer ${env.API_TOKEN}` }
})
]);
// CPU-intensive processing (only this portion is billed)
const processStartTime = Date.now();
const results = await Promise.all([
userPromise.status === 'fulfilled' ? userPromise.value.json() : null,
ordersPromise.status === 'fulfilled' ? ordersPromise.value.json() : null,
inventoryPromise.status === 'fulfilled' ? inventoryPromise.value.json() : null
]);
const aggregatedData = aggregateUserData(results);
const cpuTime = Date.now() - processStartTime;
const totalTime = Date.now() - startTime;
// Metrics recording (async, not billed)
ctx.waitUntil(logMetrics({
cpuTime,
totalTime,
ioRatio: (totalTime - cpuTime) / totalTime
}));
return Response.json(aggregatedData);
}
};
function aggregateUserData([user, orders, inventory]) {
// CPU-intensive data processing
const aggregated = {
user: user || {},
orderSummary: orders ? {
total: orders.reduce((sum, order) => sum + order.amount, 0),
count: orders.length,
recent: orders.slice(0, 5)
} : {},
inventoryStatus: inventory || {}
};
return aggregated;
}
3. Cost Comparison Simulator
Usage-Based Cost Calculator:
// cost-calculator.js
class ServerlessCostCalculator {
constructor() {
this.providers = {
lambda: {
freeRequests: 1000000, // monthly
freeGBSeconds: 400000,
requestPrice: 0.0000002, // $0.20 per 1M requests
gbSecondPrice: 0.0000166667 // GB-second price
},
cloudflareWorkers: {
freeRequests: 100000, // daily
paidPlanBase: 5, // $5/month
includedRequests: 10000000, // included in paid plan
additionalRequestPrice: 0.0000003 // $0.30 per 1M requests
},
vercel: {
freeRequests: 100000, // monthly
paidRequestPrice: 0.0000004 // $0.40 per 1M requests
}
};
}
calculateLambdaCost(monthlyRequests, avgMemoryMB, avgDurationMs) {
const { lambda } = this.providers;
// Request pricing
const billableRequests = Math.max(0, monthlyRequests - lambda.freeRequests);
const requestCost = billableRequests * lambda.requestPrice;
// Execution time pricing
const gbSeconds = (avgMemoryMB / 1024) * (avgDurationMs / 1000) * monthlyRequests;
const billableGBSeconds = Math.max(0, gbSeconds - lambda.freeGBSeconds);
const computeCost = billableGBSeconds * lambda.gbSecondPrice;
return {
requestCost,
computeCost,
total: requestCost + computeCost,
breakdown: {
billableRequests,
gbSeconds: billableGBSeconds
}
};
}
calculateWorkersCost(monthlyRequests) {
const { cloudflareWorkers } = this.providers;
const dailyRequests = monthlyRequests / 30;
// Check if free plan covers usage
if (dailyRequests <= cloudflareWorkers.freeRequests) {
return { total: 0, plan: 'free' };
}
// Calculate paid plan cost
const baseCost = cloudflareWorkers.paidPlanBase;
const additionalRequests = Math.max(0, monthlyRequests - cloudflareWorkers.includedRequests);
const additionalCost = additionalRequests * cloudflareWorkers.additionalRequestPrice;
return {
baseCost,
additionalCost,
total: baseCost + additionalCost,
plan: 'paid'
};
}
calculateVercelCost(monthlyRequests) {
const { vercel } = this.providers;
if (monthlyRequests <= vercel.freeRequests) {
return { total: 0, plan: 'free' };
}
const billableRequests = monthlyRequests - vercel.freeRequests;
const cost = billableRequests * vercel.paidRequestPrice;
return {
billableRequests,
total: cost,
plan: 'paid'
};
}
compareAll(scenarios) {
return scenarios.map(scenario => {
const { name, monthlyRequests, avgMemoryMB = 256, avgDurationMs = 1000 } = scenario;
return {
scenario: name,
lambda: this.calculateLambdaCost(monthlyRequests, avgMemoryMB, avgDurationMs),
workers: this.calculateWorkersCost(monthlyRequests),
vercel: this.calculateVercelCost(monthlyRequests)
};
});
}
}
// Usage example
const calculator = new ServerlessCostCalculator();
const scenarios = [
{
name: 'Small API (10K requests/month)',
monthlyRequests: 10000,
avgMemoryMB: 128,
avgDurationMs: 500
},
{
name: 'Medium API (1M requests/month)',
monthlyRequests: 1000000,
avgMemoryMB: 256,
avgDurationMs: 1000
},
{
name: 'Large API (10M requests/month)',
monthlyRequests: 10000000,
avgMemoryMB: 512,
avgDurationMs: 2000
}
];
const costComparison = calculator.compareAll(scenarios);
console.table(costComparison);
Infrastructure as Code (IaC) Implementation
AWS CDK Implementation Example
// infrastructure/serverless-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources';
import { Construct } from 'constructs';
export class ServerlessStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// DynamoDB Tables
const usersTable = new dynamodb.Table(this, 'UsersTable', {
tableName: 'users',
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
pointInTimeRecovery: true,
stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES
});
const ordersTable = new dynamodb.Table(this, 'OrdersTable', {
tableName: 'orders',
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
sortKey: { name: 'createdAt', type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES
});
// SQS Queues (with dead letter queue)
const dlq = new sqs.Queue(this, 'ProcessingDLQ', {
queueName: 'processing-dlq',
retentionPeriod: cdk.Duration.days(14)
});
const processingQueue = new sqs.Queue(this, 'ProcessingQueue', {
queueName: 'processing-queue',
visibilityTimeout: cdk.Duration.minutes(5),
deadLetterQueue: {
queue: dlq,
maxReceiveCount: 3
}
});
// Common Lambda function properties
const commonLambdaProps = {
runtime: lambda.Runtime.NODEJS_18_X,
timeout: cdk.Duration.seconds(30),
memorySize: 256,
environment: {
USERS_TABLE: usersTable.tableName,
ORDERS_TABLE: ordersTable.tableName,
PROCESSING_QUEUE_URL: processingQueue.queueUrl,
NODE_ENV: 'production'
},
bundling: {
externalModules: ['aws-sdk'], // Don't include AWS SDK v3
minify: true,
sourceMap: false
}
};
// API Lambda functions
const createUserFunction = new lambda.Function(this, 'CreateUserFunction', {
...commonLambdaProps,
functionName: 'create-user',
code: lambda.Code.fromAsset('src/users', { exclude: ['*.test.js'] }),
handler: 'create.handler'
});
const getUserFunction = new lambda.Function(this, 'GetUserFunction', {
...commonLambdaProps,
functionName: 'get-user',
code: lambda.Code.fromAsset('src/users', { exclude: ['*.test.js'] }),
handler: 'get.handler'
});
const createOrderFunction = new lambda.Function(this, 'CreateOrderFunction', {
...commonLambdaProps,
functionName: 'create-order',
code: lambda.Code.fromAsset('src/orders', { exclude: ['*.test.js'] }),
handler: 'create.handler'
});
// Async processing function
const orderProcessorFunction = new lambda.Function(this, 'OrderProcessorFunction', {
...commonLambdaProps,
functionName: 'order-processor',
code: lambda.Code.fromAsset('src/processors', { exclude: ['*.test.js'] }),
handler: 'orders.handler',
timeout: cdk.Duration.minutes(5),
reservedConcurrentExecutions: 10 // Cost control
});
// Event processing function (DynamoDB Streams)
const streamProcessorFunction = new lambda.Function(this, 'StreamProcessorFunction', {
...commonLambdaProps,
functionName: 'stream-processor',
code: lambda.Code.fromAsset('src/processors', { exclude: ['*.test.js'] }),
handler: 'streams.handler'
});
// Permission settings
usersTable.grantReadWriteData(createUserFunction);
usersTable.grantReadData(getUserFunction);
ordersTable.grantReadWriteData(createOrderFunction);
processingQueue.grantSendMessages(createOrderFunction);
processingQueue.grantConsumeMessages(orderProcessorFunction);
// Event source settings
orderProcessorFunction.addEventSource(
new lambdaEventSources.SqsEventSource(processingQueue, {
batchSize: 10,
maxBatchingWindow: cdk.Duration.seconds(5)
})
);
streamProcessorFunction.addEventSource(
new lambdaEventSources.DynamoEventSource(ordersTable, {
startingPosition: lambda.StartingPosition.LATEST,
batchSize: 100,
maxBatchingWindow: cdk.Duration.seconds(5),
retryAttempts: 3
})
);
// API Gateway
const api = new apigateway.RestApi(this, 'ServerlessApi', {
restApiName: 'Serverless API',
description: 'Serverless application API',
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS,
allowMethods: apigateway.Cors.ALL_METHODS,
allowHeaders: ['Content-Type', 'Authorization']
}
});
// API endpoint configuration
const users = api.root.addResource('users');
users.addMethod('POST', new apigateway.LambdaIntegration(createUserFunction));
const user = users.addResource('{id}');
user.addMethod('GET', new apigateway.LambdaIntegration(getUserFunction));
const orders = api.root.addResource('orders');
orders.addMethod('POST', new apigateway.LambdaIntegration(createOrderFunction));
// EventBridge custom bus
const eventBus = new events.EventBus(this, 'ServerlessEventBus', {
eventBusName: 'serverless-events'
});
// Event rules
const orderCreatedRule = new events.Rule(this, 'OrderCreatedRule', {
eventBus,
eventPattern: {
source: ['order.service'],
detailType: ['Order Created']
}
});
orderCreatedRule.addTarget(new targets.LambdaFunction(orderProcessorFunction));
// CloudWatch alarms
createUserFunction.metricErrors().createAlarm(this, 'CreateUserErrorAlarm', {
threshold: 5,
evaluationPeriods: 2,
alarmDescription: 'User creation function error rate too high'
});
createUserFunction.metricDuration().createAlarm(this, 'CreateUserDurationAlarm', {
threshold: cdk.Duration.seconds(10).toMilliseconds(),
evaluationPeriods: 3,
alarmDescription: 'User creation function duration too high'
});
// Outputs
new cdk.CfnOutput(this, 'ApiEndpoint', {
value: api.url,
description: 'API Gateway endpoint URL'
});
new cdk.CfnOutput(this, 'EventBusArn', {
value: eventBus.eventBusArn,
description: 'EventBridge custom bus ARN'
});
}
}
// app.ts
import * as cdk from 'aws-cdk-lib';
import { ServerlessStack } from './serverless-stack';
const app = new cdk.App();
new ServerlessStack(app, 'ServerlessStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION
},
tags: {
Environment: 'production',
Project: 'serverless-demo'
}
});
Monitoring and Observability Implementation
Distributed Tracing
AWS X-Ray Configuration:
// src/middleware/tracing.js
const AWSXRay = require('aws-xray-sdk-core');
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
// Helper for creating subsegments
function createSubsegment(name, callback) {
const segment = AWSXRay.getSegment();
const subsegment = segment.addNewSubsegment(name);
return new Promise((resolve, reject) => {
subsegment.addAnnotation('subsegment', name);
callback(subsegment)
.then(result => {
subsegment.close();
resolve(result);
})
.catch(error => {
subsegment.addError(error);
subsegment.close(error);
reject(error);
});
});
}
// Database access tracing
async function queryDatabase(params) {
return createSubsegment('DynamoDB-Query', async (subsegment) => {
subsegment.addMetadata('query', params);
const dynamodb = new AWS.DynamoDB.DocumentClient();
const result = await dynamodb.query(params).promise();
subsegment.addMetadata('result', {
itemCount: result.Items ? result.Items.length : 0,
scannedCount: result.ScannedCount
});
return result;
});
}
// External API call tracing
async function callExternalAPI(url, options = {}) {
return createSubsegment('External-API', async (subsegment) => {
subsegment.addAnnotation('url', url);
subsegment.addMetadata('request', options);
const response = await fetch(url, options);
subsegment.addAnnotation('statusCode', response.status);
subsegment.addMetadata('response', {
status: response.status,
headers: Object.fromEntries(response.headers.entries())
});
if (!response.ok) {
throw new Error(`API call failed: ${response.status}`);
}
return response.json();
});
}
module.exports = {
createSubsegment,
queryDatabase,
callExternalAPI
};
Structured Logging and Metrics
// src/utils/logger.js
class Logger {
constructor(context) {
this.requestId = context.awsRequestId;
this.functionName = context.functionName;
this.stage = process.env.STAGE || 'dev';
}
_formatLog(level, message, data = {}) {
return JSON.stringify({
timestamp: new Date().toISOString(),
level,
message,
requestId: this.requestId,
functionName: this.functionName,
stage: this.stage,
...data
});
}
info(message, data) {
console.log(this._formatLog('INFO', message, data));
}
error(message, error, data = {}) {
const errorData = {
error: {
name: error.name,
message: error.message,
stack: error.stack
},
...data
};
console.error(this._formatLog('ERROR', message, errorData));
}
warn(message, data) {
console.warn(this._formatLog('WARN', message, data));
}
debug(message, data) {
if (process.env.LOG_LEVEL === 'debug') {
console.debug(this._formatLog('DEBUG', message, data));
}
}
// Business metrics
metric(metricName, value, unit = 'Count', dimensions = {}) {
const metricData = {
timestamp: new Date().toISOString(),
type: 'METRIC',
metricName,
value,
unit,
dimensions: {
functionName: this.functionName,
stage: this.stage,
...dimensions
}
};
console.log(JSON.stringify(metricData));
}
}
// CloudWatch custom metrics sending
const AWS = require('aws-sdk');
const cloudwatch = new AWS.CloudWatch();
class MetricsCollector {
constructor() {
this.metrics = [];
}
addMetric(name, value, unit = 'Count', dimensions = {}) {
this.metrics.push({
MetricName: name,
Value: value,
Unit: unit,
Dimensions: Object.entries(dimensions).map(([Name, Value]) => ({
Name,
Value: String(Value)
}))
});
}
async flush() {
if (this.metrics.length === 0) return;
const params = {
Namespace: 'ServerlessApp/BusinessMetrics',
MetricData: this.metrics
};
try {
await cloudwatch.putMetricData(params).promise();
this.metrics = [];
} catch (error) {
console.error('Failed to send metrics:', error);
}
}
}
module.exports = { Logger, MetricsCollector };
Performance Monitoring
// src/middleware/performance.js
const { Logger, MetricsCollector } = require('../utils/logger');
class PerformanceMonitor {
constructor(context) {
this.logger = new Logger(context);
this.metrics = new MetricsCollector();
this.startTime = Date.now();
this.checkpoints = [];
}
checkpoint(name) {
const now = Date.now();
const checkpoint = {
name,
timestamp: now,
elapsed: now - this.startTime
};
this.checkpoints.push(checkpoint);
this.logger.debug('Performance checkpoint', checkpoint);
return checkpoint;
}
async measureAsync(name, asyncFunction) {
const start = Date.now();
try {
const result = await asyncFunction();
const duration = Date.now() - start;
this.metrics.addMetric(`${name}.Duration`, duration, 'Milliseconds');
this.metrics.addMetric(`${name}.Success`, 1);
this.logger.info(`${name} completed`, { duration });
return result;
} catch (error) {
const duration = Date.now() - start;
this.metrics.addMetric(`${name}.Duration`, duration, 'Milliseconds');
this.metrics.addMetric(`${name}.Error`, 1);
this.logger.error(`${name} failed`, error, { duration });
throw error;
}
}
async finalize() {
const totalDuration = Date.now() - this.startTime;
this.metrics.addMetric('Function.Duration', totalDuration, 'Milliseconds');
this.metrics.addMetric('Function.Invocation', 1);
// Memory usage
const memoryUsage = process.memoryUsage();
this.metrics.addMetric('Function.MemoryUsed',
Math.round(memoryUsage.heapUsed / 1024 / 1024), 'Megabytes');
this.logger.info('Function execution completed', {
totalDuration,
memoryUsage,
checkpoints: this.checkpoints
});
await this.metrics.flush();
}
}
// Usage example
exports.handler = async (event, context) => {
const monitor = new PerformanceMonitor(context);
try {
monitor.checkpoint('start');
// Database access
const userData = await monitor.measureAsync('database.getUser', async () => {
return await queryUser(event.userId);
});
monitor.checkpoint('user-data-loaded');
// External API call
const externalData = await monitor.measureAsync('api.getExternalData', async () => {
return await fetchExternalData(userData.id);
});
monitor.checkpoint('external-data-loaded');
// Business logic
const result = await monitor.measureAsync('business.processData', async () => {
return await processUserData(userData, externalData);
});
monitor.checkpoint('processing-completed');
return {
statusCode: 200,
body: JSON.stringify(result)
};
} catch (error) {
monitor.logger.error('Function execution failed', error);
throw error;
} finally {
await monitor.finalize();
}
};
Security Best Practices
Authentication and Authorization
// src/middleware/auth.js
const jwt = require('jsonwebtoken');
const { Logger } = require('../utils/logger');
class AuthMiddleware {
constructor(options = {}) {
this.secretKey = options.secretKey || process.env.JWT_SECRET;
this.issuer = options.issuer || process.env.JWT_ISSUER;
this.audience = options.audience || process.env.JWT_AUDIENCE;
}
// JWT token verification
async verifyToken(token) {
try {
const decoded = jwt.verify(token, this.secretKey, {
issuer: this.issuer,
audience: this.audience,
algorithms: ['HS256']
});
return {
valid: true,
user: decoded
};
} catch (error) {
return {
valid: false,
error: error.message
};
}
}
// API key verification
async verifyApiKey(apiKey) {
// Get API key information from DynamoDB
const { queryDatabase } = require('./tracing');
const result = await queryDatabase({
TableName: process.env.API_KEYS_TABLE,
Key: { keyId: apiKey }
});
if (!result.Item) {
return { valid: false, error: 'Invalid API key' };
}
const keyData = result.Item;
// Check key expiration
if (keyData.expiresAt && new Date(keyData.expiresAt) < new Date()) {
return { valid: false, error: 'API key expired' };
}
// Rate limit check
const rateLimit = await this.checkRateLimit(apiKey, keyData.rateLimit);
if (!rateLimit.allowed) {
return { valid: false, error: 'Rate limit exceeded' };
}
return {
valid: true,
key: keyData,
rateLimit
};
}
async checkRateLimit(apiKey, limit) {
const { DynamoDB } = require('aws-sdk');
const dynamodb = new DynamoDB.DocumentClient();
const now = Math.floor(Date.now() / 1000);
const windowStart = now - (limit.windowSeconds || 3600);
try {
// Get current window usage
const result = await dynamodb.get({
TableName: process.env.RATE_LIMIT_TABLE,
Key: {
keyId: apiKey,
window: Math.floor(now / (limit.windowSeconds || 3600))
}
}).promise();
const currentUsage = result.Item ? result.Item.count : 0;
if (currentUsage >= limit.requests) {
return {
allowed: false,
remaining: 0,
resetTime: (Math.floor(now / (limit.windowSeconds || 3600)) + 1) * (limit.windowSeconds || 3600)
};
}
// Update usage
await dynamodb.update({
TableName: process.env.RATE_LIMIT_TABLE,
Key: {
keyId: apiKey,
window: Math.floor(now / (limit.windowSeconds || 3600))
},
UpdateExpression: 'ADD #count :inc SET #ttl = :ttl',
ExpressionAttributeNames: {
'#count': 'count',
'#ttl': 'ttl'
},
ExpressionAttributeValues: {
':inc': 1,
':ttl': now + (limit.windowSeconds || 3600) + 86400 // Delete after 24 hours
}
}).promise();
return {
allowed: true,
remaining: limit.requests - currentUsage - 1,
resetTime: (Math.floor(now / (limit.windowSeconds || 3600)) + 1) * (limit.windowSeconds || 3600)
};
} catch (error) {
console.error('Rate limit check failed:', error);
// Allow request on error (fail open)
return { allowed: true, remaining: limit.requests };
}
}
}
// Authentication middleware
function createAuthMiddleware(options = {}) {
const auth = new AuthMiddleware(options);
return async (event, context) => {
const logger = new Logger(context);
try {
const authHeader = event.headers.Authorization || event.headers.authorization;
const apiKeyHeader = event.headers['X-API-Key'] || event.headers['x-api-key'];
let authResult = null;
if (authHeader && authHeader.startsWith('Bearer ')) {
const token = authHeader.substring(7);
authResult = await auth.verifyToken(token);
if (!authResult.valid) {
logger.warn('JWT token validation failed', {
error: authResult.error,
ip: event.requestContext?.identity?.sourceIp
});
return {
statusCode: 401,
body: JSON.stringify({ error: 'Invalid token' })
};
}
logger.info('JWT authentication successful', {
userId: authResult.user.sub
});
} else if (apiKeyHeader) {
authResult = await auth.verifyApiKey(apiKeyHeader);
if (!authResult.valid) {
logger.warn('API key validation failed', {
error: authResult.error,
ip: event.requestContext?.identity?.sourceIp
});
return {
statusCode: 401,
headers: {
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': Math.floor(Date.now() / 1000) + 3600
},
body: JSON.stringify({ error: authResult.error })
};
}
logger.info('API key authentication successful', {
keyId: authResult.key.keyId
});
} else {
logger.warn('No authentication provided', {
ip: event.requestContext?.identity?.sourceIp
});
return {
statusCode: 401,
body: JSON.stringify({ error: 'Authentication required' })
};
}
// Add authentication info to event
event.auth = authResult;
return null; // Authentication successful
} catch (error) {
logger.error('Authentication error', error);
return {
statusCode: 500,
body: JSON.stringify({ error: 'Authentication service unavailable' })
};
}
};
}
module.exports = { AuthMiddleware, createAuthMiddleware };
Testing Strategies
Unit Testing
// tests/unit/users.test.js
const { handler } = require('../../src/users/create');
const { Logger } = require('../../src/utils/logger');
// Mock AWS SDK
jest.mock('aws-sdk', () => ({
DynamoDB: {
DocumentClient: jest.fn(() => ({
put: jest.fn().mockReturnValue({
promise: jest.fn()
})
}))
}
}));
// Mock logger
jest.mock('../../src/utils/logger');
describe('Create User Function', () => {
let mockContext;
let mockDynamoDb;
beforeEach(() => {
mockContext = {
awsRequestId: 'test-request-id',
functionName: 'test-function',
remainingTimeInMillis: () => 30000
};
mockDynamoDb = require('aws-sdk').DynamoDB.DocumentClient();
Logger.mockClear();
process.env.USERS_TABLE = 'test-users-table';
});
afterEach(() => {
jest.clearAllMocks();
delete process.env.USERS_TABLE;
});
describe('Valid Input', () => {
test('should create user successfully', async () => {
const event = {
body: JSON.stringify({
name: 'John Doe',
email: '[email protected]',
age: 30
})
};
mockDynamoDb.put().promise.mockResolvedValue({});
const result = await handler(event, mockContext);
expect(result.statusCode).toBe(201);
expect(JSON.parse(result.body)).toHaveProperty('id');
expect(JSON.parse(result.body)).toHaveProperty('message', 'User created successfully');
// Verify DynamoDB call
expect(mockDynamoDb.put).toHaveBeenCalledWith({
TableName: 'test-users-table',
Item: expect.objectContaining({
name: 'John Doe',
email: '[email protected]',
age: 30,
id: expect.any(String),
createdAt: expect.any(String)
})
});
});
});
describe('Invalid Input', () => {
test('should return 400 for invalid JSON', async () => {
const event = {
body: 'invalid json'
};
const result = await handler(event, mockContext);
expect(result.statusCode).toBe(400);
expect(JSON.parse(result.body)).toHaveProperty('error');
});
test('should return 400 for missing required fields', async () => {
const event = {
body: JSON.stringify({
name: 'John Doe'
// email is missing
})
};
const result = await handler(event, mockContext);
expect(result.statusCode).toBe(400);
expect(JSON.parse(result.body)).toHaveProperty('error');
});
});
describe('Database Errors', () => {
test('should handle DynamoDB errors gracefully', async () => {
const event = {
body: JSON.stringify({
name: 'John Doe',
email: '[email protected]'
})
};
const dbError = new Error('Database connection failed');
mockDynamoDb.put().promise.mockRejectedValue(dbError);
const result = await handler(event, mockContext);
expect(result.statusCode).toBe(500);
expect(JSON.parse(result.body)).toHaveProperty('error', 'Internal server error');
});
});
});
Integration Testing
// tests/integration/api.test.js
const AWS = require('aws-sdk');
const fetch = require('node-fetch');
// Test environment configuration
const API_ENDPOINT = process.env.API_ENDPOINT || 'https://api.example.com';
const STAGE = process.env.STAGE || 'test';
describe('API Integration Tests', () => {
let createdUserId;
let authToken;
beforeAll(async () => {
// Get test authentication token
authToken = await getTestAuthToken();
});
afterAll(async () => {
// Clean up test data
if (createdUserId) {
await cleanupTestUser(createdUserId);
}
});
describe('User Management', () => {
test('should create a new user', async () => {
const userData = {
name: 'Integration Test User',
email: `test-${Date.now()}@example.com`,
age: 25
};
const response = await fetch(`${API_ENDPOINT}/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify(userData)
});
expect(response.status).toBe(201);
const result = await response.json();
expect(result).toHaveProperty('id');
expect(result).toHaveProperty('message', 'User created successfully');
createdUserId = result.id;
});
test('should retrieve created user', async () => {
expect(createdUserId).toBeDefined();
const response = await fetch(`${API_ENDPOINT}/users/${createdUserId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
expect(response.status).toBe(200);
const user = await response.json();
expect(user).toHaveProperty('id', createdUserId);
expect(user).toHaveProperty('name', 'Integration Test User');
});
test('should handle non-existent user', async () => {
const response = await fetch(`${API_ENDPOINT}/users/non-existent-id`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
expect(response.status).toBe(404);
});
});
describe('Authentication', () => {
test('should reject request without authentication', async () => {
const response = await fetch(`${API_ENDPOINT}/users/any-id`);
expect(response.status).toBe(401);
});
test('should reject request with invalid token', async () => {
const response = await fetch(`${API_ENDPOINT}/users/any-id`, {
headers: {
'Authorization': 'Bearer invalid-token'
}
});
expect(response.status).toBe(401);
});
});
describe('Rate Limiting', () => {
test('should enforce rate limits', async () => {
const requests = [];
// Execute multiple requests in parallel
for (let i = 0; i < 10; i++) {
requests.push(
fetch(`${API_ENDPOINT}/users/${createdUserId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
})
);
}
const responses = await Promise.all(requests);
// Verify some requests hit rate limits
const rateLimitedResponses = responses.filter(r => r.status === 429);
expect(rateLimitedResponses.length).toBeGreaterThan(0);
}, 10000);
});
});
async function getTestAuthToken() {
// Generate or retrieve test token
const jwt = require('jsonwebtoken');
return jwt.sign(
{
sub: 'test-user-id',
email: '[email protected]',
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600
},
process.env.JWT_SECRET || 'test-secret',
{
issuer: process.env.JWT_ISSUER || 'test-issuer',
audience: process.env.JWT_AUDIENCE || 'test-audience'
}
);
}
async function cleanupTestUser(userId) {
const dynamodb = new AWS.DynamoDB.DocumentClient({
region: process.env.AWS_REGION || 'us-east-1'
});
try {
await dynamodb.delete({
TableName: process.env.USERS_TABLE || `users-${STAGE}`,
Key: { id: userId }
}).promise();
} catch (error) {
console.warn('Failed to cleanup test user:', error);
}
}
Performance Testing
// tests/performance/load.test.js
const autocannon = require('autocannon');
describe('Performance Tests', () => {
const API_ENDPOINT = process.env.API_ENDPOINT || 'https://api.example.com';
test('should handle moderate load', async () => {
const result = await autocannon({
url: `${API_ENDPOINT}/health`,
connections: 10,
pipelining: 1,
duration: 10, // 10 seconds
headers: {
'Content-Type': 'application/json'
}
});
console.log('Load test results:', {
requests: result.requests,
latency: result.latency,
throughput: result.throughput,
errors: result.errors
});
// Performance requirements verification
expect(result.latency.mean).toBeLessThan(1000); // Average response time < 1 second
expect(result.latency.p99).toBeLessThan(3000); // 99%ile < 3 seconds
expect(result.errors).toBe(0); // Error rate 0%
expect(result.requests.average).toBeGreaterThan(50); // Minimum 50 RPS
}, 30000);
test('should maintain performance under authenticated load', async () => {
const authToken = await getTestAuthToken();
const result = await autocannon({
url: `${API_ENDPOINT}/users/test-user-id`,
connections: 5,
pipelining: 1,
duration: 10,
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
}
});
// Maintain performance even with authentication
expect(result.latency.mean).toBeLessThan(1500);
expect(result.latency.p99).toBeLessThan(5000);
expect(result.errors).toBe(0);
}, 30000);
});
Conclusion
Serverless architecture has become a core technology for modern application development in 2024. By properly combining the implementation patterns, cost optimization strategies, and monitoring techniques covered in this article, you can build scalable and efficient systems.
Key Points
- Appropriate Platform Selection: Choosing the right platform based on workload characteristics is crucial
- Cost Optimization: Continuous monitoring and optimization of execution time, memory usage, and request count
- Monitoring and Observability: Implementation of distributed tracing, structured logging, and metrics collection
- Security: Proper implementation of authentication/authorization, input validation, and rate limiting
- Testing Strategy: Comprehensive implementation of unit tests, integration tests, and performance tests
By staying informed about the latest technology trends and maintaining continuous improvement, you can maximize the benefits of serverless architecture.