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 , в которой описаны плюсы и минусы многих возможностей.

Что касается использования контекста для передачи соединения с БД:

[...] контекст запроса должен использоваться только для хранения значений, которые создаются во время отдельного цикла запроса и больше не нужны после завершения запроса. На самом деле он не предназначен для хранения долгоживущих зависимостей обработчиков, таких как пулы соединений, средства журналирования или кеши шаблонов.

Также описано решение, предложенное Лукашом Маршалом, и оно мне вполне подходит. «Обертка пула соединений» может понравиться некоторым людям больше, но, на мой взгляд, это добавляет слишком много сложности (например, подразумевает использование моделей) — не проблема, если эти структуры уже присутствуют в вашем проекте.

Другие вопросы по тегам