
在 go 语言的 mvc 实践中,sql 操作不应直接嵌入模型结构体方法中;正确的做法是将数据访问逻辑分离至独立的数据访问层(如 dao),由控制器协调业务流程,模型仅负责数据结构与领域语义。
在 MVC(Model-View-Controller)架构中,清晰的职责划分是可维护性和可测试性的基石。以用户注册场景为例,若将 SQL 代码直接写在 Form 结构体的方法中(如 func (f *Form) registerUser()),会导致多重问题:模型承担了数据持久化职责,违反单一职责原则;SQL 与业务逻辑紧耦合,难以单元测试;数据库驱动变更时需修改模型代码,破坏封装性。
✅ 正确分层实践如下:
-
Model(模型):仅定义领域实体与核心业务规则。例如:
// model/user.go type User struct { ID uint `gorm:"primaryKey"` Username string `gorm:"uniqueIndex;not null"` Password string `gorm:"not null"` } // 可附加领域校验方法(不涉及数据库) func (u *User) Validate() error { if u.Username == "" || len(u.Username) < 3 { return errors.New("username must be at least 3 characters") } return nil } -
DAO(Data Access Object)或 Repository(数据访问层):SQL 代码的唯一合法归属地。它封装所有数据库操作,屏蔽底层细节(如 SQL、ORM 调用)。推荐独立包管理,例如 pkg/repository:
// repository/user_repository.go type UserRepository interface { Create(*User) error FindByUsername(string) (*User, error) } type GORMUserRepository struct { db *gorm.DB } func (r *GORMUserRepository) Create(u *User) error { return r.db.Create(u).Error // ✅ SQL/ORM 逻辑在此,与业务解耦 } -
Controller(控制器):接收请求、调用模型校验、委托 DAO 执行持久化,并返回响应:
// controller/auth_controller.go type AuthController struct { userRepo UserRepository } func (c *AuthController) Register(w http.ResponseWriter, r *http.Request) { var form Form if err := json.NewDecoder(r.Body).Decode(&form); err != nil { http.Error(w, "Invalid input", http.StatusBadRequest) return } user := &User{Username: form.Username, Password: hash(form.Password)} if err := user.Validate(); err != nil { // ✅ 领域校验 http.Error(w, err.Error(), http.StatusBadRequest) return } if err := c.userRepo.Create(user); err != nil { // ✅ 委托 DAO 执行 SQL http.Error(w, "Registration failed", http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) }
⚠️ 注意事项:
- 避免“贫血模型”陷阱:模型不是纯 DTO,应包含业务不变量(如 Validate()),但绝不含数据库交互。
- DAO 层应面向接口编程,便于替换实现(如从 GORM 切换到 SQLx 或测试 Mock)。
- Controller 不处理 SQL,也不直接操作 *sql.DB;它只依赖抽象接口,确保业务逻辑可脱离 HTTP 环境复用。
- 若项目规模较小,可将 DAO 简化为 pkg/db 下的函数集合,但仍禁止 SQL 出现在 Model 或 Handler 中。
总结:SQL 代码的归宿不是模型,也不是控制器,而是专责数据存取的抽象层(DAO/Repository)。这种分层让模型专注领域、控制器专注流程、DAO 专注数据——三者各司其职,方为 MVC 在 Go 中落地的稳健之道。










