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惯例的方案,它能确保代码复用同时避免外部模块依赖。符号链接方案适用于需要物理分离但逻辑共享的场景。构建标签方案适合处理版本间依赖差异。

回到顶部