I've just started trying out Go, and I'm looking to re-implement an API server written in node with it.
I've hit a hurdle with trying to use dependency injection to pass around a database context as a gin middleware. So far I've set it up as this:
main.go:
package main
import (
"fmt"
"runtime"
"log"
"github.com/gin-gonic/gin"
"votesforschools.com/api/public"
"votesforschools.com/api/models"
)
type DB struct {
models.DataStore
}
func main() {
ConfigRuntime()
ConfigServer()
}
func Database(connectionString string) gin.HandlerFunc {
dbInstance, err := models.NewDB(connectionString)
if err != nil {
log.Panic(err)
}
db := &DB{dbInstance}
return func(c *gin.Context) {
c.Set("DB", db)
c.Next()
}
}
func ConfigRuntime() {
nuCPU := runtime.NumCPU()
runtime.GOMAXPROCS(nuCPU)
fmt.Printf("Running with %d CPUs\n", nuCPU)
}
func ConfigServer() {
gin.SetMode(gin.ReleaseMode)
router := gin.New()
router.Use(Database("<connectionstring>"))
router.GET("/public/current-vote-pack", public.GetCurrentVotePack)
router.Run(":1000")
}
models/db.go
package models
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
type DataStore interface {
GetVotePack(id string) (*VotePack, error)
}
type DB struct {
*sql.DB
}
func NewDB(dataSource string) (*DB, error) {
db, err := sql.Open("mysql", dataSource)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
return nil, err
}
return &DB{db}, nil
}
models/votepack.go
package models
import (
"time"
"database/sql"
)
type VotePack struct {
id string
question string
description string
startDate time.Time
endDate time.Time
thankYou string
curriculum []string
}
func (db *DB) GetVotePack(id string) (*VotePack, error) {
var votePack *VotePack
err := db.QueryRow(
"SELECT id, question, description, start_date AS startDate, end_date AS endDate, thank_you AS thankYou, curriculum WHERE id = ?", id).Scan(
&votePack.id, &votePack.question, &votePack.description, &votePack.startDate, &votePack.endDate, &votePack.thankYou, &votePack.curriculum)
switch {
case err == sql.ErrNoRows:
return nil, err
case err != nil:
return nil, err
default:
return votePack, nil
}
}
So with all of the above, I want to pass the models.DataSource around as a middleware so it can be accessed like this:
public/public.go
package public
import (
"github.com/gin-gonic/gin"
)
func GetCurrentVotePack(context *gin.Context) {
db := context.Keys["DB"]
votePack, err := db.GetVotePack("c5039ecd-e774-4c19-a2b9-600c2134784d")
if err != nil{
context.String(404, "Votepack Not Found")
}
context.JSON(200, votePack)
}
However I get public\public.go:10: db.GetVotePack undefined (type interface {} is interface with no methods)
When I inspect in the debugger (using Webstorm with plugin) the db is just an empty object. I'm trying to be good and avoid global variable use
The values within context.Keys
are all of type interface{}
, so db
will not be able to call methods from type *DB
until it's converted back to that type.
The safe way:
db, ok := context.Keys["DB"].(*DB)
if !ok {
//Handle case of no *DB instance
}
// db is now a *DB value
The less safe way, which will panic if context.Keys["DB"]
is not a value of type *DB
:
db := context.Keys["DB"].(*DB)
// db is now a *DB value
Effective Go has a section on this.