go-zero 的 types 目录,到底干啥的?

每个 go-zero 项目里都有 internal/types/types.go。它装的是什么?为什么偏偏叫 types?是手写的还是生成的?别的框架也这么干吗?

它存啥 为什么叫 types 从哪来 其他框架 都一样吗 实践建议

types 目录存的是什么

一句话:它是 API 层的数据形状(DTO) 集中存放处 —— 所有「请求长什么样、响应长什么样」的结构体。

go-zero 把 .api 文件里你用 type 声明的所有结构体,原样生成internal/types/types.go。这里面没有逻辑、没有方法,纯粹是「数据的样子」:请求体、响应体、以及被多个接口复用的公共结构。

user.api type LoginReq { type LoginResp { goctl api go internal/types/types.go type LoginReq struct { type LoginResp struct { handler / logic 直接引用

生成出来的 types.go 长这样

// 由 user.api 自动生成,不要手改大块逻辑
type LoginReq struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

type LoginResp struct {
    Token string `json:"token"`
    ExpireAt int64  `json:"expireAt"`
}

// 被多个接口复用的公共结构也放这里
type PageReq struct {
    Page int64 `json:"page"`
    Size int64 `json:"size"`
}
✅ 这里该放的
请求/响应结构体、被多个 handler 复用的公共入参/出参、枚举常量(需要的话)。本质是 API 契约的数据部分。
❌ 这里不该放的
数据库模型(那是 model 层)、业务逻辑、配置项、中间件。types 只描述「数据的形状」,不描述「怎么算」。

为什么偏偏叫 "types"

这不是随便起的文件夹名 —— 它直接对应 .api 文件里的 type 关键字。

go-zero 的 API 描述语言里,定义一个数据结构用的就是 type 关键字:
type LoginReq { ... }
goctl 扫描所有 type 声明,把它们翻译成 Go 的 struct,统一吐进一个叫 types 的包。所以文件夹名 = 「这些 type 定义」的归宿,名字和 DSL 关键词一一对应,看着就懂。

对照:.api 里的 type → types.go 里的 struct

# user.api
type LoginReq {
    Username string `json:"username"`
    Password string `json:"password"`
}

// ↓ goctl 生成 ↓

// internal/types/types.go
type LoginReq struct {
    Username string `json:"username"`
    Password string `json:"password"`
}
💡 换句话说,叫 "types" 是 go-zero 对「数据契约」的命名选择:别处可能叫 dto / schema / model / vo,go-zero 因为是从 type 关键字生成,就直白地叫了 types

这个目录从哪来

关键点:types 不是你手敲的,是 goctl api go 一次性生成的。你只改 .api 源文件。

user-api/ ├── user.api ← 你手写/维护这一份 ├── etc/ │ └── user-api.yaml └── internal/ ├── config/ ├── handler/ ← 路由 + handler(生成) ├── logic/ ← 业务逻辑骨架(生成) ├── svc/ ← 依赖容器(生成) └── types/ ← ✅ 数据形状(生成,来自 .api 的 type) └── types.go

生成命令

goctl api go -api user.api -dir .     # 扫描 .api,产出 handler/logic/svc/types
⚠️ 易踩坑:不要直接在 types.go 里加大量手写字段后再跑 goctl —— 重新生成会覆盖。正确姿势是:所有数据形状都在 .api 里用 type 定义,让 goctl 重新生成 types.go。需要给 struct 加方法时,才在 types 包里另写文件(不会被覆盖)。

别的框架也这么干吗

「给 API 数据形状一个专门归宿」是全圈共识,但 命名和是否自动生成 差别很大。

框架 / 语言对应目录 / 文件怎么来的和 go-zero 的 types 像吗
go-zero (Go) internal/types/types.go goctl.api 生成 命名最直白,强约束 + 代码生成
gRPC / protobuf (Go) xxx.pb.go 里的 message protoc.proto 生成 ★ 最像:都是「写 schema → 生成类型」。仅 RPC 场景
Spring Boot (Java) dto/ · vo/ · request/ · response/ 手写类 + Jackson 注解 概念一致,但纯手写、命名更碎、无生成
FastAPI (Python) schemas.py 里的 Pydantic Model 手写,类型提示驱动校验 很像「数据契约」理念,但放在一个 schemas 文件而非 types 目录
Gin / Echo (Go) 就近定义 struct,或 pkg/dto 手写,json/form tag 绑定 没有自动生成的固定 types 目录,放哪都行
net/http (Go 原生) 你自己定,没有约定 手写 完全自由,也完全没规范

都一样吗?—— 目标一致,机制不同

有没有「专门放 API 数据形状的地方」?基本都有。但 go-zero 的 types 有几个独特标签。

🟢 共识的部分
「请求/响应长什么样」必须集中、清晰地定义,这是所有 Web 框架的通用做法。go-zero 的 types 就是这份「数据契约」。
🔵 go-zero 的独特性
① 名字直白绑定 type 关键字;② 自动生成且和 .api 强绑定;③ 和 handler/logic/svc 一起构成「生成即分层」的硬规范。多数其他 Go 框架没有这层代码生成约束。
所以:「放数据形状」大家都一样,但「用 DSL 写、用工具生成、名字叫 types、并强制分层」是 go-zero 的标签。 类比最接近的其实是 gRPC(写 proto → 生成 pb.go),只不过 go-zero 把这套思路搬到了 HTTP API 上。

实践中的几条规矩

1. 所有入参/出参先写进 .api 的 type

改字段只动 .api,然后 goctl api go 重新生成 types.go。别手改生成的文件。

2. 复用结构抽成公共 type

PageReq / IdReq 这种被多个接口共用的,定义一次,多处引用,types 包天然适合承载。

3. 需要方法?另起文件

若想给某个 struct 加校验/格式化方法,在 types 目录新建 types_ext.go 之类文件,goctl 不会覆盖你额外加的源码文件。

4. types ≠ 数据库 model

API 的 LoginReq 和数据库表结构不是一回事。数据库模型在 model 层(go-zero 用 goctl model 从表结构生成),别混进 types。