将全局定义的数据库连接与多个包共享

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

Sharing a globally defined db conn with multiple packages

问题

我已经阅读了一些关于如何处理数据库连接的StackOverflow答案。由于它是一个连接池,我们可以在全局范围内定义它,并在多个goroutine中使用它,这是安全的。

我遇到的问题是,我将我的REST API拆分成多个包。每个包都需要一个数据库连接,所以我在启动时打开一个数据库连接。但是,即使我在全局范围内定义了连接,它也只在包级别上有效。我该如何在多个包之间共享它呢?

为了提供一些背景,我在我的应用程序中使用了PostgreSQL驱动程序和gin-gonic。

英文:

I've read a few StackOverflow answers on how we handling the db connection. Since it's a pool, we can define it globally and use it in multiple goroutines and it's safe.

The issue I'm having is that I have split my REST API into multiple packages. Each of these packages require a db connection, so I open a database connection in the startup. But even if I define the connection globally, it's only at the package level. What can I do to potentially share it among multiple packages?

For some context I'm using the PostgreSQL driver and gin-gonic in my application.

答案1

得分: 97

还有一种选择是创建另一个包来保存与数据库连接相关的设置。然后可以在main函数中初始化一个包级别的全局变量,并在导入该包的任何包中使用。

这样,你可以明确地看到数据库包被导入了。以下是一些示例代码:

package database

var (
    // DBCon 是数据库的连接句柄
    DBCon *sql.DB
)
package main

import "myApp/database"

func main() {

    var err error
    database.DBCon, err = sql.Open("postgres", "user=myname dbname=dbname sslmode=disable")

}
package user

import "myApp/database"

func Index() {
    // 数据库句柄在这里可用
    database.DBCon

    ...
}
英文:

There is also the option of creating another package to hold your database connection-related settings. It can then have a package level global, which can be initialized in main and used in any package that is importing it.

This way, you can explicitly see that the database package is being imported. Here is some sample code.

package database

var (
	// DBCon is the connection handle
	// for the database
	DBCon *sql.DB
)

package main

import "myApp/database"

func main() {

	var err error
	database.DBCon, err = sql.Open("postgres", "user=myname dbname=dbname sslmode=disable")

}

package user

import "myApp/database"

func Index() {
    // database handle is available here
	database.DBCon

	...
}

答案2

得分: 15

简单回答:将一个初始化的连接池传递给你的包的全局变量。

例如:

// package stuff

var DB *sql.DB

func GetAllStuff() (*Stuff, error) {
err := DB.Query("...")
// 等等。
}

// package things

var DB *sql.DB

func GetAllThings() (*Thing, error) {
err := DB.Query("...")
// 等等。
}

// package main

func main() {
db, err := sql.Open("...")
if err != nil {
log.Fatal(err)
}

stuff.DB = db
things.DB = db

// 等等。

}

我们定义了包级别的全局变量,确保它们被导出(大写),然后将连接池的指针传递给它们。

这样做是“可以的”,但可能会掩盖“连接”来自哪里的问题,特别是当你的包变得越来越大时。一个更可扩展的方法可能如下所示:

// package stuff

type DB struct {
*sql.DB
}

func New(db *sql.DB) (*DB, error) {
// 配置任何包级别的设置
return &DB{db}, nil
}

func (db *DB) GetAllStuff() (*Stuff, error) {
err := db.Query("...")
// 等等。
}

// package things

type DB struct {
*sql.DB
}

func New(db *sql.DB) (*DB, error) {
// 配置任何包级别的设置
return &DB{db}, nil
}

func (db *DB) GetAllThings() (*Thing, error) {
err := db.Query("...")
// 等等。
}

// package main

func main() {
db, err := sql.Open("...")
if err != nil {
log.Fatal(err)
}

stuffDB, err := stuff.New(db)
if err != nil {
    log.Fatal(err)
}

thingsDB, err := things.New(db)
if err != nil {
    log.Fatal(err)
}

// 简化。
http.HandleFunc("/stuff/all", stuff.ShowStuffHandler(stuffDB))
http.HandleFunc("/things/all", things.ShowThingsHandler(thingsDB))

// 等等。

}

func ShowStuffHandler(db *stuff.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 我们可以在这里使用 stuff.DB
stuff, err := db.GetAllStuff()
// 等等。
}
}

如果除了数据库连接之外还有其他依赖项(例如配置参数、主机名等),可以将它们包装在每个包的 things.Env 结构或 stuff.Env 结构中。

