DTO 必须按版本声明独立结构体(如V1User/V2User),禁止复用或别名;转换函数需显式调用并按版本隔离;中间件、鉴权、共享服务均须版本分离,确保契约在编译期由类型系统保障。

共用 service 层但必须隔离 DTO 结构体
不同版本的 handler 可以调用同一个 service.GetUser(),但返回值不能直接 JSON encode。v1 和 v2 的响应结构契约不同,哪怕字段名、类型一致,也必须声明为两个独立 struct:比如 V1User 和 V2User。Go 的 encoding/json 不做运行时 schema 校验,共用 struct 会导致 v1 客户端收到 v2 新增字段后 panic(如解析 "nickname" 字段失败)。
- DTO 必须按版本声明,哪怕字段完全一样,也不能用别名或嵌套复用
- 禁止在 v1 handler 中传入
&V2User{...}再靠json:",omitempty"隐式过滤——零值字段可能意外消失,破坏 v1 的必填契约 - 数据库模型(如
GORMUser)只用于存储,不暴露给 API 层
转换函数要显式、不可省略
handler 末尾必须调用明确的转换函数,例如 c.JSON(200, toV1User(dbUser)) 或 c.JSON(200, toV2User(dbUser))。这不是“多此一举”,而是把版本契约边界钉死在代码里。一旦漏掉,底层 model 变更(比如加了 CreatedAt time.Time)会直接穿透到 v1 响应中。
- 转换函数放在
api/v1/convert.go和api/v2/convert.go,按版本目录隔离 - 不要把转换逻辑塞进中间件或全局 hook,那会让 handler 行为不可见、难测试
- 如果用 Protobuf,转换发生在
proto.Message到 domain model 之间,同样需分版本实现
中间件和认证逻辑要按版本拆开注册
别让 v1 和 v2 共享一个 authMiddleware 实例。v2 可能新增双因素校验、scope 检查或 token 解析方式变更。用 gin.Group 或 echo.Group 分组后,必须分别注册中间件:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
v1.Use(authV1Middleware)和v2.Use(authV2Middleware)是两个独立调用 - 中间件内部不应通过
c.Request().URL.Path动态识别版本——分组已保证路径前缀,再判断属于冗余且易错 - 权限逻辑差异大时,建议把鉴权抽成 interface,v1/v2 各自实现,避免中间件里堆 if-else
公共逻辑包不能依赖任何版本特定的类型
如果你把业务逻辑抽成 shared/service 包供 v1/v2 共用,这个包里绝不能 import api/v1 或 api/v2 下的任何 DTO。它只能接收和返回 domain model(如 User),或者用泛型参数化输入输出。
立即学习“go语言免费学习笔记(深入)”;
- domain model 要稳定:字段不删、类型不变、新增字段设默认值
- 共享包不能引入
gin.Context、echo.Context或 HTTP 相关类型,否则就不是“业务逻辑”,而是“HTTP 适配逻辑” - 校验函数(如
Validate())可放在 domain model 包内,但必须纯函数式:只读字段,不调外部服务、不依赖 config

















