Golang中AWS Lambda的设计模式

Golang中AWS Lambda的设计模式 大家好,想请教一下关于AWS Lambda Go函数设计模式的看法。

如你所知,典型的函数看起来像这样(AWS Lambda Go函数处理程序 - AWS Lambda):

func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	...
}

func main() {
	lambda.Start(HandleRequest)
}

但在实际应用中,一个Lambda函数会调用其他Lambda函数,需要处理环境变量等。因此我创建了一些通用代码:

type Application struct {
	LambdaClient awsutils.LambdaClientInterface
	Arn          map[ServiceArn]string
	OtherEnvs    map[string]string
	Log          zerolog.Logger
	Ctx          context.Context
}

以及一个NewApplication函数,用于填充ARN映射和其他环境变量,还有一些辅助函数,例如:

func (app *Application) GetUsernArn() string {
	return app.GetArn(User)
}

等等。

现在,在每个Lambda函数中,我的启动代码类似这样:

type LambdaApp struct {
	*lu.Application
}

func (app *LambdaApp) Start() {
	lambda.Start(app.handler)
}

func main() {
	lambdaClient := awsutils.NewLambdaClient()
	serviceArns := []lu.ServiceArn{lu.XYZ, lu.ABC}
	otherEnvs := []string{"WHATEVER"}

	newApp := LambdaApp{lu.NewApplication(lambdaClient, serviceArns, otherEnvs)}
	newApp.Start()
}

func (app *LambdaApp) handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	app.Log = logger.GetApiLogger(ctx, request)
	app.Ctx = ctx
...
}

通过这种方式,我可以集中配置该Lambda函数将调用哪些服务以及需要获取哪些其他环境变量。

如你所见,日志记录器和上下文(ctx)也包含在LambdaApp中。我使用的日志记录器也是一个自定义方法,可以显示requestId、路径、方法和请求体。

至于上下文(ctx),我总是使用处理程序传入的那个,不需要使用其他具有特定超时设置的上下文。

当然,后续的代码不会只有一个庞大的处理函数,我会将代码拆分。大部分代码都需要使用日志记录器,为了避免每次都声明,我直接使用app中的这个日志记录器,例如:

func (app *LambdaApp) validateRequest...{
	app.Log.Info().Msg("xyz")
}

我不确定这是否是一个好方法。我是否应该在文件开头的导入部分之后先声明日志记录器,然后在处理函数中设置它?对于上下文(ctx)也是同样的问题。

我从Scala世界转过来,两者完全不同。


更多关于Golang中AWS Lambda的设计模式的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中AWS Lambda的设计模式的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go语言的AWS Lambda中,你的设计模式是合理的,体现了依赖注入和结构化的思想。以下是对你当前模式的评估和示例代码:

当前模式分析

你的Application结构体封装了Lambda函数所需的共享依赖,这是Go中常见的做法。将Lambda客户端、ARN映射、环境变量和日志记录器集中管理,有利于代码组织和测试。

改进建议和示例

1. 日志记录器处理

你可以在初始化时创建基础日志记录器,然后在每个请求中创建带有请求上下文的子记录器:

type LambdaApp struct {
    *lu.Application
    baseLog zerolog.Logger
}

func (app *LambdaApp) handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    // 为每个请求创建带上下文的日志记录器
    requestLog := app.baseLog.With().
        Str("requestId", request.RequestContext.RequestID).
        Str("path", request.Path).
        Str("method", request.HTTPMethod).
        Logger()
    
    // 将请求特定的日志记录器传递给业务逻辑
    return app.handleBusinessLogic(ctx, request, requestLog)
}

func (app *LambdaApp) handleBusinessLogic(ctx context.Context, request events.APIGatewayProxyRequest, log zerolog.Logger) (events.APIGatewayProxyResponse, error) {
    log.Info().Msg("处理请求")
    // 业务逻辑代码
    return events.APIGatewayProxyResponse{
        StatusCode: 200,
        Body:       "成功",
    }, nil
}

2. 上下文传递模式

避免在结构体中存储上下文,而是作为参数传递:

func (app *LambdaApp) validateRequest(ctx context.Context, log zerolog.Logger, request events.APIGatewayProxyRequest) error {
    log.Info().Msg("验证请求")
    
    // 使用ctx进行超时控制或取消
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
        // 正常处理
    }
    
    // 验证逻辑
    if request.Body == "" {
        return errors.New("请求体为空")
    }
    return nil
}

