Go微服务重构核心是按业务域组织代码、小接口解耦、DTO校验剥离、AST安全替换及行为一致性验证。需避免跨服务混乱、模型混用、技术分层错误,确保cmd/变薄、domain可独立测试、DTO仅限协议层。

微服务项目里做 Go 代码重构,不是把单体拆成一堆 service 就完事——真正卡住进度的,是跨服务调用混乱、错误类型不统一、DTO 和 domain 模型混用、测试跑不起来这些具体问题。重构有效与否,看的是 cmd/ 是否变薄、internal/domain/ 是否能独立 go test、api/v1/ 下的 DTO 是否真只负责协议层。
按业务域重组织 internal/ 目录,别再用 services/ repositories/ 这种技术分层
常见错误现象:user/service.go 直接 import order/repository,或者 handler 里 new 一个 db.Conn 实例。
- 每个业务模块(如
user、payment)必须自包含:interface.go(定义本模块对外契约)、handler.go(只依赖本模块 interface)、service.go(不 importdatabase/sql或net/http)、repository.go(只声明 interface,不写实现) - 禁止跨模块引用实现,比如
user/internal/service.go不得 importorder/internal/repository;如需协作,通过user/interface.go中定义的回调函数或事件接口(如UserCreatedEvent)通信 - 公共基础能力(日志、配置、HTTP 工具)统一收口到
internal/pkg/,显式导入;避免用_空导入或全局变量初始化
用小接口解耦,拒绝 UserService.CreateUpdateDeleteSendEmail 这种巨接口
使用场景:HTTP handler 只需要查用户,后台定时任务却要导出 CSV,两者共用一个“全能”接口,导致 mock 成本高、实现臃肿、职责模糊。
- 按调用方视角拆分接口:HTTP 层只需要
UserFinder和UserCreator;导出服务只需要UserExporter - 接口名体现用途而非实体,例如
type UserNotifier interface { Notify(user *User, msg string) error },而不是type UserService interface { ... } - 组合多个小接口:在 struct 中显式声明依赖,如
type userSvc struct{ creator UserCreator; finder UserFinder; notifier UserNotifier },而非继承一个大接口
DTO 校验与转换逻辑必须从 handler 剥离
典型坏味道:c.ShouldBindJSON(&req) 后紧跟一堆 if req.Email == ""、strings.TrimSpace、time.Parse,还夹杂着业务路由判断。
立即学习“go语言免费学习笔记(深入)”;
- 为每个 API 请求体定义专属 DTO(放在
api/v1/下),配套Validate()方法(返回error,不 panic) - 校验失败统一走
c.Error(errors.New("invalid request"))或封装为ErrorResponse,避免 handler 内写c.JSON(400, ...) - 时间格式、金额精度、字段映射等转换逻辑,提取为独立函数(如
dtoToDomainUser()),不在 handler 里做
自动化重构别碰 sed,必须走 AST 解析
错误做法:用 sed -i 's/fmt\.Println/log\.Println/g' 批量替换,结果把注释、字符串字面量、局部变量名全干掉了。
- 真正安全的批量替换必须基于
go/ast+go/parser:先解析为 AST,只匹配ast.CallExpr中Fun为fmt.Println的真实调用节点 - 替换后必须处理导入语句——可用
astutil.AddImport自动补log导入,否则编译失败 - 并发修改多个文件时,别用 goroutine 并发写,建议单 goroutine 顺序处理,或用临时文件 +
os.Rename避免覆盖
最常被忽略的点是:重构后没验证行为一致性。哪怕 AST 替换成功、格式也对了,仍要跑一次 go build 和关键路径的 go test——尤其是跨 service 的错误传递链,比如 user.Create 失败时,order.Place 是否收到预期的 errors.Is(err, user.ErrEmailTaken),而不是泛化的 500 Internal Server Error。


















