Serverless computing enables building scalable applications without managing infrastructure. This guide explores common serverless patterns and best practices.
Core Concepts
Function as a Service (FaaS)
- Event-driven execution
- Automatic scaling
- Pay per execution
- No server management
- AWS Lambda
- Azure Functions
- Google Cloud Functions
- Cloudflare Workers
Common Patterns
API Gateway Pattern
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // AWS Lambda + API Gateway
exports.handler = async (event) => {
const { httpMethod, path, body } = event;
if (httpMethod === 'GET' && path === '/users') {
const users = await getUsers();
return {
statusCode: 200,
body: JSON.stringify(users)
};
}
return { statusCode: 404, body: 'Not Found' };
};
|
Event Processing
1
2
3
4
5
6
7
8
| # AWS Lambda triggered by S3 upload
def lambda_handler(event, context):
for record in event['Records']:
bucket = record['s3']['bucket']['name']
key = record['s3']['object']['key']
# Process uploaded file
process_image(bucket, key)
|
Queue Processing
1
2
3
4
5
6
7
| // SQS Queue processor
exports.handler = async (event) => {
for (const record of event.Records) {
const message = JSON.parse(record.body);
await processMessage(message);
}
};
|
Scheduled Tasks
1
2
3
4
5
6
| # Serverless Framework
functions:
cronJob:
handler: handler.run
events:
- schedule: rate(1 hour)
|
Best Practices
Cold Start Optimization
1
2
3
4
5
6
7
8
9
| // Keep connections warm
let dbConnection;
exports.handler = async (event) => {
if (!dbConnection) {
dbConnection = await createConnection();
}
return queryDatabase(dbConnection, event);
};
|
Error Handling
1
2
3
4
5
6
7
8
9
10
| import logging
logger = logging.getLogger()
def lambda_handler(event, context):
try:
result = process_event(event)
return {'statusCode': 200, 'body': result}
except Exception as e:
logger.error(f"Error: {str(e)}")
return {'statusCode': 500, 'body': 'Internal Error'}
|
Environment Variables
1
2
3
4
5
| const config = {
dbHost: process.env.DB_HOST,
apiKey: process.env.API_KEY,
region: process.env.AWS_REGION
};
|
Security
IAM Roles
1
2
3
4
5
6
7
8
9
| provider:
iam:
role:
statements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:GetItem
Resource: arn:aws:dynamodb:*:*:table/Users
|
Secrets Management
1
2
3
4
5
6
7
8
9
| const AWS = require('aws-sdk');
const secretsManager = new AWS.SecretsManager();
async function getSecret(secretName) {
const data = await secretsManager
.getSecretValue({ SecretId: secretName })
.promise();
return JSON.parse(data.SecretString);
}
|
Monitoring
Logging
1
2
3
4
5
6
7
| import json
def lambda_handler(event, context):
print(json.dumps({
'event': event,
'requestId': context.request_id
}))
|
Tracing
1
2
3
4
5
6
7
8
9
10
11
| const AWSXRay = require('aws-xray-sdk-core');
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
exports.handler = async (event) => {
const segment = AWSXRay.getSegment();
const subsegment = segment.addNewSubsegment('custom-operation');
await performOperation();
subsegment.close();
};
|
Cost Optimization
- Right-size memory allocation
- Minimize cold starts
- Use reserved concurrency wisely
- Implement timeouts
- Clean up unused functions
- Monitor invocation patterns
Testing
Unit Tests
1
2
3
4
5
6
7
8
9
10
11
| const { handler } = require('./index');
test('returns 200 for valid request', async () => {
const event = {
httpMethod: 'GET',
path: '/users'
};
const result = await handler(event);
expect(result.statusCode).toBe(200);
});
|
Integration Tests
1
2
3
4
5
6
7
8
9
10
| import boto3
import json
def test_lambda_integration():
lambda_client = boto3.client('lambda')
response = lambda_client.invoke(
FunctionName='my-function',
Payload=json.dumps({'test': 'data'})
)
assert response['StatusCode'] == 200
|
Deployment
Infrastructure as Code
1
2
3
4
5
6
7
8
9
10
11
12
13
| # Serverless Framework
service: my-api
provider:
name: aws
runtime: nodejs18.x
functions:
hello:
handler: handler.hello
events:
- http:
path: hello
method: get
|
CI/CD
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # GitHub Actions
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm install -g serverless
- run: serverless deploy
|
Challenges
- Cold start latency
- Debugging complexity
- Vendor lock-in
- Timeout limitations
- Stateless constraints
Conclusion
Serverless architecture offers significant benefits for scalable, event-driven applications. Understanding patterns and best practices is key to successful implementation.