Golang模块导入的常见痛点与解决方案

Golang模块导入的常见痛点与解决方案 有三个目录

在某个随机文件夹中,有三个文件夹 a、b、c,每个文件夹都包含一个 mod 文件 --------a ----------go.mod --------b ----------go.mod --------c ---------go.mod

mod 文件的内容如下。

a 文件夹内的 go.mod

module a

go 1.13

b 文件夹内的 go.mod

module b
go 1.13
require a v0.0.0
replace a v0.0.0 => ./../a

c 文件夹内的 go.mod

module c
go 1.13
require b v0.0.0
replace b v0.0.0 => ./../b

模块 b 没有报错。但模块 c 报错:

go: b@v0.0.0 requires
a@v0.0.0: unrecognized import path "a" (import path does not begin with hostname)

好吧!某些聪明的开发者认为每个模块名称中必须包含一个点(.)。多么可爱啊!

“某个随机文件夹” 改为 example.com。现在名为 example.com 的文件夹包含了所有的 a、b、c 文件夹。现在模块看起来是这样的:

模块 A 看起来像:

module example.com/a
go 1.13

模块 B 看起来像:

module example.com/b
go 1.13
require example.com/a v0.0.0
replace example.com/a v0.0.0 => ../a

模块 C 看起来像:

module example.com/c
go 1.13
require example.com/b v0.0.0
replace example.com/b v0.0.0 => ../b

太糟糕了!错误!

go: example.com/b@v0.0.0 requires
example.com/a@v0.0.0: unrecognized import path "example.com/a" (https fetch: Get 
https://example.com/a?go-get=1: dial tcp 208.73.210.202:443: connect: connection refused)

这是怎么回事?为什么模块的本地导入如此复杂?


更多关于Golang模块导入的常见痛点与解决方案的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

听起来它们确实是,那么你只需要合理的、可通过 go get 获取的模块路径,否则你将需要在 go.mod 中使用 replace 来指向它们。

更多关于Golang模块导入的常见痛点与解决方案的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


是的,它们在不同的代码仓库中。每个仓库都由不同的团队负责。我也希望明天能向我的供应商开放合同,以便他们可以贡献不同的适配器。很明显,它们是模块。

它们是具有独立版本控制的独立仓库吗?如果您更新了 AdapterContract,您是否希望必须为其创建一个新的发布版本,然后在 MyApp 中单独提交一次以使用新版本?您是否希望 MyApp、MySqlAdapter 和 MongoDbAdapter 都能够使用不同版本的 AdapterContract?如果所有这些问题的答案都是“是”,那么它们就是模块。

如果您对其中任何一个问题回答了“否”,那么我相当确定这些都只是同一个总模块中的包,而这就是我的出发点,直到被证明是错误的。

为什么你会有这么多模块?模块不是包。通过在仓库的顶层设置一个模块,并在其下放置包,你的生活会轻松很多。其下的所有内容都引用该模块名称,并且无论你将其放置在文件系统的哪个位置都无关紧要。

仅仅从其他模块中声明 import "a" 并不能让 Go 知道去哪里找到那个包。

如果这对你没有帮助,请解释你试图实现什么以及为什么,我们可以提供指导。

func main() {
    fmt.Println("hello world")
}

在我看来,模块是独立的实体,可以是“库”或单个可执行组件。

就像我说的,假设有以下模块:

AdapterContract MySqlAdapter MongoDbAdapter MyApp

以上四个都是模块,任何适配器贡献者都将使用 AdapterContract 来创建适配器。这里有两个贡献者:MySql 和 MongoDb。

最后,MyApp 将使用这两个创建的适配器并连接到数据库。

你可能会问,为什么我需要 4 个模块来实现这个功能? 正是因为我希望代码具有模块化特性。 AdapterContract 不关心任何具体行为。它只是定义“要成为一个适配器,你必须看起来像这样”。 SqlAdapter 是由希望遵守 AdapterContract 的人创建的,MongoDb Adapter 也是如此。 MyApp 基本上是一个反应器。“我只运行那些行为像适配器的东西”。

假设这是示例。模块化是最好的方法吗?

这是一个典型的Go模块替换路径解析问题。问题在于replace指令中的相对路径在跨模块引用时无法正确传递。

当模块C导入模块B,而模块B又替换了模块A时,Go工具链会尝试解析完整的依赖链。但replace指令中的相对路径../a在模块C的上下文中无法正确解析到模块A的位置。

以下是解决方案:

方案1:使用绝对路径或环境变量

修改模块B的go.mod:

module example.com/b
go 1.13
require example.com/a v0.0.0
replace example.com/a v0.0.0 => /absolute/path/to/a

或者使用环境变量:

replace example.com/a v0.0.0 => ${ROOT_PATH}/a

方案2:在所有模块中使用相同的替换规则

在每个需要引用模块A的go.mod中都添加相同的replace指令:

模块C的go.mod:

module example.com/c
go 1.13
require example.com/b v0.0.0
replace example.com/b v0.0.0 => ../b
replace example.com/a v0.0.0 => ../a

方案3:使用workspace模式(Go 1.18+)

创建go.work文件:

go 1.18

use (
    ./a
    ./b
    ./c
)

方案4:使用vendor目录

在模块C中:

go mod vendor

然后确保所有依赖都被正确vendored。

方案5:使用本地代理或replace链

模块B的go.mod:

module example.com/b
go 1.13
require example.com/a v0.0.0
replace example.com/a v0.0.0 => ./../a

模块C的go.mod:

module example.com/c
go 1.13
require example.com/b v0.0.0
replace example.com/b v0.0.0 => ./../b
replace example.com/a v0.0.0 => ./../a

根本原因是Go模块系统设计时假设模块名称对应真实的网络地址,当使用本地替换时,相对路径的解析受到当前工作目录的影响。在跨多级模块依赖时,每个模块都需要能够独立解析其所有依赖的替换路径。

回到顶部