真正能落地的模块化核心在“隔离”和“可替换”,而非简单拆分:通过 go.mod replace/require 控制依赖、internal 目录实现编译级隔离、统一路由注册与迁移驱动,并用 ci + go vet 强守约束。

单体服务不是不能拆,而是很多人一上来就按业务域画模块、建仓库、搞 RPC,结果越拆越重,接口爆炸、部署耦合、本地调试像破案。真正能落地的模块化,核心不在“分”,而在“隔离”和“可替换”——Golang 的 go.mod + internal + 接口契约,足够支撑中大型服务渐进式解耦。
用 replace 和 require 控制模块边界,而不是靠 Git 子模块
子模块或 mono-repo 工具(如 gofork、git subtree)在 Golang 里容易破坏 go mod tidy 的确定性,且 IDE 跳转失效。正确做法是:每个逻辑模块独立成 repo,但主服务通过 go.mod 的 replace 指向本地路径做开发联调,上线前删掉 replace,走真实版本依赖。
-
replace github.com/org/auth => ./internal/auth:本地开发时强制使用当前目录下的auth模块,IDE 能跳转、go test能跑通 -
require github.com/org/auth v0.3.1:上线构建时拉取 tagged 版本,避免“本地能跑线上炸” - 模块 repo 的
go.mod必须声明module github.com/org/auth,且不 import 主服务的任何包——反向依赖即耦合
internal/ 目录不是放工具函数的垃圾桶,而是“不可导出契约”的物理屏障
Golang 的 internal 不是命名约定,是编译器级约束:A 包无法 import B 包下的 internal/x,除非 A 和 B 在同一 module 根目录下。这比文档、CI 检查更可靠。
- 把领域模型、仓储接口(
Repo)、事件定义全放在internal/domain/—— 它们被多个模块依赖,但绝不暴露实现 -
internal/infra/db只实现domain.UserRepo,不导出*sql.DB或具体 struct;上层只依赖接口,替换 MySQL 为 SQLite 只需改internal/infra下的实现 - 禁止在
internal外写import "xxx/internal/...",否则go build直接报错,这是最硬的解耦开关
HTTP handler 层必须收敛到单一入口,禁止模块自注册路由
常见错误:每个模块自己写 http.HandleFunc("/user/...", ...) 或用 gorilla/mux 自己 router.HandleFunc。后果是路由分散、中间件无法统一、Swagger 注释失效、健康检查路径冲突。
- 主服务的
cmd/api/main.go是唯一路由注册点,所有 handler 通过显式函数参数注入:user.NewHandler(userSvc, logger) - 模块不 import
net/http,只暴露type Handler struct { Svc UserSvc; Log *zap.Logger }和Register(r *chi.Mux)方法 - 若用
gin或echo,模块也只返回func(*gin.Engine),不 new engine、不 listen —— 启动权必须在顶层
数据库迁移和配置初始化必须由主服务驱动,模块只提供 Up/Down 函数
模块自带 goose up 或 migrate -path ./migrations user 看似方便,实则埋雷:多模块迁移顺序错乱、重复执行、rollback 无法回滚跨模块变更。
- 主服务
cmd/migrate/main.go统一收集各模块的migration.Up函数,按依赖顺序执行(例如 auth → order → notification) - 模块 migration 文件放在
db/migration/下,命名带前缀:001_auth_create_users_table.sql,避免序号冲突 - 配置初始化(如 Redis client、gRPC conn)同样由主服务创建并传入模块,模块内部绝不
redis.NewClient(...)—— 否则无法做连接池复用和健康检查
最难的不是写 interface,而是让所有人遵守“模块不 import 其他模块 internal、不启动 HTTP server、不执行 migrate”。这些约束得靠 go vet 自定义检查 + CI 中 grep -r "net/http\|goose\|migrate\|internal/" ./modules/ 来守住,否则三个月后又回到单体泥潭。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











