go.mod 是单体项目的唯一边界,整个项目只能有一个 go.mod 文件,定义根模块路径(如 github.com/yourorg/monolith),所有包均属此模块;禁止在 internal/service 等子目录下执行 go mod init,否则导致构建失败、依赖冲突与 ci 异常。

go.mod 是整个单体的边界,不是子目录的开关
Go 的模块系统不支持“嵌套模块”——你在 internal/service 或 pkg/repository 下执行 go mod init,只会让构建失败,报错类似 main module does not contain package。整个单体项目只能有一个 go.mod,它定义了项目根路径(如 github.com/yourorg/monolith),所有包都属于这个模块。
常见错误是看到目录多就想拆 go.mod,结果导致:
• go build 找不到主包
• 依赖版本冲突无法解析
• CI 流水线反复失败,因为不同子目录声称自己是“独立模块”
正确做法是:用包路径表达职责,而不是靠多个 go.mod 划分。比如:
• internal/handler:只放 HTTP 路由和 request/response 转换
• internal/usecase:封装业务逻辑,调用 domain 和 gateway 接口
• internal/gateway:实现外部依赖,如 DB 查询、第三方 API 调用
• domain:纯结构体、领域接口、校验函数,不 import 任何 infra 库
internal/ 目录才是真正的访问边界
internal/ 不是命名习惯,是 Go 编译器强制的可见性控制机制。任何以 internal/ 开头的路径,外部模块(包括其他本地子模块)都无法 import。这比文档或约定更可靠。
你该把它用在这些地方:
• 所有服务内部实现细节,比如 internal/cache/redis.go、internal/db/sqlx_repo.go
• 防止其他团队或未来微服务误引用你的 DAO 层或 handler 细节
• 拆分时保留重构空间:今天 internal/payment 是一个包,明天可以整体抽成独立服务,只要接口不变,调用方无感
反例:
• 把数据库模型放在 pkg/model 并导出给全项目用 → 后续改表结构要全量扫描所有引用
• 在 cmd/api/main.go 里直接 new 一个 repository.UserRepo → 违反依赖倒置,测试无法 mock
require 块里不该出现其他“服务”的 go.mod 路径
单体项目 go.mod 的 require 区域,只应包含真正第三方依赖(如 github.com/gin-gonic/gin、gorm.io/gorm)和可复用的公共库(如 github.com/yourorg/shared)。它绝不能列出你自己还没发布的“子服务”模块路径。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
为什么?因为:
• 如果你写了 require github.com/yourorg/user-service v0.1.0,但该 repo 还没 tag,go mod tidy 会拉取 latest commit,CI 构建不可控
• 更危险的是:你本地开发时改了 user-service 的代码,但单体里引用的是旧版本,导致行为不一致却查不出原因
替代方案:
• 公共能力下沉为 shared/ 子模块,走语义化版本发布
• 内部跨域调用必须通过接口抽象(如 user.UserReader),实现放在 internal/gateway/userclient/,不暴露具体 transport
• 真要物理拆分时,先确保 proto 契约稳定,再新建 repo + go mod init,而不是在单体里提前 require
go list -m all 能暴露隐藏的耦合
运行 go list -m all 不只是看用了哪些库,它能帮你发现单体里被忽略的强耦合点。比如输出中出现:github.com/yourorg/monolith v0.0.0-20260803120000-abc123def456github.com/yourorg/monolith/internal/order v0.0.0-00010101000000-000000000000
这说明你可能在某个包里写了 import "github.com/yourorg/monolith/internal/order" —— 这是典型的“包路径硬编码”,等于把内部实现细节暴露给了其他模块,破坏了层间隔离。
检查重点:
• 所有 import 语句是否都指向本模块路径下的相对包名(如 "internal/order"),而非完整模块路径
• domain/ 包里有没有出现 "database/sql" 或 "github.com/go-redis/redis/v9"
• internal/handler 是否直接调用了 internal/repository 的 struct 而非其 interface
这种耦合不会在编译时报错,但会让后续拆分变成一场灾难:你以为改的是 usecase 层,结果发现 domain 里藏着 DB 连接池初始化逻辑。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










