Home > database >  When is right time to run Automigrate with GORM
When is right time to run Automigrate with GORM

Time:01-04

Most Go/GORM examples I've seen show Automigrate being called immediately after opening the database connection, including GORM documentation here. For API services, this would be an expensive/wanted call with every API requests. So, I assume, for API services, Automigrate should be removed from regular flow and handled separately. Is my understanding correct?

From GORM Documentation

...
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
  panic("failed to connect database")
}

// Migrate the schema
db.AutoMigrate(&Product{})
...

CodePudding user response:

It wouldn't happen on every API request. Not even close. It'd happen every time the application is started, so basically: connect to the DB in main, and run AutoMigrate there. Pass the connection as a dependency to your handlers/service packages/wherever you need them. The HTTP handler can just access it there.

Basically this:

package main

func main() {
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    if err != nil {
        fmt.Printf("Failed to connect to DB: %v", err)
        os.Exit(1)
    }
    // see below how this is handled
    fRepo := foo.New(db) // all repos here
    fRepo.Migrate() // this handles migrations
    // create request handlers
    fHandler := handlers.NewFoo(fRepo) // migrations have already been handled
    mux := http.NewServeMux()
    mux.HandleFunc("/foo/list", fHandler.List) // set up handlers
    // start server etc...
}

Have the code that interacts with the DB in some package like this:

package foo

// The DB connection interface as you use it
type Connection interface {
    Create()
    Find()
    AutoMigrate(any)
}

type Foo struct {
    db Connection
}

func New(db Connection) *Foo {
    return &Foo{
        db: db,
    }
}

func (f *Foo) Migrate() {
    f.db.AutoMigrate(&Stuff{}) // all types this repo deals with go here
}

func (f *Foo) GetAll() ([]Stuff, error) {
    ret := []Stuff{}
    res := f.db.Find(&ret)
    return ret, res.Error
}

Then have your handlers structured in a sensible way, and provide them with the repository (aka foo package stuff):

package handlers

type FooRepo interface {
    GetAll() ([]Stuff, error)
}

type FooHandler struct {
    repo FooRepo
}

func NewFoo(repo FooRepo) *FooHandler {
    return &FooHandler{
        repo: repo,
    }
}

func (f *FooHandler) List(res http.ResponseWriter, req *http.Request) {
    all, err := f.repo.GetAll()
    if err != nil {
        res.WriteHeader(http.StatusInternalServerError)
        io.WriteString(w, err.Error())
        return
    }
    // write response as needed
}

Whenever you deploy an updated version of your application, the main function will call AutoMigrate, and the application will handle requests without constantly re-connecting to the DB or attempting to handle migrations time and time again.

I don't know why you'd think that your application would have to run through the setup for each request, especially given that your main function (or some function you call from main) explicitly creates an HTTP server, and listens on a specific port for requests. The DB connection and subsequent migrations should be handled before you start listening for requests. It's not part of handling requests, ever...

  • Related