【问题标题】:Go and Gin: Passing around struct for database context?Go and Gin:为数据库上下文传递结构?
【发布时间】:2016-06-10 21:58:22
【问题描述】:

我刚刚开始尝试 Go,我希望用它重新实现一个用 node 编写的 API 服务器。

我在尝试使用依赖注入将数据库上下文作为 gin 中间件传递时遇到了障碍。到目前为止,我已将其设置为:

main.go:

package main

import (
        "fmt"
        "runtime"
        "log"
        "github.com/gin-gonic/gin"
        "votesforschools.com/api/public"
        "votesforschools.com/api/models"
)

type DB struct {
        models.DataStore
}

func main() {
        ConfigRuntime()
        ConfigServer()
}

func Database(connectionString string) gin.HandlerFunc {
        dbInstance, err := models.NewDB(connectionString)
        if err != nil {
                log.Panic(err)
        }

        db := &DB{dbInstance}

        return func(c *gin.Context) {
                c.Set("DB", db)
                c.Next()
        }
}


func ConfigRuntime() {
        nuCPU := runtime.NumCPU()
        runtime.GOMAXPROCS(nuCPU)
        fmt.Printf("Running with %d CPUs\n", nuCPU)
}

func ConfigServer() {

        gin.SetMode(gin.ReleaseMode)

        router := gin.New()
        router.Use(Database("<connectionstring>"))
        router.GET("/public/current-vote-pack", public.GetCurrentVotePack)
        router.Run(":1000")
}

models/db.go

package models

import (
        "database/sql"
        _ "github.com/go-sql-driver/mysql"
)

type DataStore interface {
        GetVotePack(id string) (*VotePack, error)
}

type DB struct {
        *sql.DB
}

func NewDB(dataSource string) (*DB, error) {
        db, err := sql.Open("mysql", dataSource)
        if err != nil {
                return nil, err
        }
        if err = db.Ping(); err != nil {
                return nil, err
        }
        return &DB{db}, nil
}

models/votepack.go

package models

import (
        "time"
        "database/sql"
)

type VotePack struct {
        id string
        question string
        description string
        startDate time.Time
        endDate time.Time
        thankYou string
        curriculum []string
}

func (db *DB) GetVotePack(id string) (*VotePack, error) {

        var votePack *VotePack

        err := db.QueryRow(
                "SELECT id, question, description, start_date AS startDate, end_date AS endDate, thank_you AS thankYou, curriculum WHERE id = ?", id).Scan(
                &votePack.id, &votePack.question, &votePack.description, &votePack.startDate, &votePack.endDate, &votePack.thankYou, &votePack.curriculum)

        switch {
        case err == sql.ErrNoRows:
                return nil, err
        case err != nil:
                return nil, err
         default:
                return votePack, nil
        }
}

因此,对于以上所有内容,我想将 models.DataSource 作为中间件传递,以便可以像这样访问它:

public/public.go

package public

import (
        "github.com/gin-gonic/gin"
)

func GetCurrentVotePack(context *gin.Context) {
        db := context.Keys["DB"]

        votePack, err := db.GetVotePack("c5039ecd-e774-4c19-a2b9-600c2134784d")
        if err != nil{
                context.String(404, "Votepack Not Found")
        }
        context.JSON(200, votePack)
}

但是我得到public\public.go:10: db.GetVotePack undefined (type interface {} is interface with no methods)

当我在调试器中检查时(使用带有插件的 Webstorm),db 只是一个空对象。我正在努力做好并避免使用全局变量

【问题讨论】:

    标签: go dependency-injection go-gin


    【解决方案1】:

    我认为context 不应该用作 DI 容器:https://golang.org/pkg/context/

    包上下文定义了上下文类型,它携带截止日期、取消信号和其他跨 API 边界和进程之间的请求范围值。

    我宁愿使用:

    package public
    
    type PublicController struct {
            Database *DB
    }
    
    func (c *PublicController) GetCurrentVotePack(context *gin.Context) {
            votePack, err := c.Database.GetVotePack("c5039ecd-e774-4c19-a2b9-600c2134784d")
            if err != nil{
                    context.String(404, "Votepack Not Found")
            }
            context.JSON(200, votePack)
    }
    

    并在 main 中配置您的控制器:

    func main() {
            pCtrl := PublicController { Database: models.NewDB("<connectionstring>") }
    
            router := gin.New()
            router.GET("/public/current-vote-pack", pCtrl.GetCurrentVotePack)
            router.Run(":1000")
    }
    

    【讨论】:

      【解决方案2】:

      context.Keys 中的值都是interface{} 类型,因此db 将无法调用来自*DB 类型的方法,直到它转换回该类型。

      安全的方法:

      db, ok := context.Keys["DB"].(*DB)
      if !ok {
              //Handle case of no *DB instance
      }
      // db is now a *DB value
      

      不太安全的方法,如果context.Keys["DB"] 不是*DB 类型的值,则会出现恐慌:

      db := context.Keys["DB"].(*DB)
      // db is now a *DB value
      

      Effective Go 有一个关于此的部分。

      【讨论】:

      • 虽然这解决了问题,但对我来说它看起来是非常糟糕的设计。依赖于将 interface{} 结构放在通用上下文中的人看起来不像是正确的强类型设计。我猜大部分的错误是因为 gin 需要一个函数来处理句柄方法而不是一个接口
      • @SnoProblem 是通过 Context 传递数据库连接的好方法吗?有没有其他办法&?
      【解决方案3】:

      您需要类型断言来将接口 (db := context.Keys["DB"]) 转换为有用的东西。例如看这篇文章:convert interface{} to int in Golang

      【讨论】:

        【解决方案4】:

        在启动期间将 DB 设置为上下文时,还有另一种方法。

         db := ctx.MustGet("DB").(*gorm.DB)
        

        MustGet 返回给定键的值(如果存在),否则会发生混乱。

        【讨论】:

          猜你喜欢
          • 2021-09-13
          • 1970-01-01
          • 2021-06-17
          • 1970-01-01
          • 1970-01-01
          • 2015-09-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多