Golang中如何处理包之间的依赖关系?

Golang中如何处理包之间的依赖关系? 我有以下项目结构:

20210305_070534

处理依赖关系的最佳方式是什么? 有人会如何处理这种设计?如果有人能指出我在哪里可以找到这种情况的解决方案,也将非常有帮助。

谢谢!

5 回复

非常感谢您抽出时间!

更多关于Golang中如何处理包之间的依赖关系?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


嘿 Mihai,

我不太确定你所说的处理包之间的依赖关系是什么意思。你能提供一个更具体的例子吗?

感谢您的回答。 这是我想构建的东西。这个项目最多会有10000行代码,所以不会是一个大项目。然而,我的问题是关于架构的。 我确定了这四个包:一个用于处理某些硬件设备的设备包,一个用于持久化的数据库,一个记录软件功能相关各类信息的日志器,以及一个面向用户的Web界面。对于最佳的日志机制,我不是很确定:是写入单个文件、多个文件、SQLite,甚至是我打算用于其他目的的数据库,所以这个主题可能会改变。 这些包之间的关系如上图所示。 我搜索了关于人们如何在项目中集成数据库的信息,发现有些人使用一个持有数据库连接的全局变量,有些人使用依赖注入机制,或者只是传递依赖项。 在许多在线帖子中,人们使用某种构造函数来实例化结构体:

type Device struct {
IP string
}

func NewDevice(newIp string) *Device {
return &Device{newIp}
}

设计Go应用程序的最佳方式或最常用的方式是什么?

您设计的包可以存放在项目根目录下,Go编译器会知道从那里读取。如果您打算在Github上保存和维护您的项目,也可以将您的包和项目放在$GOPATH/src/github.com目录下。

关于日志记录器,通常标准库中的log包就足够了,否则您可以使用网络上更复杂的日志包,或者自己编写一个。然而,是否需要特殊的日志记录器取决于您的应用程序需求。

关于数据库,您可以例如使用SQLite或像MySQL这样的SQL服务器。在使用SQL服务器时,一个常见的错误是创建多个数据库连接,这是错误的做法。应该使用一个全局变量来持有数据库连接。如果您使用SQLite,需要注意并发性,因为同一时间只能有一个写入操作。如果您确实需要在SQLite上实现并发,可以看看sqlitex包(参见此处的示例)。

关于结构体,您找到的示例是实例化结构的常用方式,是的,这是一个好的实践。

[LE] 对于Web界面,一个好的实践是使用template包。

在Go中处理包依赖关系主要使用Go Modules。根据你的项目结构,这是一个典型的包含多个子包的Go项目。以下是处理这种依赖关系的标准方式:

1. 初始化Go Modules

首先在项目根目录初始化module:

go mod init github.com/yourusername/yourproject

这会创建 go.mod 文件,记录项目依赖。

2. 导入子包

在你的代码中导入子包时,使用完整的module路径。例如,在 cmd/main.go 中:

package main

import (
    "fmt"
    "github.com/yourusername/yourproject/internal/db"
    "github.com/yourusername/yourproject/internal/models"
    "github.com/yourusername/yourproject/pkg/utils"
)

func main() {
    // 使用db包
    dbConn := db.NewConnection()
    
    // 使用models包
    user := models.User{Name: "John"}
    
    // 使用utils包
    result := utils.FormatString("hello")
    
    fmt.Println(user, result, dbConn)
}

3. 处理内部包依赖

internal/db/db.go 中导入 internal/models

package db

import (
    "github.com/yourusername/yourproject/internal/models"
)

type Connection struct {
    // 字段定义
}

func NewConnection() *Connection {
    user := models.User{Name: "Default"}
    // 使用models包
    return &Connection{}
}

func (c *Connection) SaveUser(user models.User) error {
    // 保存用户逻辑
    return nil
}

4. 处理循环依赖

如果出现循环依赖(如A导入B,B导入A),需要重构代码。常见的解决方案:

  • 将共享代码提取到第三个包
  • 使用接口解耦

例如,将共享类型提取到 pkg/types

// pkg/types/types.go
package types

type User struct {
    ID   int
    Name string
}

// internal/models/models.go
package models

import "github.com/yourusername/yourproject/pkg/types"

func ProcessUser(u types.User) {
    // 处理用户
}

// internal/db/db.go  
package db

import "github.com/yourusername/yourproject/pkg/types"

func SaveUser(u types.User) error {
    // 保存用户
    return nil
}

5. 管理外部依赖

添加外部依赖:

go get github.com/gorilla/mux

更新所有依赖:

go mod tidy

这会自动添加缺失的依赖,移除未使用的依赖。

6. Vendor依赖(可选)

如果需要vendor依赖:

go mod vendor

这会创建vendor目录,包含所有依赖的源代码副本。

示例项目结构

yourproject/
├── go.mod
├── go.sum
├── cmd/
│   └── main.go
├── internal/
│   ├── db/
│   │   └── db.go
│   └── models/
│       └── models.go
├── pkg/
│   ├── utils/
│   │   └── utils.go
│   └── types/
│       └── types.go
└── vendor/ (可选)

这种结构清晰地分离了:

  • cmd/: 可执行程序入口
  • internal/: 内部包,外部项目无法导入
  • pkg/: 公共库包,可供外部使用

Go Modules会自动处理这些包之间的依赖关系,你只需要正确导入即可。

回到顶部