基础包应按职责拆分为 pkg/log、pkg/config、pkg/errx、pkg/httpx 四个子包,严禁 common 大杂烩;所有包仅依赖标准库或指定第三方,禁止引入业务代码;通过 CI 检查、单元测试隔离、语义化版本(如 v2 新路径)管控升级风险。
Go 语言中没有“公共基础包”的官方概念,但团队协作时必须统一处理日志、配置、错误、HTTP 客户端等共性能力——关键不是建一个叫 common 或 utils 的包,而是按职责边界拆分、避免循环依赖、禁止在基础包里引入业务逻辑。
按 Go 的惯用法和实际维护成本,建议划分为:
pkg/log:封装 zap 或 zerolog,暴露 Logger 接口和预设字段(如 request_id),不暴露底层 logger 实例pkg/config:支持 YAML/TOML/环境变量多源加载,返回结构体而非 map,校验失败应 panic(启动期错误)pkg/errx:定义 ErrorCode 枚举、Wrap/Is 等函数,错误值必须可序列化(不带闭包或指针)pkg/httpx:封装 http.Client,内置超时、重试、trace 注入,不封装具体 API(那是 domain/client 层的事)不要建 pkg/common 这种大杂烩包——它会迅速变成循环依赖的温床,且无法做细粒度版本控制。
最常见问题是:有人在 pkg/log 里加了个 LogUserAction(),结果这个函数依赖了 user.Service,导致整个日志包强耦合用户模块。
go.uber.org/zap),禁止 import 任何业务路径(如 internal/service、domain/model)pkg/ 下每个包执行 go list -f '{{.Imports}}' pkg/log,grep 出现 internal/ 或 domain/ 就报错基础包一旦发布 v1,所有下游服务都会隐式依赖它的 ABI。Go 没有语义导入版本,所以:
go.mod 的 replace 在本地验证 breaking change,但上线前必须删掉pkg/errx.Er
rorDetail() 返回类型属于 breaking change,需升 v2 并新建路径 pkg/errx/v2(Go Modules 规范)log.WithField("trace_id", id) 是安全的;但把 log.Info() 改成接收 context.Context 就不是——已有调用处会编译失败真正难的不是写代码,是守住边界:基础包不是功能集合,而是协议定义者。它越薄、越不可变,项目后期越不容易因为一次“加个简单工具函数”引发雪崩式重构。