本文以 GitHub 仓库 big‑dudu‑mosty/go_zero_test (
postgres分支)为蓝本,详细拆解如何在现有 go-zero 单体示例上完成数据库迁移、ORM 替换,以及微服务拆分。你可以参考该仓库的 commit 历史 (commits/postgres)逐阶段对照学习。
一、整体演进路线回顾
这个项目主要分为几个阶段:
-
MySQL + sqlx 单体版本(项目初始阶段)
-
切换到 PostgreSQL + GORM(实现持久化层升级)
-
拆分微服务 :拆为
user-api(HTTP)和user-rpc(RPC 服务) -
RPC 通信:API 层通过 zrpc 调用 RPC 服务完成业务
-
整理 go-zero 结构:ServiceContext、proto、goctl 等配合使用
通过这些阶段,你可以理解:为什么要迁移数据库、为什么用 GORM、为什么拆服务,以及拆完怎样组织代码。
⚡ 想跟着实践的朋友,可以直接访问源码:big-dudu-mosty/go_zero_test
仓库里 commit 历史清晰,适合按阶段学习和调试。如果觉得教程有帮助,顺手点个 Star ⭐ 支持一下,让我继续更新更多 go-zero 实战内容。
二、如何使用 commit 历史学习( strongly 推荐这样做)
在你的仓库里,postgres 分支的 commit 历史非常清晰。建议读者:
-
git clone后切换到postgres分支git clone https://github.com/big-dudu-mosty/go_zero_test.git cd go_zero_test git checkout postgres -
使用
git log --oneline或者图形化工具(如 GitKraken、GitHub web 界面)查看每一个 commit。-
你可以看到某些 commit 明确标注为 "switch to postgres"、 "add GORM model"、 "split service into api + rpc" 等。
-
建议读者 按 commit 顺序执行或阅读,这样就像在做一个分阶段的实践,而不是一次性看最终代码。
-
这样做有几个好处:
-
能明确哪些改动是为了数据库迁移,哪些是微服务拆分。
-
有助于理解改动背后的 "为什么" ------ 而不是只看最终状态。
-
如果自己按类似路径改造自己的项目,也可以照着历史一步步来。
三、阶段详解与关键技术点、易错点
下面我按阶段拆解整个演进过程,并强调每一步常见坑、注意事项。
3.1 阶段一:MySQL + sqlx 单体 Demo(起点)
-
初始结构:只有一个服务(单体 app),Handler → Logic → Model → sqlx 操作 MySQL。
-
这个阶段是稳定基础,也是开发者理解业务逻辑、接口定义和 go-zero 架构分层的基础。
注意点:
-
sqlx 的查询语法比较原始,自写 SQL 时容易拼错占位符(
?)。 -
sqlx 插入并获取主键常用
LastInsertId(),但这个在切换到 PostgreSQL 后会变成问题。 -
如果初始数据模型设计不合理(如缺乏
created_at/updated_at字段),后面 ORM 转换时可能得补表结构。
3.2 阶段二:切换到 PostgreSQL + GORM
这是一个非常关键但容易出错的阶段。下面是详细拆解:
3.2.1 修改配置
-
更新 go-zero 配置文件(比如
user-rpc/etc/user.yaml)中的数据库连接字符串为 Postgres,比如:DataSource: "postgres://user:password@localhost:5432/dbname?sslmode=disable" -
注意:
sslmode设置、端口、用户名密码等都必须和你的 Postgres 本地 / 容器环境对应。
3.2.2 GORM 引入和 Model 层重构
-
创建 GORM
*gorm.DB实例,并在 ServiceContext 中注入。 -
重写
model/层,把原 sqlx CRUD 迁移到 GORM。-
对象定义(struct)要带上 GORM tag,例如
gorm:"column:id;primaryKey;autoIncrement"。 -
插入时,GORM 默认会处理
RETURNING id。 -
查询方法(Find / First / Where)要写清楚条件。
-
常见坑:
-
大小写问题 :GORM 对字段名和数据库列默认会做一定映射,但如果你手动写了
column:tag,一定要对照 Postgres 表定义。 -
占位符问题 :在纯 SQL 自定义查询里(如果有 Raw SQL)要注意 Postgres 占位符是
$1, $2...,不能继续写?。 -
事务处理 :GORM 的事务和 go-zero 的上下文结合时,要确保在逻辑层正确传递
*gorm.DB,不要误用全局 DB。
3.2.3 数据迁移与兼容
-
如果你从 MySQL 迁移真实数据到 Postgres,需要手写迁移脚本 (SQL) 或者使用迁移工具。你可以在项目里查看或扩展迁移逻辑。
-
测试迁移后 CRUD 接口是否正常 ------ 建议编写集成测试,用真实 Postgres 实例来跑。
3.2.4 commit 回顾
- 在 commit 历史中,你会看到为了这一阶段引入的 commit ------
switch to postgres、add GORM model等。读者可以定位这些 commit 来理解每一步改动。
3.3 阶段三:微服务拆分 --- API 与 RPC
这一步是构建真正 "微服务 + go-zero 标准结构" 的关键。
3.3.1 拆分服务目录
-
user-api/:只负责 HTTP 接收请求 → 验证 / 解析 → 调用 RPC Client。 -
user-rpc/:负责业务逻辑 + 数据访问 + GORM + 持久化。
目录结构类似:
user-api/
internal/handler
internal/logic
internal/svc
user-rpc/
internal/logic
internal/svc
model/
proto/user.proto
3.3.2 定义并生成 RPC 接口
-
在
user-rpc/user.proto中定义 RPC 方法,例如CreateUser,GetUser,ListUsers等。 -
用 go-zero 的
goctl生成代码:goctl rpc protoc user.proto --go_out=./internal --go-grpc_out=./internal --zrpc_out=. -
生成后的代码会包含服务端和客户端 stub、ServiceContext scaffold。
注意点 / 易错点:
-
.proto文件定义时要考虑输入输出结构是否合理,例如分页查询要带limit/offset。 -
goctl rpc protoc时路径必须在服务模块根目录下,否则生成路径可能混乱。 -
每次改
.proto后重新生成代码,不要手动修改生成文件中的业务逻辑。
3.3.3 在 user-api 调用 RPC
-
在 API 层 logic 中注入 RPC client,例如:
resp, err := l.svcCtx.UserRpc.CreateUser(l.ctx, &userRpc.CreateUserReq{ Name: req.Name, Age: req.Age, }) -
API 层不再访问数据库,不再依赖 GORM / sqlx 逻辑。职责清晰。
注意:
-
错误处理非常关键。RPC 返回错误时,API 要做恰当转换(HTTP 状态码 + 返回内容)。
-
上下文(context)要传递好(例如
l.ctx),以便在 RPC 层做超时、取消。
3.3.4 启动脚本与设置
-
你的仓库中有
start-services.sh/stop-services.sh,读者可以用它启动整个系统(API + RPC)。 -
启动前请检查配置文件(API 和 RPC 分别有自己的 etc 配置 yaml),确认数据库连接、RPC 端口、日志等配置正确。
四、易错点 & 实践建议(经验总结)
下面是从这个项目 +微服务 +迁移实践中提炼出的 踩坑建议:
-
搞错占位符
-
sqlx 到 GORM 时,如果还写原来 sql 部分,自定义 SQL 中可能误用
?,导致 Postgres 报错。 -
推荐对 Raw SQL 使用
$1, $2...,或者尽量避免 Raw SQL,优先使用 GORM API。
-
-
ID 插入与返回
-
PostgreSQL 插入记录后,如果你想拿到 ID,一定要使用
RETURNING id。GORMCreate默认能处理,但如果用Exec、Raw等要注意。 -
在事务中插入多个表时,要处理事务提交失败 /回滚。
-
-
上下文传递失误
-
在 go-zero ServiceContext 和 logic 里,不要丢掉
ctx,否则你无法控制 RPC 超时,也不利于 trace。 -
RPC client 和 server 之间要有合理的超时策略。
-
-
proto 更新不及时
-
每次你改用户结构(比如新增字段)后,proto 也要同步更新。否则 API 与 RPC 结构不匹配。
-
生成代码后,不要手动修改生成的
.pb.go,业务逻辑都写在internal/logic。
-
-
依赖注入 & 初始化
-
ServiceContext 要注入 GORM DB 连接、RPC Client、配置对象。
-
启动初始化顺序要正确:RPC 服务启动时先连接数据库、迁移(如需要),然后才开始监听;API 服务启动时,要确保 RPC 客户端能连接。
-
-
测试覆盖
-
建议写集成测试:启动 Postgres(可以用 Docker)、启动 RPC 服务、调用 API 接口,验证整个链路。
-
针对 GORM Model 层写单元测试,对 CRUD 各种边界条件(空字段、重复、分页)都覆盖。
-
-
版本控制策略
-
用好仓库的 commit 历史。读者可以通过
git checkout <commit>返回到某个阶段来调试 /观察。 -
推荐你在每个关键阶段 commit 时写清晰且有意义的 commit message(例如 "migrate to Postgres", "introduce GORM models", "split into RPC service")。
-
五、后续可扩展方向(对读者的建议)
在完成上述迁移和拆分之后,这个项目已经非常有示范意义。接下来你还可以考虑:
-
集成 分布式链路追踪(如 Jaeger / Zipkin),让 API → RPC 调用可视化
-
引入 限流 / 熔断 / 断路器(go-zero 本身支持)
-
实现 鉴权(JWT / OAuth),在 API 层做认证,RPC 层做权限校验
-
数据库迁移管理:使用迁移工具(如
golang-migrate、pgmigrate)持续管理 Postgres schema -
增加 监控(Prometheus + Grafana):监控 RPC 调用次数、延迟、错误率等
-
支持 分页 / 搜索、复杂查询、事务、批量操作
六、小结
-
通过仓库(
postgres分支)和 commit 历史,我们可以 系统地学习 go-zero 项目的演进:从单体 MySQL → PostgreSQL + GORM → 拆分 API + RPC。 -
每一步都有技术要点和容易出错的地方:数据库语法差异、ORM 模型设计、RPC 接口定义、上下文管理等。
-
建议读者强烈依赖 commit 历史,按阶段对照阅读和实践,这样理解最透彻。
-
这个项目不仅适合 go-zero 初学者,也适合中级开发者用来练微服务拆分、架构升级。