Go 语言(Go)有个好特性:用获取代码的位置给代码设命名空间。若托管在 GitHub(GitHub),导入路径就是 github.com/...,Go 会用 git 拉取。这便于报告开源库 bug,也便于分发,无需中心化包管理。但很多人直接用托管地址,会有缺点,应改用自有域名。主要问题是代码与托管商耦合:迁到 GitLab(GitLab)就得改代码,否则仍拉旧版本,切换成本高,最终被 GitHub 绑住。
正文
Go 的一个优良特性是用代码拉取地址作为命名空间。比如代码托管在 github.com/thetrueares/boneclone,导入写 "github.com/thetrueares/boneclone",Go 会用 git 获取。这让人一眼知道去哪报开源库 bug,也便于分发,无需中央包管理。对很多人这就是 git 托管地址,但这有缺点,你应该用自定义域名,理由如下。
用 git 托管位置的主要问题是代码和托管商耦合了。如果迁到 GitLab,就得改代码,否则拉到旧版。切换开销大得让人没法换提供商,代码实质绑死在 GitHub 上。这听着荒唐,却是 Go 社区的事实标准。
我曾见一家公司为这问题吃尽苦头:他们同时用 GitLab、GitHub 和 Azure Devops,因为改代码位置任务重又“没时间”,不如在三平台运营。这也促使我写了 Boneclone 跨平台同步骨架代码。这问题让公司白掏钱养三个托管服务。
办法是用自定义域名,如 go.iain.rocks、go.uber.org、go.mongodb.org 等,只改域名指向即可。例如 go.iain.rocks/boneclone 指向 github.com/thetrueares/boneclone,就算我迁到 GitLab,用户安装命令也不变。
我认为每个用 Go 的商业开发团队都该用自定义域名给内部库命名空间,轻松避开无谓耦合。
下面是我的配置副本,方便你给项目搭一套。
Nginx.conf
配置里 server_name 设为 go.iain.rocks,对普通访客重定向到 GitHub,对带 go-get=1 的 Go 工具则提供 HTML 文件,并配置了 SSL 证书与 HTTP 强制转 HTTPS。
index.html
页面里用 meta 标签 go-import 和 go-source 把 go.iain.rocks/boneclone 指向 github.com/that-guy-iain/boneclone 仓库及其目录、文件链接。
评论
社区对 Go 包是否应采用自定义域名命名空间意见不一,多数人质疑其可靠性和必要性,认为平台托管或简单替换更稳妥,少数人看好其长期可控与供应链安全。
认为自定义域名能提升可控性与供应链安全
- 自定义域名允许后续指向制品仓库,逐步强化版本固定与校验,适应新安全态势;且该做法不限于 Go,可推广到其他栈避免链接失效(认可度:少数人) > 未来可将此域名指向制品仓库,以代理模式起步来保障软件供应链,这会逐渐成为标配(@0xbadcafebee)
质疑自定义域名易失效且带来不必要负担
- 域名依赖持续付费与维护,个人、开源项目或倒闭企业极易弃管导致断链甚至被劫持;相较之下 GitHub 更持久,且用 go.mod 的 replace 或批量替换即可解决迁移,属过早优化(认可度:较多人) > 公司倒闭后悬空域名被他人抢注接管依赖代码的情况已多次发生,你绝不会像微软那样可靠地续费域名(@st3fan)
批评 Go 以托管地址作为包名的语言设计
- Go 将获取位置嵌入包名在实践中暴露出问题,社区本应承认官方统一包名管理的价值,而非让每个开发者自建转发服务来修补设计缺陷(认可度:少数人) > 文章一面说用地址命名是优点,一面又解释它实践不好,却仍建议所有人设 nginx 做 vanity 转发而非承认官方包名更优(@unscaled)
建议更轻量的替代技术机制
- 可用 go.mod 的 replace 指令或简单文本替换应对极少发生的托管迁移,无需引入域名层;也有观点主张用相对路径兄弟子模块或抽象 URN 解析来代替 URL 嵌入(认可度:较多人) > 迁移后只需在 go.mod 加一行 replace 即可照常工作,为罕见事件搞自定义域名是过早优化(@dewey)
有价值的补充
- 有 Hugo 主题模板可快速生成 vanity 导入路径页,但作者承认自身仍依赖 GitHub 托管该模板,折射出矛盾(@andreashaerter)
- Nginx 配置 301 重定向会被 Chrome 等浏览器永久缓存,若日后更换目标地址可能致客户端跳转失效,实践需留意(@0xCMP)
- 域名注册商可能单方面批量删除域名,使自定义域名策略瞬间归零(@p4bl0)
- Go 的 go.sum 已记录模块哈希,若结合内容寻址获取与声明式包名重定向,可从根本上解决可重现构建与迁移演化(@cyberax)