duanditang2916 2018-12-02 17:32
浏览 77

用Go编写的Lambda API设计

I need help with the design of my API that is written in Go. That's the file structure:

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

By now I only have the pageviews service.

Let me show you what's inside the handler responsible for creating a pageview (services/pageviews/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)
}

As you can see, the request handler (or controller) is responsible for delegating the creation of the resource to the model, let's see what it's inside the pageview model:

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
}

So, the model is responsible for:

  1. Unmarshal the request body
  2. Create the new resource

This starts to become messy when I need to return the data to the controller, let's say that I have a Find pageview. That's the request handler (or controller):

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))
}

And the models Find function:

func (p *Pageview) Find(id string) ([]byte, error) {
    p.ID = id

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

In this case, the model is responsible for:

  1. Find the resource
  2. Marshal the resource to JSON

As you can see, the model is responsible for both the persistence logic, but also return the response that the controller needs to do its job - I feel that something is misplaced, but why am I doing this?

I will introduce authentication, and some actions (like Find pageview) on the models should be limited to the current user. To achieve that, I will use an authentication middleware that injects the current user in the namespace of the models:

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)
    }
}

And in the user models:

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

Now, any request handler (controller) that needs to do the operation limited to the current user, I can change:

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

to:

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

Some questions:

  1. What do you think about injecting the user in the namespace of the models?
  2. What do you think about using the request handlers (controllers) to only call the proper function?
  3. What do you think about the models being responsible for the persistence logic, validating the database action, marshilling/unmarshalling the request/response data.
  • 写回答

1条回答 默认 最新

  • dthtvk3666 2018-12-02 18:33
    关注

    It is good ideas to use some module to isolate business logic from transport details. It is just two different levels of abstraction and code became cleaner if we do not mix them. Although, it should be pragmatic and we can keep HTTP codes as they are universal language now and there is nothing wrong if your business logic returns 500 and 400 for different kinds of errors.

    That separation would be main goal of controller if I write this code. Business logic layer (model) should work with strong type objects that model business domain and do not need to know about HTTP or AWS lambda implementation details.

    Controller to handle:

    • Routing
    • Api Versioning
    • Serialization/Deserialization including url params, headers, AWS lambda specific fields and etc

    Model:

    • Receive and Return strong type objects and errors
    • Validate input (this can be partially moved to controller depending on framework)
    • IO, including loading User by ID for authentication and business transactions
    评论

报告相同问题?

悬赏问题

  • ¥15 Oracle中如何从clob类型截取特定字符串后面的字符
  • ¥15 想通过pywinauto自动电机应用程序按钮,但是找不到应用程序按钮信息
  • ¥15 MATLAB中streamslice问题
  • ¥15 如何在炒股软件中,爬到我想看的日k线
  • ¥15 seatunnel 怎么配置Elasticsearch
  • ¥15 PSCAD安装问题 ERROR: Visual Studio 2013, 2015, 2017 or 2019 is not found in the system.
  • ¥15 (标签-MATLAB|关键词-多址)
  • ¥15 关于#MATLAB#的问题,如何解决?(相关搜索:信噪比,系统容量)
  • ¥500 52810做蓝牙接受端
  • ¥15 基于PLC的三轴机械手程序