Golang模块化开发中的接口定义包设计_API Package模型

1次阅读

接口应由调用方(如service)定义,被依赖方(如repository)实现而不导入调用方包,避免循环引用;api包仅含协议层契约,不含业务逻辑;handler需通过构造函数注入依赖,不硬编码初始化;接口方法名用动词开头并返回带上下文的Error

Golang模块化开发中的接口定义包设计_API Package模型

接口定义该放在哪个包里才不会循环引用

go 模块化中,interface 放错位置是循环导入的头号原因。不是“抽象要提前”,而是“谁消费谁声明”——调用方(比如 handler 或 service)应定义它需要的 Interface,而不是让被依赖方(比如 repository 或 client)提前暴露一接口。

常见错误现象:import cycle not allowed,尤其在把 UserService 接口塞进 model 包、又让 repository 实现它时爆发。

  • service 层需要查用户 → 它定义 UserReader 接口,只含 GetByID(id int) (*User, error)
  • repository 包实现该接口,但不 import service 包;它只 export 实现类型,如 sqlUserRepo
  • main 或 wire 注册时,把 SQLUserRepo{...} 传给 service 构造函数,完成依赖注入

这样做避免了 repository → service 反向依赖,也防止接口膨胀——UserReader 不会因为新增一个导出字段就强制所有实现改写。

API Package 里到底放什么,不放什么

api 包不是“所有对外暴露的东西都扔进来”,它只负责 http/rpc 协议层契约:请求/响应结构、路由绑定、中间件接入点。业务逻辑、校验规则、领域模型都不该出现在这里。

立即学习go语言免费学习笔记(深入)”;

使用场景:你正在用 ginchi 写 REST API,或用 gRPC-gateway 暴露 proto 接口。

  • 放:type CreateUserRequest Struct { Name String `json:"name"` }func RegisterHandlers(r chi.router, h *Handler)Validate() error 方法(仅字段级校验)
  • 不放:user.Create() 调用、auth.CheckPermission()数据库事务控制、自定义错误码映射(那是 transporthandler 层的事)
  • 参数差异:api 中的 struct 字段名和 tag(json/protobuf)必须稳定;内部 domain model 可随时重构,只要转换层适配即可

为什么不要在 api 包里直接 new service 实例

硬编码 svc := user.NewService(user.NewRepo(db)) 在 handler 函数里,等于把初始化逻辑和协议层耦死,测试无法 mock,wire 或 dig 无法接管生命周期,上线后也无法切换实现(比如从 SQL 切到 redis 缓存版 user repo)。

性能影响:每次 HTTP 请求都新建 service,可能重复初始化连接池、日志器、限流器等单例资源。

  • handler 应通过构造函数接收依赖:type UserHandler struct { svc user.Service }
  • cmd/xxx/main.gointernal/di 包中统一 new 并传递,确保单例复用
  • 容易踩的坑:忘记把 context.Context 从 handler 透传到 service 方法,导致超时/取消信号中断

接口方法命名和 error 返回怎么保持一致性

Go 接口方法名不是越短越好,而是要反映调用者视角的动作意图。返回 error 是必须项,但别用 fmt.Errorf("failed to X") 这种模糊包装,更别返回 nil 错误却偷偷吞掉底层失败。

常见错误现象:前端收到 500 Internal Server Error,日志里只有 user service: unknown error,根本没法定位是 DB 连接失败还是 redis timeout。

  • 方法名用动词开头:GetUser()CreateOrder(),不用 UserGetter 这类名词化接口名
  • 错误需携带上下文:return fmt.Errorf("user.Create: failed to insert into db: %w", err),保留原始 error 链
  • 兼容性注意:如果某接口方法新增一个返回值(比如加个 bool 表示是否创建了新记录),所有实现都要改——这违反接口演进原则;此时应新增方法,旧方法保留

真正难的不是定义接口,而是守住边界:当同事说“这个字段我前端急着用,你快加到 api.UserResponse 里”,得立刻想清楚——这是协议层该承担的职责吗,还是该由前端自己拼装?

text=ZqhQzanResources