一个示例是为每个包都有一个 things.New("deps...") *Env 函数,该函数返回一个配置好的 *things.Env,其中包含 things 包使用的依赖项。

英文:

Simple answer: pass an initialised connection pool to your packages' own globals.

e.g.

// package stuff

var DB *sql.DB

func GetAllStuff() (*Stuff, error) {
    err := DB.Query("...")
    // etc.
}

// package things

var DB *sql.DB

func GetAllThings() (*Thing, error) {
    err := DB.Query("...")
    // etc.
}

// package main

func main() {
    db, err := sql.Open("...")
    if err != nil {
        log.Fatal(err)
    }
    
    stuff.DB = db
    things.DB = db
    
    // etc.
}

We define package-level globals, make sure they're exported (capitalised) and then pass a pointer to our connection pool to them.

This is "okay", but can mask "where" things are being used. If you're looking at a handler it may not be clear where the connection is coming from, especially as your package grows. A more scalable approach might look like the below:

// package stuff

type DB struct {
    *sql.DB
}

func New(db *sql.DB) (*DB, error) {
    // Configure any package-level settings
    return &DB{db}, nil
}

func (db *DB) GetAllStuff() (*Stuff, error) {
    err := db.Query("...")
    // etc.
}

// package things

type DB struct {
    *sql.DB
}

func New(db *sql.DB) (*DB, error) {
    // Configure any package-level settings
    return &DB{db}, nil
}

func (db *DB) GetAllThings() (*Thing, error) {
    err := db.Query("...")
    // etc.
}

// package main

func main() {
    db, err := sql.Open("...")
    if err != nil {
        log.Fatal(err)
    }
    
    stuffDB, err := stuff.New(db)
    if err != nil {
        log.Fatal(err)
    }
    
    thingsDB, err := things.New(db)
    if err != nil {
        log.Fatal(err)
    }
    
    // Simplified.
    http.HandleFunc("/stuff/all", stuff.ShowStuffHandler(stuffDB))
    http.HandleFunc("/things/all", things.ShowThingsHandler(thingsDB))

    // etc.
}

func ShowStuffHandler(db *stuff.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // We can use our stuff.DB here
        stuff, err := db.GetAllStuff()
        // etc.
    }
}

If you have more than just the DB connection as a dependency (e.g. config params, hostnames, etc.) , wrap them in an things.Env struct or a stuff.Env struct for each package.

An example would be to have a things.New("deps...") *Env function that returns a configured *things.Env that contains the dependencies used by your things package.

答案3

得分: 1

更新

我最终使用package config来初始化数据库,在应用程序启动时进行。这样做非常好,因为你不需要向函数传递任何不需要的参数。


原文

我对Go语言还不太熟悉,但我正在处理同样的问题,通过在主包中声明局部变量来解决,对我来说是package app

var mongoClient *mongo.Client
var mongoCtx context.Context
var mongoCancelCtx context.CancelFunc

然后,我将它们传递给package config中的一个数据库文件,以连接到数据库,并使用传递的指针来分配结果。

# app/app.go

var mongoClient *mongo.Client
var mongoCtx context.Context
var mongoCancelCtx context.CancelFunc
config.BootstrapDatabase(&mongoClient, &mongoCtx, &mongoCancelCtx)
# app/config/database.go

// BootstrapDatabase ...
func BootstrapDatabase(mongoClient **mongo.Client, ctx *context.Context, cancel *context.CancelFunc) {
	*ctx, *cancel = context.WithTimeout(context.Background(), 10*time.Second)

	client, err := mongo.Connect(*ctx, options.Client().ApplyURI(os.Getenv("MONGO_URI")))
	if err != nil {
		panic(err)
	}

	if err := client.Ping(*ctx, readpref.Primary()); err != nil {
		panic(err)
	}

	*mongoClient = client

	fmt.Println("Successfully connected and pinged.")
}

然后,我创建一个数据库引用,并将其传递给每个将使用它的包。


# app/app.go

db := mongoClient.Database(os.Getenv("MONGO_DATABASE"))
repositories.BootstrapRepositories(db)

e := echo.New()
routes.BootstrapRoutes(e, db)

因此,我的想法是在高级包中声明数据库引用,即我的app/app.go,并根据需要将其传递下去,例如在我的package routes中。

#app/app.go


