Golang多版本模块结构解析
Golang多版本模块结构解析
我正在开发一个包(https://github.com/GrigoriyMikhalkin/sqlboiler-paginate),它应该包含多个版本。版本代码位于 vN 子包中,并且除了一个依赖项(该依赖项应有不同版本)外,其他部分几乎完全相同。因此,我将公共代码保留在包的根目录中。
我的问题是,在不同版本之间复用代码的最佳方式是什么?看起来,如果公共代码位于根模块的其他位置,那么根模块将被视为依赖项并出现在 go.mod 中。有没有办法避免这种情况,还是我应该为每个版本复制代码?
2 回复
通过将公共代码移至另一个嵌套模块解决了这个问题。现在项目基本上是一个包含三个模块的单一代码库:
- /root
- /common -- 公共模块
- /v3 -- v3 模块
- /v4 -- v4 模块
更多关于Golang多版本模块结构解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go模块多版本结构中,复用代码的最佳实践是使用内部包(internal package)。以下是具体实现方案:
方案一:使用internal包(推荐)
// 项目结构
sqlboiler-paginate/
├── go.mod
├── internal/
│ └── shared/
│ ├── paginator.go
│ └── utils.go
├── v1/
│ ├── go.mod
│ └── paginate.go
└── v2/
├── go.mod
└── paginate.go
// internal/shared/paginator.go
package shared
import "context"
type Paginator interface {
Paginate(ctx context.Context, query interface{}) (interface{}, error)
}
func NewBasePaginator() *BasePaginator {
return &BasePaginator{}
}
type BasePaginator struct {
// 公共字段
}
func (b *BasePaginator) CommonMethod() {
// 公共实现
}
// v1/go.mod
module github.com/GrigoriyMikhalkin/sqlboiler-paginate/v1
go 1.21
// v1/paginate.go
package paginate
import (
"github.com/GrigoriyMikhalkin/sqlboiler-paginate/internal/shared"
"github.com/volatiletech/sqlboiler/v4/queries/qm"
)
type V1Paginator struct {
*shared.BasePaginator
}
func (v *V1Paginator) PaginateV1(query []qm.QueryMod) ([]qm.QueryMod, error) {
v.CommonMethod()
// v1特定实现
return query, nil
}
方案二:使用符号链接(适用于复杂场景)
# 创建共享代码目录
mkdir -p shared
# 为每个版本创建符号链接
ln -s ../shared v1/shared
ln -s ../shared v2/shared
# 项目结构
sqlboiler-paginate/
├── shared/
│ └── common.go
├── v1/
│ ├── shared -> ../shared
│ └── paginate.go
└── v2/
├── shared -> ../shared
└── paginate.go
方案三:使用go:linkname链接私有函数
// shared/shared.go
package shared
import _ "unsafe"
//go:linkname commonHelper github.com/GrigoriyMikhalkin/sqlboiler-paginate/v1.commonHelper
func commonHelper() string {
return "shared implementation"
}
// v1/paginate.go
package paginate
import (
_ "github.com/GrigoriyMikhalkin/sqlboiler-paginate/shared"
)
func commonHelper() string
func V1Function() string {
return commonHelper() + " v1"
}
方案四:使用构建标签隔离版本特定依赖
// shared/dependencies.go
//go:build !v1 && !v2
// +build !v1,!v2
package shared
import "github.com/volatiletech/sqlboiler/v4/queries/qm"
type Dependency interface {
GetQueryMod() qm.QueryMod
}
// v1/dependencies.go
//go:build v1
// +build v1
package shared
import "github.com/volatiletech/sqlboiler/v3/queries/qm"
type Dependency interface {
GetQueryMod() qm.QueryMod
}
// 构建命令
go build -tags v1 ./v1
go build -tags v2 ./v2
实际应用示例
// 根目录go.mod定义模块
module github.com/GrigoriyMikhalkin/sqlboiler-paginate
go 1.21
// v1/go.mod
module github.com/GrigoriyMikhalkin/sqlboiler-paginate/v1
go 1.21
require (
github.com/GrigoriyMikhalkin/sqlboiler-paginate v0.0.0
github.com/volatiletech/sqlboiler/v3 v3.7.1
)
replace github.com/GrigoriyMikhalkin/sqlboiler-paginate => ../
// v1/paginate.go使用内部包
package paginate
import (
"github.com/GrigoriyMikhalkin/sqlboiler-paginate/internal/shared"
v3qm "github.com/volatiletech/sqlboiler/v3/queries/qm"
)
type V1Paginator struct {
shared.BasePaginator
v3Dependency v3qm.QueryMod
}
func NewV1Paginator() *V1Paginator {
return &V1Paginator{
BasePaginator: *shared.NewBasePaginator(),
}
}
使用internal包是最符合Go惯例的方案,它能确保代码复用同时避免外部模块依赖。符号链接方案适用于需要物理分离但逻辑共享的场景。构建标签方案适合处理版本间依赖差异。

