Go 中解耦的项目结构仍然使用 main.go 中初始化的变量进行其他打包和测试?

Msw*_* Tm 6 go

我正在从 Python/Django 切换到 Go。在 Django 中,我非常喜欢它的模块化应用程序项目结构设计,其中每个应用程序都有单独的业务模型、路由和视图。然后,所有应用程序将在中央/项目的主路由系统等内进行通信。

Django项目结构例如:

- myproject
    - myproject
        - urls.py
        - views.py
        ...
    - planner
        - urls.py
        - views.py
        - models.py
        ...
Run Code Online (Sandbox Code Playgroud)

我试图在Go项目中实现类似的项目设计:

    - myproject
        - cmd
            - api
                - main.go
                - routes.go
                - handlers.go
            - planner
                - routes.go
                - handlers.go
                - models.go
Run Code Online (Sandbox Code Playgroud)

摘自 cmd/api/main.go:

package main

...

db, err := sql.Open("pgx", cfg.db.dsn)
...
srv := &http.Server{
    Addr:         fmt.Sprintf("localhost:%d", app.config.port),
    Handler:      app.routes()
}
...
Run Code Online (Sandbox Code Playgroud)

摘自 cmd/api/routes.go:

package main

func (app *application) routes() *httprouter.Router {
    router := httprouter.New()

    planner.Routes(router)

    return router
}
Run Code Online (Sandbox Code Playgroud)

摘自 cmd/planner/routes.go:

package planner
...
func Routes(router *httprouter.Router) {
    router.HandlerFunc(http.MethodPost, "/v1/planner/todos", CreateTodoHandler)
}
Run Code Online (Sandbox Code Playgroud)

摘自 cmd/planner/models.go:

package planner

type PlannerModel struct {
    DB *sql.DB
}

func (p PlannerModel) InsertTodo(todo *Todo) error {
    query := `INSERT INTO todos (title, user_id)
    VALUES ($1, $2)
    RETURNING id, created_at`

    return p.DB.QueryRow(query, todo.Title, todo.UserID).Scan(&todo.ID, &todo.CreatedAt)
}
Run Code Online (Sandbox Code Playgroud)

现在的问题是我需要使用cmd/api/main.go文件中初始化的数据库连接package maincmd/planner/handlers.go。由于变量来自主包,我无法将其导入到我的应用程序(规划器)处理程序函数中。

package planner

func CreateTodoHandler(w http.ResponseWriter, r *http.Request) {
    var input struct {
        Title  string `json:"title"`
        UserID int64  `json:"user_id"`
    }

    err := helpers.ReadJSON(w, r, &input)
    ...
    todo := &Todo{
        Title:  input.Title,
        UserID: input.UserID,
    }
    ...
    // How to use/inject DB connection dependency into the PlannerModel?
    pm := PlannerModel{
        DB:    // ????
    } 

    err = pm.InsertTodo(todo)
    ...
}
Run Code Online (Sandbox Code Playgroud)

我认为拥有一个全局 DB 变量可以解决问题,或者我发现的合理答案是在单独的包中声明该变量,并在 main.go 中初始化它。另一种方法是使 Planner/handlers.go 成为package main并在主包中创建一个application结构来保存项目的所有模型并在处理程序中使用它,但我想这会破坏解耦的架构设计。

我想知道什么是首选方法,或者是否有更好的方法来拥有类似 Django 的解耦项目结构,并且仍然使用 main.go 中初始化的变量到其他包中并进行测试?

meh*_*hdy 3

当我从 Python/Django 转向 Go 时,我也有过类似的经历。

在每个应用程序中访问db连接的解决方案是在每个应用程序中定义具有db连接字段的结构,然后db在主应用程序中创建连接和所有应用程序结构体。

// planner.go

func (t *Todo) Routes(router *httprouter.Router) {
    router.HandlerFunc(http.MethodPost, "/v1/planner/todos", t.CreateTodoHandler)
}
Run Code Online (Sandbox Code Playgroud)
// handlers.go

struct Todo {
    DB: *sql.DB
}

func (t *Todo) CreateTodo(w http.ResponseWriter, r *http.Request) {
    var input struct {
        Title  string `json:"title"`
        UserID int64  `json:"user_id"`
    }

    err := helpers.ReadJSON(w, r, &input)
    ...
    todo := &Todo{
        Title:  input.Title,
        UserID: input.UserID,
    }
    ...

    pm := PlannerModel{
        DB: t.DB
    } 

    err = pm.InsertTodo(todo)
    ...
}
Run Code Online (Sandbox Code Playgroud)

这可以解决您当前的问题,但如果您没有更好的应用程序设计,则会出现其他问题。

建议阅读这两篇博客文章,以更好地理解用 Go 设计应用程序和构建代码。

https://www.gobeyond.dev/standard-package-layout/
https://www.gobeyond.dev/packages-as-layers/