从go-chi路由处理程序访问DB实例

huangapple go评论86阅读模式
英文:

Access DB instance from go-chi route handlers

问题

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

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

或者我是否应该为每个处理程序创建一个实例,但这对我来说听起来不太对。

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

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

	//我如何在这里访问db?
	//result := db.Find(&users)

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

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

I am trying to build a REST API with go-chi and Gorm.

I am not sure how I should pass the Gorm DB instance to the route handlers.

Or if I should create one instance per handler, which does not sound right to me.

Should I use middleware, dependency injection or other? What would be recommended pattern here?

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

答案1

得分: 8

使用方法而不是函数。这样可以通过这些方法的接收者传递处理程序所需的任何信息:

type MyHandler struct {
  DB *gorm.DB
}

func (m MyHandler) IndexHandler(w http.ResponseWriter, r *http.Request) {
  // 在这里使用 m.DB
}

在主函数中:

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) {
     // 在这里实现索引处理程序,使用 db
   }
}

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

Use methods instead of functions. This allows you to pass any information needed by the handlers using the receiver of those methods:

type MyHandler struct {
  DB *gorm.DB
}

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

In main:

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

In some cases, a closure makes more sense.

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

答案2

得分: 1

将DB实例声明为全局变量在项目规模较小时非常方便。

有很多种方法可以很好地组织数据库访问,这里有详细记录。选择适合你需求的方法。

英文:

Declaring the DB instance as a global variable is quite convenient if your project is small.

A number of ways for organising DB access are documented here quite well. Pick the one which fits your needs.

答案3

得分: -1

在DB/query函数本身中,我个人会为控制器创建一个单独的包,为服务创建一个单独的包。我在控制器中处理所有的请求验证和HTTP相关的内容(包括处理函数)。如果一切都符合要求,我会调用一个服务包。服务包负责调用DB以及其他服务或API集成。

无论在哪里调用DB,通常你会调用一个名为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是包级别的DB连接对象
  }      
  //...
}

通常,在服务器启动时,我会创建DB连接(作为连接池),所以在函数中传递它并不是必需的。那为什么要这样做呢?这是因为测试的原因。你不希望DB处理程序直接访问包级别的DB连接对象,因为这样会更难对该函数进行独立测试。

因此,这个函数签名提供了可测试性,而初始的if条件仍然使用了单一的中央DB连接对象,如果传入的DB值为nil,则始终为nil,除非你在进行测试。

这只是一种方法,但是我多年来一直成功使用的方法。

英文:

In the DB/query function itself. I personally make a separate package for controllers and a separate package for services. I handle all the request validation and HTTP stuff in the controller (which has my handler functions). Then, if everything checks out, I call a service package. The service package is the one that calls the DB as well as any other services or API integrations.

Yet, where ever you call the DB, usually you are calling into a db package that has a bunch of query functions with friendly names like db.GetAccountByID or something like that. Well, that db function is exactly where you pass the *sql.DB or *gorm.DB object.

An example would be...

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
  }      
  //...
}

Generally, when the server starts, I create the DB connection (which functions as a connection pool) and so it's not really necessary to pass it into the function. So, why do it? Well, it's because of testing. You don't want your DB handler reaching out to a package level DB connection object because it becomes more difficult to do isolated testing of that function.

So, this function signature gives you that testability and the initial if condition still uses that single central DB connection object if nil is passed in for the DB value, which is always is nil unless you are testing.

This is just one approach but one I've used successfully for years now.

huangapple
  • 本文由 发表于 2021年7月1日 02:27:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/68199922.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定