工程化与框架系列(11)--Serverless实践

Serverless实践 ☁️

Serverless(无服务器)架构是云计算的一种新范式,它让开发者专注于业务逻辑而无需关心服务器运维。本文将详细介绍前端开发中的Serverless实践方案。

Serverless概述 🌟

💡 小知识:Serverless并不是真的没有服务器,而是将服务器管理的职责转移给了云服务提供商,开发者只需要关注业务代码的编写。

为什么选择Serverless

在现代前端开发中,Serverless带来以下优势:

  1. 降低运维成本

    • 无需管理服务器
    • 自动扩缩容
    • 按使用付费
    • 降低维护成本
  2. 提高开发效率

    • 专注业务逻辑
    • 快速部署上线
    • 简化开发流程
    • 减少基础设施代码
  3. 灵活扩展能力

    • 自动伸缩
    • 高可用性
    • 全球部署
    • 按需使用资源
  4. 成本优化

    • 按量计费
    • 无闲置资源
    • 精确计量
    • 成本可控

函数计算实践 ⚡

基于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!'
);

最佳实践建议 ⭐

开发原则

  1. 函数设计

    • 单一职责
    • 无状态设计
    • 适当超时设置
    • 错误处理完善
  2. 性能优化

    • 冷启动优化
    • 资源复用
    • 并发控制
    • 缓存策略
  3. 安全考虑

    • 最小权限原则
    • 密钥管理
    • 输入验证
    • 日志审计

开发流程建议

  1. 本地开发环境
# 安装Serverless Framework
npm install -g serverless

# 创建新项目
serverless create --template aws-nodejs-typescript
cd my-serverless-project

# 安装依赖
npm install

# 本地测试
serverless offline start
  1. 调试和测试
// 本地调试配置
// 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架构为前端开发提供了一种全新的开发模式,它能够显著提高开发效率并降低运维成本。通过本文,我们学习了:

  1. Serverless的基本概念和优势
  2. 函数计算的实践方案
  3. 静态网站的部署策略
  4. 数据存储的实现方式
  5. 身份认证的解决方案

💡 学习建议:

  1. 从简单的API开始实践
  2. 熟悉云服务提供商的产品
  3. 注重安全性和性能优化
  4. 建立完善的监控体系
  5. 保持代码的可维护性

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值