// BootstrapRoutes ...
func BootstrapRoutes(e *echo.Echo, db *mongo.Database) {

	// 设置中间件
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	// 用户相关路由
	e.POST("/api/users", controllers.CreateUser)

	// 启动Web服务器
	e.Logger.Fatal(e.Start(":4242"))
}

我还在进行实验,还不知道这种方法是否适用于许多端点,或者是否需要尝试将其分离为自己的包,就像在此问题的第一个线程中建议的那样。

请分享你的想法,我很乐意听取你的意见。

英文:

Update

I've ended up using the package config to initialize database, at the start of the application, It's pretty good, as you don't have to pass any unneeded param to functions.


Original

I'm still new to the golang world, but I'm handling this same problem, by declaring
local variables in the main package, in my case its package app

var mongoClient *mongo.Client
var mongoCtx context.Context
var mongoCancelCtx context.CancelFunc

then I'm passing them to a database file inside package config to connect to the database, and use the passed pointer to assign the result.

# app/app.go

var mongoClient *mongo.Client
var mongoCtx context.Context
var mongoCancelCtx context.CancelFunc
config.BootstrapDatabase(&mongoClient, &mongoCtx, &mongoCancelCtx)
# app/config/database.go

// BootstrapDatabase ...
func BootstrapDatabase(mongoClient **mongo.Client, ctx *context.Context, cancel *context.CancelFunc) {
	*ctx, *cancel = context.WithTimeout(context.Background(), 10*time.Second)

	client, err := mongo.Connect(*ctx, options.Client().ApplyURI(os.Getenv("MONGO_URI")))
	if err != nil {
		panic(err)
	}

	if err := client.Ping(*ctx, readpref.Primary()); err != nil {
		panic(err)
	}

	*mongoClient = client

	fmt.Println("Successfully connected and pinged.")
}

Then I'm creating a database reference and passing it to each package
that will make use of it.


# app/app.go

db := mongoClient.Database(os.Getenv("MONGO_DATABASE"))
repositories.BootstrapRepositories(db)

e := echo.New()
routes.BootstrapRoutes(e, db)

So the idea is to declare the database reference in the high level package which is my app/app.go in my app and pass it down as needed, for example in my package routes

#app/app.go


// BootstrapRoutes ...
func BootstrapRoutes(e *echo.Echo, db *mongo.Database) {

	// Set middleware
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	// Users related routes
	e.POST("/api/users", controllers.CreateUser)

	// Starte web server
	e.Logger.Fatal(e.Start(":4242"))
}

I'm still experimenting with it, I don't know yet if this approach will skill
with many endpoints, or I need to try separating it into its own package, as suggested in the first thread of this issue.

Please share you thoughts I'll be happy to hear from you.

答案4

得分: 1

我正在做类似这样的事情。每个配置文件都包含自己的连接方法并导出一个全局变量。

main.go