3. 完整的初始化示例

type LambdaApp struct {
    LambdaClient awsutils.LambdaClientInterface
    Arn          map[lu.ServiceArn]string
    Config       map[string]string
    BaseLogger   zerolog.Logger
}

func NewLambdaApp(lambdaClient awsutils.LambdaClientInterface, serviceArns []lu.ServiceArn, envs []string) *LambdaApp {
    app := &LambdaApp{
        LambdaClient: lambdaClient,
        BaseLogger:   zerolog.New(os.Stdout).With().Timestamp().Logger(),
    }
    
    // 初始化ARN映射
    app.Arn = make(map[lu.ServiceArn]string)
    for _, arn := range serviceArns {
        app.Arn[arn] = os.Getenv(string(arn))
    }
    
    // 初始化环境变量
    app.Config = make(map[string]string)
    for _, env := range envs {
        app.Config[env] = os.Getenv(env)
    }
    
    return app
}

func (app *LambdaApp) Start() {
    lambda.Start(app.handler)
}

func (app *LambdaApp) handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    // 创建请求特定的日志记录器
    requestLogger := app.BaseLogger.With().
        Str("requestId", request.RequestContext.RequestID).
        Str("path", request.Path).
        Logger()
    
    // 调用其他Lambda的示例
    if app.LambdaClient != nil {
        payload := []byte(`{"action": "process"}`)
        _, err := app.LambdaClient.Invoke(ctx, &lambda.InvokeInput{
            FunctionName:   aws.String(app.Arn[lu.XYZ]),
            Payload:        payload,
            InvocationType: aws.String("RequestResponse"),
        })
        if err != nil {
            requestLogger.Error().Err(err).Msg("调用Lambda失败")
            return events.APIGatewayProxyResponse{
                StatusCode: 500,
                Body:       "内部错误",
            }, nil
        }
    }
    
    // 执行业务逻辑
    result, err := app.processRequest(ctx, requestLogger, request)
    if err != nil {
        return events.APIGatewayProxyResponse{
            StatusCode: 400,
            Body:       err.Error(),
        }, nil
    }
    
    return events.APIGatewayProxyResponse{
        StatusCode: 200,
        Body:       result,
    }, nil
}

func (app *LambdaApp) processRequest(ctx context.Context, log zerolog.Logger, request events.APIGatewayProxyRequest) (string, error) {
    log.Info().Msg("处理请求开始")
    
    // 使用配置
    whatever := app.Config["WHATEVER"]
    log.Debug().Str("whatever", whatever).Msg("配置值")
    
    // 业务逻辑
    return "处理完成", nil
}

func main() {
    lambdaClient := awsutils.NewLambdaClient()
    serviceArns := []lu.ServiceArn{lu.XYZ, lu.ABC}
    otherEnvs := []string{"WHATEVER", "ANOTHER_ENV"}
    
    app := NewLambdaApp(lambdaClient, serviceArns, otherEnvs)
    app.Start()
}

4. 中间件模式

考虑使用中间件来处理横切关注点:

type HandlerFunc func(context.Context, events.APIGatewayProxyRequest, zerolog.Logger) (events.APIGatewayProxyResponse, error)

func (app *LambdaApp) withLogging(next HandlerFunc) HandlerFunc {
    return func(ctx context.Context, request events.APIGatewayProxyRequest, log zerolog.Logger) (events.APIGatewayProxyResponse, error) {
        start := time.Now()
        log.Info().Msg("请求开始")
        
        response, err := next(ctx, request, log)
        
        duration := time.Since(start)
        log.Info().
            Dur("duration", duration).
            Int("status", response.StatusCode).
            Msg("请求完成")
        
        return response, err
    }
}

func (app *LambdaApp) handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    log := app.BaseLogger.With().
        Str("requestId", request.RequestContext.RequestID).
        Logger()
    
    handler := app.withLogging(app.handleBusinessLogic)
    return handler(ctx, request, log)
}

你的当前设计已经走在正确的道路上,主要改进点在于避免在结构体中存储请求特定的数据(如上下文),而是通过参数传递。这样可以避免并发问题,并使代码更清晰。

回到顶部