应使用 interface 抽象外部依赖并结合依赖注入替代硬编码客户端,将 UserServiceClient 等接口与 HTTP 实现分离,业务层仅依赖接口;中间件、工具函数、domain model 和健康检查等需统一提取为可版本化管理的独立 module,严格约束共享边界与变更流程。

用 interface + 依赖注入替代硬编码 client
微服务间调用容易写成直接 new HTTP client 或直接调用 SDK,导致测试难、替换难、耦合高。核心是把外部依赖抽象为 interface,再通过构造函数或配置传入。
- 不要在业务逻辑里写
http.DefaultClient.Do(req),定义如type UserServiceClient interface { GetUser(ctx context.Context, id int) (*User, error) } - 实现类(如 HTTP 实现)只负责协议细节,不掺杂业务判断;业务层只依赖 interface
- 用结构体字段接收依赖,而非全局变量或单例:比如
type OrderService struct { userClient UserServiceClient } - 测试时可轻松传入 mock 实现,避免启动真实服务或打网络请求
提取通用中间件和工具包到独立 module
日志、链路追踪、重试、限流这些逻辑若散落在各服务里,版本不一致、行为不统一,复用就变成复制粘贴。必须拆成可 version 控制的 Go module。
- 新建
github.com/yourorg/go-middleware,导出Recovery()、TraceID()等标准中间件函数 - 避免用
init()注册全局 handler,所有中间件应返回func(http.Handler) http.Handler - 工具函数如
ParseJSONBody()、ExtractCtxValue()要带 context 支持,别用map[string]interface{}做通用解包 - module 的 go.mod 必须明确 require 其他基础库(如
go.opentelemetry.io/otel),防止下游服务因版本冲突编译失败
共享 domain model 但禁止跨服务直接 import struct
多个服务操作同一类实体(如 User、Order)时,不能让服务 A 直接 import 服务 B 的 models.User。这会制造隐式依赖,破坏服务边界。
- 建独立 repo 如
github.com/yourorg/go-domain,只放纯 struct + JSON tag + validator tag,不带方法、不依赖框架 - 每个服务用自己的 package 引入该 module,并做必要 alias 或 wrapper:比如
type Order = domain.Order,而非直接嵌套使用 - 禁止在 domain 包里加
Save()、Validate()这类带逻辑的方法;验证规则应由各服务按需实现 - 如果字段变更(如新增
email_verified bool),先发 PR 到 domain repo,CI 验证所有依赖服务能构建通过,再合并
用 embed + template 构建可复用的健康检查与指标 endpoint
每个服务都写一遍 /health 和 /metrics,容易漏字段、格式不一致、Prometheus label 写错。不如把模板和默认逻辑固化下来。
- 在公共工具包中用
//go:embed health.html加载静态响应模板,用html/template渲染状态、版本、依赖服务连通性 - 暴露
RegisterHealthHandler(mux *http.ServeMux, checker HealthChecker),让各服务只提供func() error检查逻辑 - 指标 endpoint 不要自己拼
promhttp.Handler(),而是封装RegisterMetricsHandler(mux *http.ServeMux, reg *prometheus.Registry),确保 registry 可被注入 - 注意
embed文件路径必须是相对当前文件的固定路径,不能用变量拼接,否则 build 时找不到

















