在使用GORM时,什么时候是运行Automigrate的合适时间?

huangapple go评论91阅读模式
英文:

When is right time to run Automigrate with GORM

问题

我理解你的问题是关于在API服务中使用GORM时是否应该将Automigrate从常规流程中移除并单独处理,以避免在每个API请求中都进行昂贵的数据库迁移操作。

根据你提供的GORM文档示例,大多数Go/GORM示例都在打开数据库连接后立即调用Automigrate方法。对于API服务来说,这将是一个昂贵且不必要的操作,因为它会在每个API请求中都执行一次。因此,我认为你的理解是正确的,应该将Automigrate从常规流程中移除,并单独处理。

你可以在API服务启动时执行一次数据库迁移操作,例如在应用程序初始化阶段或者在启动脚本中调用Automigrate方法。这样可以确保数据库的表结构与模型定义保持一致,而无需在每个API请求中都执行迁移操作。

希望这个回答对你有帮助!如果你还有其他问题,请随时提问。

英文:

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{})
...

答案1

得分: 2

这不会发生在每个API请求上。甚至远远不会发生。它只会在应用程序启动时发生,所以基本上是:在main中连接到数据库,并在那里运行AutoMigrate。将连接作为依赖项传递给处理程序/服务包/任何需要它的地方。HTTP处理程序可以直接在那里访问它。

基本上是这样的:

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)
    }
    // 请参考下面的处理方式
    fRepo := foo.New(db) // 所有的存储库都在这里
    fRepo.Migrate() // 这处理迁移
    // 创建请求处理程序
    fHandler := handlers.NewFoo(fRepo) // 迁移已经处理过了
    mux := http.NewServeMux()
    mux.HandleFunc("/foo/list", fHandler.List) // 设置处理程序
    // 启动服务器等...
}

将与数据库交互的代码放在某个包中,例如:

package foo

// 作为您使用的数据库连接接口
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{}) // 所有此存储库处理的类型都在这里
}

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

然后以合理的方式组织处理程序,并为它们提供存储库(即foo包的内容):

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
    }
    // 根据需要编写响应
}

每当部署应用程序的更新版本时,main函数将调用AutoMigrate,应用程序将处理请求,而不会不断重新连接到数据库或尝试一次又一次地处理迁移。

我不知道为什么你会认为你的应用程序必须对每个请求运行设置,特别是考虑到你的main函数(或者从main调用的某个函数)明确地创建了一个HTTP服务器,并在特定端口上监听请求。数据库连接和随后的迁移应该在开始监听请求之前处理。它从来都不是处理请求的一部分...

英文:

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...

huangapple
  • 本文由 发表于 2023年1月4日 10:30:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/75000424.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定