结论先行:这条链路就是 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 入口)

  1. func GetArticle(c *gin.Context) {
  2. id := c.Param("id")
  3. article, err := articleService.GetByID(id)
  4. if err != nil {
  5. c.JSON(500, gin.H{"error": err.Error()})
  6. return
  7. }
  8. c.JSON(200, article)
  9. }

只做三件事:

  1. 解析参数(path / query / body)
  2. 调用 Service
  3. 返回 JSON / Template

❌ 不在 Handler 里写 SQL
❌ 不在 Handler 里写业务判断


✅ Service(PHP 里你经常在 Controller 里写的那堆逻辑)

  1. func (s *articleService) GetByID(id string) (*Article, error) {
  2. if id == "" {
  3. return nil, errors.New("id is empty")
  4. }
  5. return s.repo.FindByID(id)
  6. }

特点:

  • 事务控制在这里
  • 业务校验在这里
  • 可以调用多个 Repository

👉 这是 Go 项目里“最有价值的一层”,也是面试最爱问的。


✅ Repository(对应 PHP 的 Model / DAO)

  1. func (r *articleRepo) FindByID(id string) (*Article, error) {
  2. var article Article
  3. err := r.db.Where("id = ?", id).First(&article).Error
  4. return &article, err
  5. }

只做:

  • 和数据库打交道
  • 不关心业务规则
  • 不关心谁在调用

✅ 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”的落地建议

  1. 先按这个结构建目录,哪怕项目很小
    1. handler/
    2. service/
    3. repository/
    4. model/
    5. router/
  2. 强制自己:Handler 里不写 SQL、不写 if 业务判断
  3. Service 之间可以调用,Repository 之间绝不互相调用
  4. 前期宁可多写一层,也不要图省事塞进 Handler

5. 一句话收个尾

PHP 是“写得爽,后期痛”;Go 是“前期规整,后期稳”。
Handler → Service → Repository 这条线,就是你从“PHP 思维”升级到“工程化思维”的分水岭。