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
听起来它们确实是,那么你只需要合理的、可通过 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模块系统设计时假设模块名称对应真实的网络地址,当使用本地替换时,相对路径的解析受到当前工作目录的影响。在跨多级模块依赖时,每个模块都需要能够独立解析其所有依赖的替换路径。

