【问题标题】:How to split my resources into multiply files如何将我的资源拆分为多个文件
【发布时间】:2017-11-04 21:18:32
【问题描述】:

我正在尝试在 golang 上编写一个宁静的 api。对于 http 路由器,我使用 gin-gonic,与我使用 gorm 的数据库交互。 主包

import (
    "fmt"

    "github.com/gin-gonic/gin"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/postgres"
)

var db *gorm.DB

type Person struct {
    ID        uint   `json:"id"`
    FirstName string `json:"firstname"`
    LastName  string `json:"lastname"`
}

func main() {
    // NOTE: See we’re using = to assign the global var
    // instead of := which would assign it only in this function
    db, err := gorm.Open("postgres", fmt.Sprintf("host=localhost sslmode=disable user=postgres password="))
    if err != nil {
        fmt.Println(err)
    }
    defer db.Close()
    db.AutoMigrate(&Person{})
    r := gin.Default()
    r.GET("/people/", GetPeople)
    r.GET("/people/:id", GetPerson)
    r.POST("/people", CreatePerson)
    r.Run(":8080")
}
func CreatePerson(c *gin.Context) {
    var person Person
    c.BindJSON(&person)
    db.Create(&person)
    c.JSON(200, person)
}
func GetPerson(c *gin.Context) {
    id := c.Params.ByName("id")
    var person Person
    if err := db.Where("id = ?", id).First(&person).Error; err != nil {
        c.AbortWithStatus(404)
        fmt.Println(err)
    } else {
        c.JSON(200, person)
    }
}
func GetPeople(c *gin.Context) {
    var people []Person
    if err := db.Find(&people).Error; err != nil {
        c.AbortWithStatus(404)
        fmt.Println(err)
    } else {
        c.JSON(200, people)
    }
}

如何将代码拆分为多个文件,以便单独的资源位于单独的文件中?如何在另一个文件中使用路由器和数据库?

更新

结构如下:

.
└── app
    ├── users.go
    ├── products.go
    └── main.go

我有两个问题:

  1. db == nil in products.gousers.go
  2. 在不同的文件中重新声明函数(getcreate ...),这通过函数声明中的前缀解决,如CreateUserCreateProduct等。但可以通过将代码放入另一个包中来解决

ma​​in.go

package main

import (
    "fmt"

    "github.com/gin-gonic/gin"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/postgres"
)

var (
    db *gorm.DB
    r  *gin.Engine
)

func init() {
    db, err := gorm.Open("postgres", fmt.Sprintf("host=localhost sslmode=disable user=postgres password="))
    if err != nil {
        fmt.Println(err)
    }
    defer db.Close()

    r = gin.Default()
}

func main() {
    r.Run(":8080")
}

products.go

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/jinzhu/gorm"
)

type Product struct {
    gorm.Model
    Code  string
    Price uint
}

func init() {
    db.AutoMigrate(&Product{}) // db -> nil

    r.GET("/products", get)
}

func get(c *gin.Context) {
    var product Product
    db.First(&product, 1)

    c.JSON(200, gin.H{
        "product": product,
    })
}

users.go

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/jinzhu/gorm"
)

type User struct {
    gorm.Model
    Name string
}

func init() {
    db.AutoMigrate(&User{}) // db -> nil

    r.GET("/users", get)
}

// ./users.go:19: get redeclared in this block
// previous declaration at ./products.go:20    
func get(c *gin.Context) {
    var user User
    db.First(&user, 1)

    c.JSON(200, gin.H{
        "user": user,
    })
}

