结论先行:这条链路就是 Go Web 开发的“标准姿势”,也是你从 PHP 切过来最值得固化的开发习惯。
它本质上是在用“分层”换“可维护性”,代价是前期多写几层,收益是后期改需求不炸。
1. 和 PHP 的直观对照(重点看职责)
| 层 | PHP(Laravel 为例) | Go(Gin 为例) | 核心职责 |
|---|---|---|---|
| 入口 | routes/web.php + Controller |
router.go + Handler |
接请求、校验参数、返回响应 |
| 业务 | Controller 里直接写逻辑 / Service | Service |
业务规则、事务、编排 |
| 数据 | Model / Repository | Repository |
只管 CRUD,不碰业务 |
| 输出 | Blade 模板 / JSON | JSON / html/template |
纯数据 or 渲染 HTML |
👉 PHP 里 Controller 往往又当爹又当妈;Go 里 Handler 只干“收发”的活。
2. 每一层在 Go 里到底干什么?
✅ Handler(对应 PHP 的 Controller 入口)
func GetArticle(c *gin.Context) {id := c.Param("id")article, err := articleService.GetByID(id)if err != nil {c.JSON(500, gin.H{"error": err.Error()})return}c.JSON(200, article)}
只做三件事:
- 解析参数(path / query / body)
- 调用 Service
- 返回 JSON / Template
❌ 不在 Handler 里写 SQL
❌ 不在 Handler 里写业务判断
✅ Service(PHP 里你经常在 Controller 里写的那堆逻辑)
func (s *articleService) GetByID(id string) (*Article, error) {if id == "" {return nil, errors.New("id is empty")}return s.repo.FindByID(id)}
特点:
- 事务控制在这里
- 业务校验在这里
- 可以调用多个 Repository
👉 这是 Go 项目里“最有价值的一层”,也是面试最爱问的。
✅ Repository(对应 PHP 的 Model / DAO)
func (r *articleRepo) FindByID(id string) (*Article, error) {var article Articleerr := r.db.Where("id = ?", id).First(&article).Errorreturn &article, err}
只做:
- 和数据库打交道
- 不关心业务规则
- 不关心谁在调用
✅ JSON / Template(输出层)
- API 项目:
c.JSON(200, data) - SEO 页面:
c.HTML(200, "article.html", data)
👉 这一层越薄越好,不要塞业务逻辑。
3. 为什么 Go 比 PHP 更强调这一套?
| 原因 | 说明 |
|---|---|
| 常驻内存 | 一个全局变量写错,所有请求一起炸 |
| 并发模型 | goroutine 下,耦合代码极难排查问题 |
| 项目寿命 | Go 项目往往跑几年,分层是给“未来的你”看的 |
| 团队协作 | 新人进来,看目录就知道代码该放哪 |
PHP 项目小的时候可以“乱写”,Go 项目一乱,重构成本远高于 PHP。
4. 给你一个“PHP → Go”的落地建议
- 先按这个结构建目录,哪怕项目很小
handler/service/repository/model/router/
- 强制自己:Handler 里不写 SQL、不写 if 业务判断
- Service 之间可以调用,Repository 之间绝不互相调用
- 前期宁可多写一层,也不要图省事塞进 Handler
5. 一句话收个尾
PHP 是“写得爽,后期痛”;Go 是“前期规整,后期稳”。
Handler → Service → Repository这条线,就是你从“PHP 思维”升级到“工程化思维”的分水岭。
