拆微服务真正的卡点在于依赖解耦与部署单元定义,而非新建仓库;单体Go中DB连接、Redis客户端、配置初始化等强依赖共享资源的模块不应直接拆分,需先厘清初始化依赖图并用接口抽象隔离跨域调用,再通过多cmd入口共仓演进。

拆微服务不是起新 repo 就完事,真正的演进卡点在依赖解耦和部署单元定义上。
单体 Go 里哪些模块不该拆?
很多团队一说“要微服务”,立马建 user-svc、order-svc 新仓库,结果发现 DB 连接、Redis 客户端、配置初始化全绑在同一个 main.go 里——这些模块物理上根本没法分离。
- 先画出当前启动时的初始化依赖图:
db.Init()→redis.NewClient()→service.NewOrderService()→handler.RegisterRoutes(),标出哪些环节强依赖共享资源 - 如果
user.Service直接调用了order.Repository(比如查订单状态),说明领域边界没划清,得先用接口抽象(如order.OrderReader)隔离,再考虑进程级拆分 - 一个
go mod不等于一个服务;可用多cmd/入口(cmd/userapi/main.go、cmd/usersvc/main.go)共仓管理,二进制分离构建,这是最轻量的演进起点
HTTP 调用下游服务时最容易写的错代码
硬编码地址 + 原生 http.Get 是典型反模式,本地联调失败、线上无法熔断、超时控制失效,全是它惹的祸。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 错误写法:
http.Get("http://order-svc:8080/v1/order/123")—— 地址写死,无重试、无上下文超时、无法 mock - 正确做法:封装成 client 接口,例如
type OrderClient interface { GetOrder(ctx context.Context, id string) (*Order, error) } - client 实现里用
http.Client或 gRPC stub,初始化交给 DI(如dig.In或构造函数注入),不要在 handler 里 new - 生产环境必须走服务发现(
Consul+go-micro/registry/consul)或 DNS SRV,避免把order-svc:8080放进 config.json
gRPC 和 HTTP 共存时 proto 文件怎么管才不踩坑?
把所有 .proto 放进一个 api/ 仓库,各服务都 go get 它,看似统一,实则制造了新的单点故障——改个字段就得全链路发版。
立即学习“go语言免费学习笔记(深入)”;
- 按限界上下文划分 proto 仓库:用户上下文的
user.proto、订单上下文的order.proto,各自独立版本 - 服务只依赖自己需要的 proto,生成代码放在
internal/pb/下,不暴露给其他模块 - 跨上下文通信优先用 HTTP + JSON(如
/v1/orders?user_id=123),而非直接 import 对方的 pb 类型;类型转换在 client 层做,不污染 domain 层 - 如果必须用 gRPC,用
protoc-gen-go-grpc生成 server stub,但 client 接口仍保持抽象,实现可替换为 HTTP fallback
真正卡住演进的,从来不是技术选型,而是每次拆服务前要不要动那行 db := sql.Open(...) —— 它连着多少包、是否被多个 service 共享、有没有隐藏的事务传播逻辑。这些细节不厘清,起再多新进程也只是套壳单体。

