【问题讨论】:

    标签: go


    【解决方案1】:

    由于您的db var 是在包级别定义的,因此它基本上是该包的全局变量,可以在该包中的任何文件中引用。

    例如,在这样的项目中:

    .
    └── app
        ├── a.go
        ├── b.go
        ├── c.go
        └── main.go
    

    如果 db 在包级别的 main.go 中定义,如您的示例所示,则文件 a.go、b.go 和 c.go 中的代码可以使用 db

    反之亦然,a.go、b.go 和 c.go 中定义的任何资源处理程序都可以在 main.go 中引用。这意味着在每个文件中,您可以定义一个函数,该函数接受一个路由器,即 gin 路由器,并设置相应的处理程序,然后在 main.go 的 main 函数中,您调用传入路由器 r 的这些函数。


    更新

    首先,您在 init 函数内部调用 defer db.Close(),这意味着在 init 返回后您的 db 立即关闭,这绝对不是您想要的。在 main 中使用 defer db.Close() 很好,因为 main 在您的应用程序终止时终止,此时关闭数据库是有意义的,但是当 init 终止时您的应用程序甚至没有正常启动,main 刚刚被执行,您仍然需要您的 @ 987654331@.

    如果您想在每个文件中使用init 函数来执行特定于该文件的初始化,则必须确保这些初始化函数所依赖的任何内容都在在它们执行之前进行了初始化

    在您的示例中,您的所有初始化函数都依赖于dbr,因此您需要确保这两个不是nil。我不确定在 Go 中,单个包中的多个 init 函数的执行顺序是什么,但我可以肯定的是包级变量表达式在执行 init 函数之前被初始化。

    所以你可以做的是使用函数调用来初始化两个包级变量,如下所示:

    package main
    
    import (
        "fmt"
    
        "github.com/gin-gonic/gin"
        "github.com/jinzhu/gorm"
        _ "github.com/jinzhu/gorm/dialects/postgres"
    )
    
    var (
        db = func() *gorm.DB {
            db, err := gorm.Open("postgres", fmt.Sprintf("host=localhost sslmode=disable user=postgres password="))
            if err != nil {
                // if you can't open a db connection you should stop the app,
                // no point in continuing if you can't do anything useful.
                panic(err)
            }
            return db
        }() // <- call the anon function to get the db.
    
        r = gin.Default()
    )
    
    func main() {
        // you can call defer db.Close() here but you don't really need to
        // because after main exists, that is, your app terminates, db
        // will be closed automatically.
    
        r.Run(":8080")
    }
    

    关于你的第二个问题,在 Go 中 init 是一个特殊情况,我的意思是你可以在一个包中,甚至在一个文件中拥有多个 init 函数。这不适用于您声明的任何其他标识符。

    这意味着在一个包内,并在包级别声明,你只能有一个db标识符,一个get标识符,只有一个User标识符,等等。无论你使用后缀,例如getUser 或包裹 user.Get 完全取决于您。

    请注意,您可以在在另一个范围内重新声明标识符,假设您在包级别有type User struct { ...,然后在同一个包中声明的函数可以在其自己的范围内声明一个变量就像var User = "whatever",虽然它可能不是编译的最佳主意。

    更多详情见: Package initialization


    更新 2

    如果要将代码拆分为多个包,只需将文件放入单独的文件夹中,并确保文件顶部的 package 声明具有正确的包名称。

    这是一个例子:

    └── app/
        ├── main.go
        ├── product/
        │   ├── product.go
        │   └── product_test.go
        └── user/
            ├── user.go
            └── user_test.go
    

    现在您的app/user/user.go 代码可能看起来像这样。

    package user
    
    import (
        "github.com/gin-gonic/gin"
        "github.com/jinzhu/gorm"
    )
    
    var db *gorm.DB
    
    type User struct {
        gorm.Model
        Name string
    }
    
    // custom and exported Init function, this will not be called automatically
    // by the go runtime like the special `init` function and therefore must be called
    // manually by the package that imports this one.
    func Init(gormdb *gorm.DB, r *gin.Engine) {
        db = gormdb // set package global
    
        db.AutoMigrate(&User{})
    
        r.GET("/users", get)
    }
    
    func get(c *gin.Context) {
        var user User
        db.First(&user, 1)
    
        c.JSON(200, gin.H{
            "user": user,
        })
    }
    

    你的app/product/product.go ...

    package product
    
    import (
        "github.com/gin-gonic/gin"
        "github.com/jinzhu/gorm"
    )
    
    var db *gorm.DB
    
    type Product struct {
        gorm.Model
        Code  string
        Price uint
    }
    
    // custom and exported Init function, this will not be called automatically
    // by the go runtime like the special `init` function and therefore must be called
    // manually by the package that imports this one.
    func Init(gormdb *gorm.DB, r *gin.Engine) {
        db = gormdb // set package global
    
        db.AutoMigrate(&Product{})
    
        r.GET("/products", get)
    }
    
    func get(c *gin.Context) {
        var product Product
        db.First(&product, 1)
    
        c.JSON(200, gin.H{
            "product": product,
        })
    }
    

    还有你的入口点app/main.go ...

    package main
    
    import (
        "fmt"
    
        // This assumes that the app/ folder lives directly in $GOPATH if that's
        // not the case the import won't work.
        "app/product"
        "app/user"
    
        "github.com/gin-gonic/gin"
        "github.com/jinzhu/gorm"
        _ "github.com/jinzhu/gorm/dialects/postgres"
    )
    
    func main() {
        db, err := gorm.Open("postgres", fmt.Sprintf("host=localhost sslmode=disable user=postgres password="))
        if err != nil {
            fmt.Println(err)
        }
        defer db.Close()
    
        r := gin.Default()
    
        // manually initialize imported packages
        user.Init(db, r)
        product.Init(db, r)
    
        r.Run(":8080")
    }
    

    【讨论】:

    • 我根据您的建议更新了我的问题
    • 如果我理解正确,那么使用后缀会是个好主意吗?
    • 是包还是后缀通常取决于项目有多大,但即便如此,这仍然只是一个主观意见,您应该自己或与您的团队一起决定使用哪种方法。只要您的代码编译并遵循"standard" coding practices(包与后缀不是“标准化”afaik :)),那么您应该没问题。
    • Package vs suffix is not "standardized" 是的,但在我看来,最好的做法是分成单独的包,我不明白如何将我的代码成几个包
    • 非常感谢,帮助我理解了=)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-29
    • 2020-08-09
    • 2010-11-13
    • 2018-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多