从 go-chi 路由处理程序访问数据库实例

问题描述

我正在尝试使用 go-chi 和 Gorm 构建 REST API。

我不确定应该如何将 Gorm 数据库实例传递给路由处理程序。

或者如果我应该为每个处理程序创建一个实例,这对我来说不太合适。

我应该使用中间件、依赖注入还是其他?这里推荐的模式是什么?

package main

import (
    "encoding/json"
    "fmt"
    "github.com/go-chi/chi/v5"
    "log"
    "net/http"
    "os"
    "time"
)

func main() {
    r := chi.NewRouter()

    r.Get("/",indexHandler)


    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
        log.Printf("Defaulting to port %s",port)
    }

    db := Connect()
    migrations(db)
    logStartServer(port)
    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s",port),r))
}

func logStartServer(port string) {
    log.Printf("Listening on port %s",port)
    log.Printf("Open http://localhost:%s in the browser",port)
}

func indexHandler(w http.ResponseWriter,r *http.Request) {

    //How can I access db here?
    //result := db.Find(&users)

    policy := InsurancePolicy{ValidFrom: time.Now()}
    err := json.NewEncoder(w).Encode(policy)

    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
    }
}

解决方法

使用方法而不是函数。这允许您使用这些方法的接收器传递处理程序所需的任何信息:

type MyHandler struct {
  DB *gorm.DB
}

func (m MyHandler) IndexHandler(w http.ResponseWriter,r *http.Request) {
  // Use m.DB here
}

主要内容:

handler:=mypkg.MyHandler{DB:gormDB}
r.Get("/",handler.IndexHandler)

在某些情况下,闭包更有意义。

func GetIndexHandler(db *gorm.DB) func(http.ResponseWriter,*http.Request) {
   return func(w http.ResponseWriter,req *http.Request) {
     // Implement index handler here,using db
   }
}

func main() {
  ...
  r.Get("/",GetIndexHandler(db))
,

如果您的项目很小,将数据库实例声明为全局变量非常方便。

许多组织数据库访问的方法在 here 中有很好的记录。选择一款适合您的需求。

,

在数据库/查询函数本身中。我个人为控制器制作了一个单独的包,为服务制作了一个单独的包。我在控制器(有我的处理函数)中处理所有请求验证和 HTTP 内容。然后,如果一切正常,我会调用一个服务包。服务包是调用数据库以及任何其他服务或 API 集成的包。

然而,无论您在何处调用数据库,通常都会调用一个 db 包,该包具有一堆查询函数,名称为 db.GetAccountByID 之类的友好名称。好吧,db 函数正是您传递 *sql.DB*gorm.DB 对象的地方。

一个例子是...

package db

func GetAccountByID(id int,db *gorm.DB) (*model.Account,error) {
  if db == nil {
    db = conn // conn is the package level db connection object
  }      
  //...
}

通常,当服务器启动时,我会创建 DB 连接(用作连接池),因此实际上没有必要将其传递给函数。那么,为什么要这样做呢?嗯,这是因为测试。您不希望 DB 处理程序接触到包级别的 DB 连接对象,因为对该函数进行隔离测试变得更加困难。

因此,此函数签名为您提供了可测试性,并且如果 if 为 DB 值传入,则初始 nil 条件仍然使用该单个中央 DB 连接对象,该值始终为 {{1 }} 除非你在测试。

这只是一种方法,但我已经成功使用了多年。