Ignacio Perez
Ignacio Perez

Reputation: 2361

Golang how can I do a Dependency Injection to store some string values

I am using a mysql database and have many different Functions/Methods that interact with the database. For every Function I offcourse have to supply the Database Credentials such as

ReadAll.go

func ReadAll() {
    db, err := sql.Open("mysql",
        "user:password@tcp(127.0.0.1:3306)/hello")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
}

The part of "mysql", "user:password@tcp(127.0.0.1:3306)/hello" never changes and I am supplying that to every Function that interacts with DB. I was wondering how can I for instance create a new File say DataBase.go put those credentials into some global variable and then reference when I need those strings ? That way if I have to change the credentials I only have to change them in 1 place. I want to do something like

Database.go

const GlobalDB := "mysql","user:password@tcp(127.0.0.1:3306)/hello"

then

ReadAll.go

func ReadAll() {
    db, err := sql.Open(GlobalDB)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
}

I am brand new to Golang but trying to figure this out.

Upvotes: 1

Views: 1568

Answers (3)

John Deng
John Deng

Reputation: 29

hiboot-data provides out of the box starter that meet your requirement, the starter is github.com/hidevopsio/hiboot-data/starter/gorm, or you can implement your own starter by using hiboot framework, then you can inject then anywhere to decouple from the creation of the database configuration.

package service


import (
    "errors"
    "hidevops.io/hiboot-data/examples/gorm/entity"
    "hidevops.io/hiboot-data/starter/gorm"
    "hidevops.io/hiboot/pkg/app"
    "hidevops.io/hiboot/pkg/utils/idgen"
)

type UserService interface {
    AddUser(user *entity.User) (err error)
    GetUser(id uint64) (user *entity.User, err error)
    GetAll() (user *[]entity.User, err error)
    DeleteUser(id uint64) (err error)
}

type UserServiceImpl struct {
    // add UserService, it means that the instance of UserServiceImpl can be found by UserService
    UserService
    repository gorm.Repository
}

func init() {
    // register UserServiceImpl
    app.Component(newUserService)
}

// will inject BoltRepository that configured in github.com/hidevopsio/hiboot/pkg/starter/data/bolt
func newUserService(repository gorm.Repository) UserService {
    repository.AutoMigrate(&entity.User{})
    return &UserServiceImpl{
        repository: repository,
    }
}

func (s *UserServiceImpl) AddUser(user *entity.User) (err error) {
    if user == nil {
        return errors.New("user is not allowed nil")
    }
    if user.Id == 0 {
        user.Id, _ = idgen.Next()
    }
    err = s.repository.Create(user).Error()
    return
}

func (s *UserServiceImpl) GetUser(id uint64) (user *entity.User, err error) {
    user = &entity.User{}
    err = s.repository.Where("id = ?", id).First(user).Error()
    return
}

func (s *UserServiceImpl) GetAll() (users *[]entity.User, err error) {
    users = &[]entity.User{}
    err = s.repository.Find(users).Error()
    return
}

func (s *UserServiceImpl) DeleteUser(id uint64) (err error) {
    err = s.repository.Where("id = ?", id).Delete(entity.User{}).Error()
    return
}

Upvotes: 1

MrMagix
MrMagix

Reputation: 116

you can easily create a new File with your credentials. Just have the file be in the main package main.

package main

var myDBConnectionString := "mysql://...."

This will be included when you compile your source.

The problem is, that you have to recompile your code everytime you have to connect to another database. Think about a development System vs. production System. The database credentials should differ in those systems, right? :)

To fix this, it is quit common to have a config file. So you can change the credentials with out re compiling your code.

I've got an other idea - just connect to the db once, and access this resource globally. package main

import (
    "fmt"
)

var myDb = "example"

func main() {
    fmt.Println("Hello, playground")
    doSomthingWithDatabase()
}

func doSomthingWithDatabase() {
   fmt.Println("We can access a global variable here, see", myDb)
}

https://play.golang.org/p/npZ6Z49ink

For the configuration handling you can look here https://blog.gopheracademy.com/advent-2014/reading-config-files-the-go-way/

Upvotes: 1

Vatine
Vatine

Reputation: 21258

I would probably do this by opening a session to the database once, then pass this session around to any function or method that may need it. This has a few potential problems:

  • You may need to lock access to it, so you don't co-mingle multiple queries on the same session (but it may be that your DB library ensures this, FWIW "database/sql" is concurrency-safe and recommends NOT opening short-lived database connections)
  • You can't safely close the session as it may still be in use elsewhere.

Another way would be to have a function that returns a DB sesssion, so instead of doing:

db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/hello")

You do the following:

func dbSession() (sql.DB, error) {
  return sql.Open("mysql", "credentials")
}

func ReadAll() {
    db, err := dbSession()
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close() 
}

And if you want even more flexibility, you can have a struct that contains the data you need, then build your DB connection parameters from that.

type dbData struct {
    DBType, DBName, User, Host, Password string
}
var DBData dbData

func dbSession() (*sql.DB, error) {
  return sql.Open(DBData.DBType, fmt.Sprintf("%s:%s@tcp(%s)/%s", DBData.User, DBData.Password, DBData.Host, DBData.DBName)
}

Also note the following in the documentation from sql.Open:

The returned DB is safe for concurrent use by multiple goroutines and maintains its own pool of idle connections. Thus, the Open function should be called just once. It is rarely necessary to close a DB.

Upvotes: 3

Related Questions