使用mux路由器时,如何将我的数据库传递给处理程序?

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

Go using mux Router - How to pass my DB to my handlers

问题

目前,我正在尝试使用Go创建一个小型Web项目,用于在服务器上处理数据。

我试图将我的数据库连接传递给我的HandlerFunc函数,但它并没有按预期工作。我对golang还很陌生,所以可能我没有理解这种语言的一些基本原则。

我的main函数如下所示:

func main() {

    db, err := config.NewDB("username:password@/databasename?charset=utf8&parseTime=True")
    if err != nil {
        log.Panic(err)
    }   
    env := &config.Env{DB: db} 

    router := NewRouter(env)
    log.Fatal(http.ListenAndServe(":8080", router))
}

我的路由器:

func NewRouter(env *config.Env) *mux.Router {
    router := mux.NewRouter().StrictSlash(true)
    for _, route := range routes {
        var handler http.Handler

        handler = route.HandlerFunc
        handler = Logger(handler, route.Name)

        router.
            Methods(route.Method).
            Path(route.Pattern).
            Name(route.Name).
            Handler(handler)
    }   
    return router
}

以及我的路由:

type Route struct {
    Name        string
    Method      string
    Pattern     string
    HandlerFunc http.HandlerFunc
}

type Routes []Route

var routes = Routes{
    Route{
        "Index",
        "GET",
        "/",
        controller.Index,
    },  
    Route{
        "Show",
        "GET",
        "/todos/{todoId}",
        controller.TodoShow,
    },  
    Route{
        "Create",
        "POST",
        "/todos",
        controller.TodoCreate,
    },  
}

那么,我该如何将"env"(或env.DB)传递给我的FuncHandlers?我尝试了很多方法,但它们都没有起作用。

英文:

At the moment, I try to create a small Web-Project using Go for data handling on the server.

I try to pass my database-connection to my HandlerFunc(tions) but it does not work as expected. I am pretty new to golang, so maybe I did not understand some basic principles of this lang.

My main func looks like this:

func main() {

    db, err := config.NewDB("username:password@/databasename?charset=utf8&parseTime=True")
    if err != nil {
        log.Panic(err)
    }   
    env := &config.Env{DB: db} 

    router := NewRouter(env)
    log.Fatal(http.ListenAndServe(":8080", router))
}

My Router:

func NewRouter(env *config.Env) *mux.Router {
    router := mux.NewRouter().StrictSlash(true)
    for _, route := range routes {
        var handler http.Handler

        handler = route.HandlerFunc
        handler = Logger(handler, route.Name)

        router.
            Methods(route.Method).
            Path(route.Pattern).
            Name(route.Name).
            Handler(handler)
    }   
    return router
}

and my routes:

type Route struct {
    Name        string
    Method      string
    Pattern     string
    HandlerFunc http.HandlerFunc
}

type Routes []Route

var routes = Routes{
    Route{
        "Index",
        "GET",
        "/",
        controller.Index,
    },  
    Route{
        "Show",
        "GET",
        "/todos/{todoId}",
        controller.TodoShow,
    },  
    Route{
        "Create",
        "POST",
        "/todos",
        controller.TodoCreate,
    },  
}

So - how can I pass my "env" (or env.DB) to my FuncHandlers? I tried many things, but none of them worked.

答案1

得分: 35

