Go and Gin: Передача структуры для контекста базы данных?
Я только начал пробовать Go, и я пытаюсь повторно реализовать сервер API, написанный в ноде с ним.
Я столкнулся с проблемой при попытке использовать внедрение зависимостей для передачи контекста базы данных в качестве промежуточного программного обеспечения джина. Пока я настроил это так:
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")
}
модели / 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
}
модели / 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
}
}
Итак, со всем вышеперечисленным, я хочу передать модели. DataSource как промежуточное ПО, чтобы к нему можно было получить доступ следующим образом:
общественности / 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)
}
Однако я получаю public\public.go:10: db.GetVotePack undefined (type interface {} is interface with no methods)
Когда я проверяю в отладчике (используя Webstorm с плагином), БД - это просто пустой объект. Я стараюсь быть хорошим и избегать использования глобальных переменных
5 ответов
Значения в context.Keys
все типа interface{}
, так db
не сможет вызывать методы из типа *DB
пока не преобразуется обратно в этот тип.
Безопасный способ:
db, ok := context.Keys["DB"].(*DB)
if !ok {
//Handle case of no *DB instance
}
// db is now a *DB value
Менее безопасный способ, который будет паниковать, если context.Keys["DB"]
не является значением типа *DB
:
db := context.Keys["DB"].(*DB)
// db is now a *DB value
У Effective Go есть раздел по этому вопросу.
Я не думаю context
следует использовать как контейнер DI: https://golang.org/pkg/context/
Контекст пакета определяет тип контекста, который передает крайние сроки, сигналы отмены и другие значения области запроса через границы API и между процессами.
Я бы предпочел использовать:
package public
type PublicController struct {
Database *DB
}
func (c *PublicController) GetCurrentVotePack(context *gin.Context) {
votePack, err := c.Database.GetVotePack("c5039ecd-e774-4c19-a2b9-600c2134784d")
if err != nil{
context.String(404, "Votepack Not Found")
}
context.JSON(200, votePack)
}
и настройте свой контроллер в основном:
func main() {
pCtrl := PublicController { Database: models.NewDB("<connectionstring>") }
router := gin.New()
router.GET("/public/current-vote-pack", pCtrl.GetCurrentVotePack)
router.Run(":1000")
}
Вам потребуется утверждение типа, чтобы преобразовать интерфейс (db:= context.Keys["DB"]) во что-то полезное. Смотрите, например, этот пост: конвертировать интерфейс {} в int в Голанге
Есть другой способ сделать это, когда БД настроен на контекст во время запуска.
db := ctx.MustGet("DB").(*gorm.DB)
MustGet возвращает значение для данного ключа, если он существует, в противном случае происходит паника.
Отличная статья на эту тему:Организация доступа к базе данных в Go , в которой описаны плюсы и минусы многих возможностей.
Что касается использования контекста для передачи соединения с БД:
[...] контекст запроса должен использоваться только для хранения значений, которые создаются во время отдельного цикла запроса и больше не нужны после завершения запроса. На самом деле он не предназначен для хранения долгоживущих зависимостей обработчиков, таких как пулы соединений, средства журналирования или кеши шаблонов.
Также описано решение, предложенное Лукашом Маршалом, и оно мне вполне подходит. «Обертка пула соединений» может понравиться некоторым людям больше, но, на мой взгляд, это добавляет слишком много сложности (например, подразумевает использование моделей) — не проблема, если эти структуры уже присутствуют в вашем проекте.