Serverless实践 ☁️
Serverless(无服务器)架构是云计算的一种新范式,它让开发者专注于业务逻辑而无需关心服务器运维。本文将详细介绍前端开发中的Serverless实践方案。
Serverless概述 🌟
💡 小知识:Serverless并不是真的没有服务器,而是将服务器管理的职责转移给了云服务提供商,开发者只需要关注业务代码的编写。
为什么选择Serverless
在现代前端开发中,Serverless带来以下优势:
-
降低运维成本
- 无需管理服务器
- 自动扩缩容
- 按使用付费
- 降低维护成本
-
提高开发效率
- 专注业务逻辑
- 快速部署上线
- 简化开发流程
- 减少基础设施代码
-
灵活扩展能力
- 自动伸缩
- 高可用性
- 全球部署
- 按需使用资源
-
成本优化
- 按量计费
- 无闲置资源
- 精确计量
- 成本可控
函数计算实践 ⚡
基于AWS Lambda的实现
// aws-lambda.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
import * as AWS from 'aws-sdk';
const dynamoDB = new AWS.DynamoDB.DocumentClient();
export const createUser: APIGatewayProxyHandler = async (event) => {
try {
const requestBody = JSON.parse(event.body || '{}');
const { username, email } = requestBody;
const params = {
TableName: 'Users',
Item: {
userId: Date.now().toString(),
username,
email,
createdAt: new Date().toISOString()
}
};
await dynamoDB.put(params).promise();
return {
statusCode: 201,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: 'User created successfully',
user: params.Item
})
};
} catch (error) {
return {
statusCode: 500,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: 'Failed to create user',
error: error.message
})
};
}
};
export const getUser: APIGatewayProxyHandler = async (event) => {
try {
const userId = event.pathParameters?.userId;
const params = {
TableName: 'Users',
Key: {
userId
}
};
const result = await dynamoDB.get(params).promise();
if (!result.Item) {
return {
statusCode: 404,
body: JSON.stringify({
message: 'User not found'
})
};
}
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(result.Item)
};
} catch (error) {
return {
statusCode: 500,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: 'Failed to get user',
error: error.message
})
};
}
};
基于Vercel的实现
// vercel-api.ts
import { VercelRequest, VercelResponse } from '@vercel/node';
import { connectToDatabase } from '../utils/database';
export default async function handler(
req: VercelRequest,
res: VercelResponse
) {
if (req.method === 'POST') {
try {
const { username, email } = req.body;
const db = await connectToDatabase();
const result = await db.collection('users').insertOne({
username,
email,
createdAt: new Date()
});
return res.status(201).json({
message: 'User created successfully',
userId: result.insertedId
});
} catch (error) {
return res.status(500).json({
message: 'Failed to create user',
error: error.message
});
}
}
if (req.method === 'GET') {
try {
const { userId } = req.query;
const db = await connectToDatabase();
const user = await db.collection('users').findOne({
_id: userId
});
if (!user) {
return res.status(404).json({
message: 'User not found'
});
}
return res.status(200).json(user);
} catch (error) {
return res.status(500).json({
message: 'Failed to get user',
error: error.message
});
}
}
return res.status(405).json({
message: 'Method not allowed'
});
}
静态网站部署 🚀
Serverless Framework配置
# serverless.yml
service: my-static-website
provider:
name: aws
runtime: nodejs14.x
stage: ${opt:stage, 'dev'}
region: ${opt:region, 'us-east-1'}
plugins:
- serverless-finch
- serverless-single-page-app-plugin
custom:
client:
bucketName: my-website-${self:provider.stage}
distributionFolder: build
indexDocument: index.html
errorDocument: index.html
spa:
website: true
certificate: ${self:custom.domain.certificate}
dns: true
resources:
Resources:
ClientBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:custom.client.bucketName}
WebsiteConfiguration:
IndexDocument: ${self:custom.client.indexDocument}
ErrorDocument: ${self:custom.client.errorDocument}
ClientBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref ClientBucket
PolicyDocument:
Statement:
- Effect: Allow
Principal: '*'
Action: s3:GetObject
Resource: !Join ['/', [!GetAtt ClientBucket.Arn, '*']]
自动部署配置
# github-actions-deploy.yml
name: Deploy Website
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
run: npm ci
- name: Build website
run: npm run build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy to S3
run: |
aws s3 sync build/ s3://my-website-${GITHUB_REF##*/} \
--delete \
--cache-control "max-age=31536000"
- name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*"
数据存储方案 💾
基于DynamoDB的实现
// dynamodb-service.ts
import { DynamoDB } from 'aws-sdk';
import { DocumentClient } from 'aws-sdk/clients/dynamodb';
export class DynamoDBService {
private readonly client: DocumentClient;
private readonly tableName: string;
constructor(tableName: string) {
this.client = new DynamoDB.DocumentClient();
this.tableName = tableName;
}
async create<T extends { id: string }>(item: T): Promise<T> {
const params = {
TableName: this.tableName,
Item: {
...item,
createdAt: new Date().toISOString()
}
};
await this.client.put(params).promise();
return item;
}
async get<T>(id: string): Promise<T | null> {
const params = {
TableName: this.tableName,
Key: { id }
};
const result = await this.client.get(params).promise();
return (result.Item as T) || null;
}
async update<T extends { id: string }>(
id: string,
updates: Partial<T>
): Promise<T> {
const updateExpressions: string[] = [];
const expressionAttributeNames: Record<string, string> = {};
const expressionAttributeValues: Record<string, any> = {};
Object.entries(updates).forEach(([key, value]) => {
if (key !== 'id') {
const attributeName = `#${key}`;
const attributeValue = `:${key}`;
updateExpressions.push(`${attributeName} = ${attributeValue}`);
expressionAttributeNames[attributeName] = key;
expressionAttributeValues[attributeValue] = value;
}
});
const params = {
TableName: this.tableName,
Key: { id },
UpdateExpression: `SET ${updateExpressions.join(', ')}`,
ExpressionAttributeNames: expressionAttributeNames,
ExpressionAttributeValues: expressionAttributeValues,
ReturnValues: 'ALL_NEW'
};
const result = await this.client.update(params).promise();
return result.Attributes as T;
}
async delete(id: string): Promise<void> {
const params = {
TableName: this.tableName,
Key: { id }
};
await this.client.delete(params).promise();
}
async query<T>(
indexName: string,
keyCondition: string,
expressionAttributeValues: Record<string, any>
): Promise<T[]> {
const params = {
TableName: this.tableName,
IndexName: indexName,
KeyConditionExpression: keyCondition,
ExpressionAttributeValues: expressionAttributeValues
};
const result = await this.client.query(params).promise();
return (result.Items as T[]) || [];
}
}
// 使用示例
const userService = new DynamoDBService('Users');
// 创建用户
const user = await userService.create({
id: 'user123',
name: 'John Doe',
email: 'john@example.com'
});
// 查询用户
const result = await userService.query(
'EmailIndex',
'email = :email',
{ ':email': 'john@example.com' }
);
身份认证实现 🔐
基于Cognito的认证
// auth-service.ts
import { CognitoIdentityServiceProvider } from 'aws-sdk';
export class AuthService {
private readonly cognito: CognitoIdentityServiceProvider;
private readonly userPoolId: string;
private readonly clientId: string;
constructor(userPoolId: string, clientId: string) {
this.cognito = new CognitoIdentityServiceProvider();
this.userPoolId = userPoolId;
this.clientId = clientId;
}
async signUp(
username: string,
password: string,
email: string
): Promise<string> {
const params = {
ClientId: this.clientId,
Username: username,
Password: password,
UserAttributes: [
{
Name: 'email',
Value: email
}
]
};
const result = await this.cognito.signUp(params).promise();
return result.UserSub;
}
async confirmSignUp(
username: string,
code: string
): Promise<void> {
const params = {
ClientId: this.clientId,
Username: username,
ConfirmationCode: code
};
await this.cognito.confirmSignUp(params).promise();
}
async signIn(
username: string,
password: string
): Promise<{
accessToken: string;
refreshToken: string;
idToken: string;
}> {
const params = {
AuthFlow: 'USER_PASSWORD_AUTH',
ClientId: this.clientId,
AuthParameters: {
USERNAME: username,
PASSWORD: password
}
};
const result = await this.cognito.initiateAuth(params).promise();
const authResult = result.AuthenticationResult!;
return {
accessToken: authResult.AccessToken!,
refreshToken: authResult.RefreshToken!,
idToken: authResult.IdToken!
};
}
async refreshToken(
refreshToken: string
): Promise<{
accessToken: string;
idToken: string;
}> {
const params = {
AuthFlow: 'REFRESH_TOKEN_AUTH',
ClientId: this.clientId,
AuthParameters: {
REFRESH_TOKEN: refreshToken
}
};
const result = await this.cognito.initiateAuth(params).promise();
const authResult = result.AuthenticationResult!;
return {
accessToken: authResult.AccessToken!,
idToken: authResult.IdToken!
};
}
async forgotPassword(username: string): Promise<void> {
const params = {
ClientId: this.clientId,
Username: username
};
await this.cognito.forgotPassword(params).promise();
}
async confirmForgotPassword(
username: string,
code: string,
newPassword: string
): Promise<void> {
const params = {
ClientId: this.clientId,
Username: username,
ConfirmationCode: code,
Password: newPassword
};
await this.cognito.confirmForgotPassword(params).promise();
}
}
// 使用示例
const authService = new AuthService(
'us-east-1_xxxxxx',
'xxxxxxxxxxxxxxxxxx'
);
// 注册用户
const userId = await authService.signUp(
'johndoe',
'Password123!',
'john@example.com'
);
// 登录
const tokens = await authService.signIn(
'johndoe',
'Password123!'
);
最佳实践建议 ⭐
开发原则
-
函数设计
- 单一职责
- 无状态设计
- 适当超时设置
- 错误处理完善
-
性能优化
- 冷启动优化
- 资源复用
- 并发控制
- 缓存策略
-
安全考虑
- 最小权限原则
- 密钥管理
- 输入验证
- 日志审计
开发流程建议
- 本地开发环境
# 安装Serverless Framework
npm install -g serverless
# 创建新项目
serverless create --template aws-nodejs-typescript
cd my-serverless-project
# 安装依赖
npm install
# 本地测试
serverless offline start
- 调试和测试
// 本地调试配置
// serverless.yml
custom:
serverless-offline:
httpPort: 3000
lambdaPort: 3002
websocketPort: 3001
plugins:
- serverless-offline
- serverless-webpack
- serverless-dotenv-plugin
# 单元测试示例
describe('User API', () => {
it('should create user', async () => {
const event = {
body: JSON.stringify({
username: 'test',
email: 'test@example.com'
})
};
const result = await createUser(event as any);
expect(result.statusCode).toBe(201);
});
});
结语 📝
Serverless架构为前端开发提供了一种全新的开发模式,它能够显著提高开发效率并降低运维成本。通过本文,我们学习了:
- Serverless的基本概念和优势
- 函数计算的实践方案
- 静态网站的部署策略
- 数据存储的实现方式
- 身份认证的解决方案
💡 学习建议:
- 从简单的API开始实践
- 熟悉云服务提供商的产品
- 注重安全性和性能优化
- 建立完善的监控体系
- 保持代码的可维护性
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