go - 用Go编写的lambda API的设计

标签 go design-patterns api-design serverless go-gorm

我需要帮助设计用go编写的API。这就是文件结构:

database/
 database.go
middlewares/
 authentication.go
models/
 pageview
services/
 pageviews/
   create/
     main.go
   show/
     main.go
   serverless.yml

现在我只有页面浏览服务。
让我向您展示负责创建页面视图(services/pageview s/create/main.go)的处理程序中的内容:
package main

import (
    "context"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/clickhound/api/models"
)

func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    var pageviews models.Pageview
    if err := pageviews.Create(request.Body); err != nil {
        return events.APIGatewayProxyResponse{}, err
    }

    return events.APIGatewayProxyResponse{
        StatusCode: 201,
    }, nil
}

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

如您所见,请求处理程序(或控制器)负责将资源的创建委托给模型,让我们看看pageview模型中的内容:
package models

import (
    "encoding/json"
)

type Pageview struct {
    ID       string
    Hostname string `gorm:"not null"`
}

func (p *Pageview) Create(data string) error {
    if err := json.Unmarshal([]byte(data), p); err != nil {
        return err
    }

    // TODO validate error here.
    db.Create(p)
    return nil
}

因此,模型负责:
取消标记请求正文
创建新资源
当我需要将数据返回到控制器时,这就开始变得混乱,假设我有一个Findpageview。这是请求处理程序(或控制器):
package main

import (
    "context"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/clickhound/api/middlewares"
    "github.com/clickhound/api/models"
)

func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    var pageview models.Pageview
    data, err := pageview.Find(request.PathParameters["id"])
    if err != nil {
        return events.APIGatewayProxyResponse{}, err
    }

    return events.APIGatewayProxyResponse{
        StatusCode: 200,
        Body:       string(data),
    }, nil
}

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

模型的功能是:
func (p *Pageview) Find(id string) ([]byte, error) {
    p.ID = id

    // TODO validate error here.
    db.Find(p)
    return json.Marshal(p)
}

在这种情况下,模型负责:
查找资源
将资源封送到JSON
正如您所看到的,模型既负责持久性逻辑,也返回控制器完成其工作所需的响应——我感觉有些东西放错了地方,但为什么要这样做?
我将介绍身份验证,并且模型上的一些操作(如find pageview)应该仅限于当前用户。为了实现这一点,我将使用一个将当前用户注入模型名称空间的Find中间件:
package middlewares

import (
    "context"

    "github.com/aws/aws-lambda-go/events"
    "github.com/clickhound/api/models"
)

func Authentication(next MiddlewareSignature) MiddlewareSignature {
    return func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
        claims := request.RequestContext.Authorizer["claims"]

        if models.InjectUser(claims).RecordNotFound() {
            return events.APIGatewayProxyResponse{StatusCode: 401}, nil
        }

        return next(ctx, request)
    }
}

在用户模型中:
package models

import (
    "time"

    "github.com/jinzhu/gorm"
    "github.com/mitchellh/mapstructure"
)


type User struct {
    ID        string `gorm:"not null"`
    Email     string `gorm:"not null;unique"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

func InjectUser(claims interface{}) *gorm.DB {
    if err := mapstructure.Decode(claims, user); err != nil {
        panic(err)
    }
    return db.Find(&user)
}

var user User

现在,任何需要执行仅限于当前用户的操作的请求处理程序(控制器),我都可以更改:
func main() {
    lambda.Start(middlewares.Authentication(Handler))
}

到:
func main() {
    lambda.Start(
        middlewares.Authentication(Handler),
    )
}

一些问题:
您对在模型的名称空间中注入用户有什么看法?
您认为使用请求处理程序(控制器)只调用正确的函数如何?
您对负责持久性逻辑、验证数据库操作、对请求/响应数据进行编组/解编组的模型有什么看法?

最佳答案

最好使用一些模块将业务逻辑与传输细节隔离开来。它只是两个不同的抽象层次,如果我们不混合它们,代码就会变得更干净。尽管如此,它应该是实用的,我们可以保留HTTP代码,因为它们现在是通用语言,并且如果您的业务逻辑返回500和400来处理不同类型的错误,也没有什么错。
如果我编写这段代码,那么这种分离将是控制器的主要目标。
业务逻辑层(模型)应该与建模业务域的强类型对象一起工作,而不需要了解HTTP或AWS lambda实现的详细信息。
要处理的控制器:
路由
API版本控制
序列化/反序列化,包括URL参数、头、AWS lambda特定字段等
模型:
接收并返回强类型对象和错误
验证输入(根据框架,这可以部分移动到控制器)
IO,包括为身份验证和业务事务按ID加载用户

关于go - 用Go编写的lambda API的设计,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53582834/

相关文章:

go - 从 go.mod 手动获取依赖项?

objective-c - 使用委托(delegate)将数组从一个类传递到另一个类 - 最佳设计模式?

design-patterns - builder 设计模式

rest - 使用 REST 获取多个资源(批量获取)的正确方法是什么?

google-app-engine - 正确的 GOPATH 包含来自 App Engine SDK 的 App Engine 库?

process - golang 重新启动的父进程没有收到 SIGINT

algorithm - 在不重复逻辑的情况下对项目进行分组

api - 如何定义 API 的好坏?

authentication - PWA 的 API 身份验证

go - 如何在运行时添加命令,或定义未知命令的行为?