Go语言中MVC不是框架强制约束,而是开发纪律:Controller仅负责路由分发与响应包装,Model应含业务行为而非仅数据结构,View在API服务中退化为序列化逻辑,需通过物理隔离与职责解耦实现真正MVC。

Go 语言本身没有强制的架构约束,Gin 作为轻量 HTTP 框架,天然不绑定 MVC;但你可以用它“模拟”出经典 MVC 的职责边界——关键不是 Gin 支持与否,而是你是否把 Controller、Model、View 的代码物理隔离、逻辑解耦。
Controller 层该只做路由分发和响应包装
很多人把数据库查询、字段校验、状态判断全塞进 c.GET 的 handler 函数里,这已经不是 Controller,是“上帝函数”。真正的 Controller 应该只干三件事:解析输入、调用 Service/Repository、封装输出。
- 不要在
controller/user.go里直接写db.Where(...).Find(&u),那是Repository的事 - 不要在 handler 里做密码哈希、权限检查、业务规则判断,这些属于
Service层 -
c.JSON(200, u)可以保留,但错误响应建议统一走Response封装(比如response.Fail(400, "用户名已存在"))
Model 不等于 struct,更不等于 ORM 实体
很多项目把 type User struct 放在 model/ 目录下就宣称“MVC 完整”,这是误解。Model 的本质是领域对象,它应包含行为(方法),而不仅是数据容器。
- 纯数据结构(如
User)更适合叫DTO或Entity,放在entity/或pojo/ - 真正 Model 应该有业务方法,比如
user.IsExpired()、user.CalculateDiscount(),这些逻辑不应出现在 Controller 或 Service 中 - 如果用 GORM,
gorm.Model是 ORM 映射辅助,不是领域 Model;别让GormTable字段污染业务 Model
View 在 API 服务中可完全省略,但别误删模板能力
现代 Go 后端绝大多数是 JSON API,View 层实际退化为序列化逻辑。但这不意味着你要删掉 router.LoadHTMLGlob() 或弃用 c.HTML()。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 若项目含管理后台或邮件模板,
view/目录仍有价值,用html/template或pongo2渲染即可 - 避免在 HTML 模板里写业务逻辑(比如
{{ if eq .Status 1 }}...),状态判断应前置到 Controller 或 Service - 模板路径建议用相对路径加载,比如
router.LoadHTMLGlob("view/**/*"),而非硬编码绝对路径
Gin 路由注册方式直接影响 MVC 可维护性
用 router.GET("/user", userHandler) 这种写法,很快就会陷入“路由散落在各处、handler 找不到归属”的混乱。必须让路由与 Controller 绑定清晰。
- 推荐按模块分组注册:
userRouter := router.Group("/api/v1/users"),再挂userRouter.GET("", uc.List) - Controller 方法签名统一为
func(*gin.Context),但内部只调用userService.List(c),不碰c.Request原始字段 - 避免匿名函数写法:
router.GET("/x", func(c *gin.Context) { ... })—— 这类代码无法单元测试,也不属于任何 Controller 文件
最难的部分不是目录怎么建,而是每次写新接口时,能否忍住不在 Controller 里写 SQL、不把业务规则当 if-else 塞进去。MVC 在 Go 里不是框架特性,是开发纪律。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










