「Go 语言入门指南:基础语法和常用特性解析」后端实践 | 青训营

118 阅读4分钟

本文是后端实践第二篇。总共第四篇笔记。

一、实践选题:

Go 语言入门指南:基础语法和常用特性解析

本文选择Go语言中常用的go module特性,介绍实践如何打包发布v2以上的go库。

二、问题介绍

直接git打tag就可以发布新版本,但在发布v2以上版本时遇到了一些问题,记录一下。

在本文中, 库(被调用方)路径均以github.com/onelib/mylib代替, 业务程序(调用方)路径均以github.com/caller/myservice代替)


还是按照老方法,直接git打tag,发布。结果在调用方就出现了问题。

更新go mod时直接报错:

require github.com/onelib/mylib: version "v2.x.x" invalid: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2

先说结论:

  • 如果你是库作者,如何处理这个错误,请见本文后续的高版本号(>=v2)如何良好支持go module特性一节。
  • 如果你是库用户,遇到这个错误,那么这属于库作者对go module特性支持不完善,请向其反馈。如果你着急使用,可以参见本文下面的临时解决方案一节。

三、解决方案

库用户如何调用【未良好支持go module特性的库】的临时解决方法

库用户临时凑合调用的方法:在go.mod,import时在版本号后面加+incompatible,如

module github.com/caller/myservice

import (
	github.com/onelib/mylib v2.0.0+incompatible
)

(仅作为库未良好支持go module时,调用者可以采用的补丁方案)

但是这样做有缺点, 无法做到同时import多个版本。甚至在不同的文件里都不行,即使你一个文件或目录里只用了一个版本,整个项目也只能导入一份版本。 这样就不能让旧代码仍然使用旧版本,而必须整个项目一起升级到新版,所以有时会造成旧代码的破坏。 同时一些IDE可能由于不识别这种版本号标识而报错。

如果我们仅仅是调用者而非编写者,而实际编写者又没有支持go module时,在确定不会造成旧版本代码破坏后,我们就可以使用这种方案临时解决调用问题。

如果我们就是库的编写者,请继续看下节。

库作者发布高版本号(主版本号>=v2)如何良好支持go module特性

如果你是库作者,更完美的解决方案是:

将库的go.mod中的moulde名称后面增加版本号/vx

module github.com/onelib/mylib -> module github.com/onelib/mylib/v2

注意,文件结构不需要做任何改变,不需要建立v2或者vn文件夹,增加的“/vx”仅仅用于require/import时区分主版本号。

调用时也仍以库文件中声明的package xxx为准来进行xxx.FuncX()调用,而与go.mod中module怎么写的无关。

而后重新在git中打tag v2.x.x,发布。调用方就可以愉快地使用了。

代码示例:

go.mod

module github.com/onelib/mylib/v2

import (
	// ...
)

文件目录结构无需改变。

调用方使用方法见下节。

这就是module发布者根据go module特性处理版本问题的正确方法。 同时,库作者请参照下节,告知库用户在require/import时加上合适的主版本号后缀。

库用户如何调用【有良好支持go module特性的库】的方法

库作者在正确支持go module特性后,库用户(调用方)的注意事项:

  1. go.mod文件中require path需要同样在结尾加上/vx。 require github.com/onelib/mylib/v2 v2.x.x
  2. 代码(.go文件)中的import path需要同样在结尾加上/vx。 import github.com/onelib/mylib/v2

这样,调用者可以在同一项目下import同一模块的多个不同主版本了。

但同一主版本、不同子版本仍然不能共存,这是因为由golang采用的语义化版本号(Semantic Versioning)可知,同一主版本应当是向下兼容,因此同一主版本的高子版本应当能兼容低子版本的功能。

例外:v0和v1似乎还是只能导入一个。有了v1就不能再用v0。 不过在语义化版本号中,v0代表试验的初期不稳定版本,有了v1就基本不会再有用v0的需求,问题不大。

代码示例:

go.mod

module github.com/caller/myservice
    require (
        github.com/onelib/mylib v1.0.0
        github.com/onelib/mylib/v2 v2.1.3
        github.com/onelib/mylib/v3 v3.2.1
    )

在同一项目的不同的代码文件中使用同一库的不同主版本:

代码文件1.go

    import "github.com/onelib/mylib"
	func f1() {
		mylib.FuncX()
	}

代码文件2.go

    import "github.com/onelib/mylib/v2"
	func f2() {
		mylib.FuncX()
	}

代码文件3.go

    import "github.com/onelib/mylib/v3"
	func f3() {
		mylib.FuncX()
	}

在同一项目的同一代码文件中使用同一库的不同主版本: (这个需求有点少见,不过也能实现,如果各主版本包名冲突,import时指定别名即可) 代码文件x.go

package main
    import (
        myliba "github.com/onelib/mylib"
        mylibb "github.com/onelib/mylib/v2"
        mylibc "github.com/onelib/mylib/v3"
	)
	func main() {
		myliba.FuncX()
		mylibb.FuncX()
		mylibc.FuncX()
	}