在Golang中与多个包共享全局定义的db conn

时间:2015-07-04 07:11:38

标签: go

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

我遇到的问题是我已将REST API拆分为多个包。这些包中的每一个都需要数据库连接,因此我在启动时打开数据库连接。但即使我全局定义连接,它也只是在包级别。我可以做些什么来在多个包中分享它?

对于某些上下文,我在我的应用程序中使用PostgreSQL驱动程序和gin-gonic。

4 个答案:

答案 0 :(得分:69)

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

这样,您可以明确地看到正在导入数据库包。这是一些示例代码。

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

    ...
}

答案 1 :(得分:11)

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

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

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

这是“好的”,但可以屏蔽“使用”的东西。如果您正在查看处理程序,可能不清楚 连接的来源,特别是当您的包增长时。更具伸缩性的方法可能如下所示:

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

如果您不仅仅将数据库连接作为依赖项(例如config params,hostnames等),请将它们包装在每个包的things.Env结构或stuff.Env结构中。

一个示例是使用things.New("deps...") *Env函数返回已包含*things.Env包使用的依赖项的已配置things

答案 2 :(得分:0)

更新

我最终在应用程序开始时使用 package config 来初始化数据库,这非常好,因为您不必将任何不需要的参数传递给函数。


原创

我仍然是 golang 世界的新手,但我正在处理同样的问题,通过声明 主包中的局部变量,在我的例子中是 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) {

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

我还在试验它,我还不知道这种方法是否会熟练 有许多端点,或者我需要尝试将其分离到自己的包中,如本期第一个线程中所建议的那样。

请分享您的想法,我很高兴收到您的来信。

答案 3 :(得分:0)

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

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

在我的配置包中有三个文件:

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