func main() {

	var cors = handlers.CORS(
		handlers.AllowedOrigins([]string{"*"}),
		handlers.AllowedHeaders([]string{"Content-Type", "x-api-key"}),
		handlers.AllowedMethods([]string{"POST","GET", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"}),
	)

	server := &http.Server{
		Handler:      cors(apiRouter.Router),
		Addr:         "0.0.0.0:80",
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}

	if os.Getenv("STATUS") == "dev" {
		server.Addr = "0.0.0.0:3000"
	}

	// 配置
	configs.ConfigureS3()
	configs.ConnectRedis()
	configs.ConfigureDb()

	log.Fatal(server.ListenAndServe())
}

在我的configs包中有三个文件:

configs/mysql.go

var Db *sql.DB

func ConfigureDb() {
	var err error

	mysqlConfig := &mysql.Config{
		User:   os.Getenv("MYSQL_USER"),
		Net: "tcp",
		Passwd: os.Getenv("MYSQL_PASSWORD"),
		Addr:   os.Getenv("MYSQL_HOST"),
		DBName: os.Getenv("MYSQL_DATABASE"),
	}

	Db, err = sql.Open(
		"mysql",
		mysqlConfig.FormatDSN(),
	)
	if err != nil {
		panic(err)
	}

	// Db连接配置
	Db.SetConnMaxLifetime(time.Minute * 5)
	Db.SetMaxOpenConns(1000)
	Db.SetMaxIdleConns(1000) // 我必须始终将其设置为大于或等于最大打开连接数
}

configs/redis.go

var RedisPool *redis.Pool

func ConnectRedis() {
	RedisPool = &redis.Pool{
		MaxIdle:     3,
		IdleTimeout: 3 * time.Minute,
		Dial: func() (redis.Conn, error) {
			return redis.Dial(
				"tcp",
				os.Getenv("REDIS_HOST") + ":" + os.Getenv("REDIS_PORT"),
				redis.DialPassword(os.Getenv("REDIS_PASSWORD")),
			)
		},
	}
}

configs/aws.go

var S3Client *s3.Client

func ConfigureS3() {
	awsConfig, err := config.LoadDefaultConfig(context.TODO())
	if err != nil {
		panic(err)
	}
	S3Client = s3.NewFromConfig(awsConfig)
}
英文:

I am doing something like this. Each configuration file contains its own connection method and exports a global variable.

main.go

func main() {

	var cors = handlers.CORS(
		handlers.AllowedOrigins([]string{"*"}),
		handlers.AllowedHeaders([]string{"Content-Type", "x-api-key"}),
		handlers.AllowedMethods([]string{"POST","GET", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"}),
	)

	server := &http.Server{
		Handler:      cors(apiRouter.Router),
		Addr:         "0.0.0.0:80",
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}

	if os.Getenv("STATUS") == "dev" {
		server.Addr = "0.0.0.0:3000"
	}

	// configuration
	configs.ConfigureS3()
	configs.ConnectRedis()
	configs.ConfigureDb()

	log.Fatal(server.ListenAndServe())
}

and in my configs package there are three files:<br>

configs/mysql.go

var Db *sql.DB

func ConfigureDb() {
	var err error

	mysqlConfig := &amp;mysql.Config{
		User:   os.Getenv(&quot;MYSQL_USER&quot;),
		Net: &quot;tcp&quot;,
		Passwd: os.Getenv(&quot;MYSQL_PASSWORD&quot;),
		Addr:   os.Getenv(&quot;MYSQL_HOST&quot;),
		DBName: os.Getenv(&quot;MYSQL_DATABASE&quot;),
	}

	Db, err = sql.Open(
		&quot;mysql&quot;,
		mysqlConfig.FormatDSN(),
	)
	if err != nil {
		panic(err)
	}

	// Db connection configuration
	Db.SetConnMaxLifetime(time.Minute * 5)
	Db.SetMaxOpenConns(1000)
	Db.SetMaxIdleConns(1000) // I have to always set it to greater or equal to max open connection
}

configs/redis.go

var RedisPool *redis.Pool

func ConnectRedis() {
	RedisPool = &amp;redis.Pool{
		MaxIdle:     3,
		IdleTimeout: 3 * time.Minute,
		Dial: func() (redis.Conn, error) {
			return redis.Dial(
				&quot;tcp&quot;,
				os.Getenv(&quot;REDIS_HOST&quot;) + &quot;:&quot; + os.Getenv(&quot;REDIS_PORT&quot;),
				redis.DialPassword(os.Getenv(&quot;REDIS_PASSWORD&quot;)),
			)
		},
	}
}

configs/aws.go

var S3Client *s3.Client

func ConfigureS3() {
	awsConfig, err := config.LoadDefaultConfig(context.TODO())
	if err != nil {
		panic(err)
	}
	S3Client = s3.NewFromConfig(awsConfig)
}

答案5

得分: 0

要初始化一个包范围的值(在这里是一个数据库连接),只需定义一个具有包范围的变量。

init()函数中,就像在main()函数中一样,描述如何初始化该变量。

package database

var db *sql.DB

func init(){
  db, err = sql.Open("credentials")
  if err != nil{
    panic(err)
  }
}

// Getter for use everywhere else package
func DB()*sql.DB{
  return db
}

Go在导入库之前和main()函数之前自动执行init()函数。

在你的main.go文件中,导入依赖项如下:

package main

import _ "../folder/database" // init()已被执行,并初始化了db

func main(){}
英文:

For initialize a package-scoped value (here a database connection), just defined a variable with package scope.

In a init() function as main(), describe how to initialize the variable.

package database

var db *sql.DB

func init(){
  db, err = sql.Open(&quot;credentials&quot;)
  if err != nil{
    panic(err)
  }
}

// Getter for use everywhere else package
func DB()*sql.DB{
  return db
}

Go automatically execute init() function while importing the library and before main() function.

In you main.go file, import as dependancies as

package main

import _ &quot;../folder/database&quot; // init() have been executed, and initialize db

func main(){}

huangapple
  • 本文由 发表于 2015年7月4日 15:11:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/31218008.html
匿名

发表评论

匿名网友

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

确定