你有三个选项:

  1. 将数据库连接池设为全局变量,这样就不需要传递它了。sql.DB 对并发访问是安全的,这是最简单的方法。缺点是会增加测试的难度,并且会使得连接池的来源不明显,例如:

     var db *sql.DB
    
     func main() {
         var err error
         db, err = sql.Open(...)
         // 现在可以在全局范围内访问,不需要传递它
         // ...
      }
    
  2. 将处理程序包装在闭包中,这样它就可以在内部处理程序中访问。你需要根据你的路由遍历方法进行适应,这种方法有点晦涩,而且使得查看存在的路由更加困难,但我偏离了主题,例如:

     func SomeHandler(db *sql.DB) http.HandlerFunc {
         fn := func(w http.ResponseWriter, r *http.Request) {
             res, err := db.GetThings()
             // 等等。
         }
    
         return http.HandlerFunc(fn)
     }
    
     func main() {
         db, err := sql.Open(...)
         http.HandleFunc("/some-route", SomeHandler(db))
         // 等等。
     }
    
  3. 创建一个接受处理程序的自定义处理程序类型,例如:

     type AppHandler struct {
         Handler func(env *config.Env, w http.ResponseWriter, r *http.Request)
         Env *config.Env
     }
    
     // ServeHTTP 允许你的类型满足 http.Handler 接口。
     func (ah *AppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
         ah.Handler(ah.Env, w, r)
     }
    
     func SomeHandler(env *config.Env, w http.ResponseWriter, r *http.Request) {
         res, err := env.DB.GetThings()
         // 等等。
     }
    

请注意(厚颜无耻地推销!)我已经详细介绍了最后一种方法,Alex Edwards 在他的博客文章中也介绍了在 Go 程序中访问数据库连接池的方法。

我唯一的严格建议是避免在请求上下文中传递数据库连接池,这是低效且不良的做法(请求上下文是用于临时的、每个请求的对象)。

英文:

You have three options:

  1. Make your database connection pool a global, so that you don't have to pass it. sql.DB is safe for concurrent access, and this is the easiest approach. The downside is that it makes testing harder and obfuscates "where" the pool is coming from - e.g.

     var db *sql.DB
    
     func main() {
         var err error
         db, err = sql.Open(...)
         // Now accessible globally, no need to pass it around
         // ...
      }
    
  2. Wrap your handlers in a closure, which makes it accessible to the inner handler. You'll need to adapt this to your range-over-routes approach—which is a little obtuse IMO, and makes it harder to see what routes exist, but I digress—for example:

     func SomeHandler(db *sql.DB) http.HandlerFunc {
         fn := func(w http.ResponseWriter, r *http.Request) {
             res, err := db.GetThings()
             // etc.
         }
    
         return http.HandlerFunc(fn)
     }
    
     func main() {
         db, err := sql.Open(...)
         http.HandleFunc("/some-route", SomeHandler(db))
         // etc.
     }
    
  3. Create a custom handler type that accepts a handler - e.g.

     type AppHandler struct {
         Handler func(env *config.Env, w http.ResponseWriter, r *http.Request)
         Env *config.Env
     }
    
     // ServeHTTP allows your type to satisfy the http.Handler interface.
     func (ah *AppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
         ah.Handler(ah.Env, w, r)
     }
    
     func SomeHandler(env *config.Env, w http.ResponseWriter, r *http.Request) {
         res, err := env.DB.GetThings()
         // etc.
     }
    

Note that (shameless plug!) I've written about the last approach in detail, and Alex Edwards has an excellent blog post on approaches to accessing DB pools in Go programs as well.

The only strict advice I can give is that you should shy away from passing your DB pool around in a request context, which is inefficient and not good practice (request contexts are for temporary, per-request objects).

答案2

得分: 1

你可以始终将"env"定义为全局变量

但在大家讨厌我之前,这不是一个好的解决方案!你应该创建一个封装了对数据库访问的包,其中包含公共函数来说明你的确切意图。

类似于以下内容:

包 db

var config ....

func ShowTodos(params ... ) result {
   在这里编写你的数据库访问代码.... 
}

然后从你的路由函数中访问它:

db.ShowTodos(...)
英文:

You can always have "env" defined as global variable.

But before everyone will hate me, this is not a good solution! You should create a package that encapsulate the access to your database with public function that state your exact intent.

Something along the lines of

Package db

var config ....

func ShowTodos(params ... ) result {
   your database access code here.... 
}

and from your router function access it with

db.ShowTodos(...)

huangapple
  • 本文由 发表于 2015年11月11日 16:46:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/33646948.html
匿名

发表评论

匿名网友

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

确定