
在 go 的 mvc 架构实践中,sql 数据访问逻辑不应直接嵌入模型结构体方法中,而应通过分层解耦——将数据操作封装在独立的数据访问层(如 dao 或 repository),由 controller 协调调用,确保模型纯净、职责单一。
MVC 并非 Go 官方强制规范,而是一种指导性架构思想。在实际工程中,生硬套用“Model = 结构体 + SQL 方法”会迅速导致模型膨胀、测试困难、数据库耦合严重。正确的分层策略如下:
✅ 推荐分层结构(Go 风格)
| 层级 | 职责 | 示例 |
|---|---|---|
| Model(领域模型) | 纯数据结构,无业务或 SQL 逻辑;仅定义领域实体及其约束 | type User struct { ID int; Username string; PasswordHash string } |
| Repository / DAO(数据访问层) | 封装所有 SQL 操作,依赖数据库驱动(如 database/sql 或 sqlx),返回 Model 实例 | func (r *UserRepo) Create(ctx context.Context, u *User) error |
| Controller(协调层) | 接收请求、校验输入、调用 Repository、组装响应;不包含 SQL 字符串 | func (c *AuthController) Register(w http.ResponseWriter, r *http.Request) |
? 反模式示例(应避免)
// ❌ 错误:SQL 泄漏到 Model 中,违反单一职责
type Form struct {
Username string
Password string
}
func (f *Form) registerUser() {
// 直接写 SQL —— 模型污染、无法单元测试、难以切换数据库
db.Exec("INSERT INTO users ...")
}✅ 正确实现示例(基于 sqlx)
// model/user.go —— 纯结构体,零依赖
type User struct {
ID int `db:"id"`
Username string `db:"username"`
PasswordHash string `db:"password_hash"`
}
// repository/user_repo.go
type UserRepo struct {
db *sqlx.DB
}
func NewUserRepo(db *sqlx.DB) *UserRepo {
return &UserRepo{db: db}
}
func (r *UserRepo) Create(ctx context.Context, u *User) error {
const query = `INSERT INTO users(username, password_hash) VALUES($1, $2) RETURNING id`
return r.db.QueryRowContext(ctx, query, u.Username, u.PasswordHash).Scan(&u.ID)
}
// controller/auth_controller.go
type AuthController struct {
userRepo *UserRepo
}
func (c *AuthController) Register(w http.ResponseWriter, r *http.Request) {
var form struct {
Username string `json:"username"`
Password string `json:"password"`
}
json.NewDecoder(r.Body).Decode(&form)
// 密码哈希(业务逻辑)
hash, _ := bcrypt.GenerateFromPassword([]byte(form.Password), 12)
user := &User{
Username: form.Username,
PasswordHash: string(hash),
}
// 调用 Repository —— SQL 完全隔离
if err := c.userRepo.Create(r.Context(), user); err != nil {
http.Error(w, "Registration failed", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]int{"user_id": user.ID})
}⚠️ 关键注意事项
- 模型 ≠ 数据访问器:User 是数据载体,不是数据库操作者;它的方法应仅限于领域行为(如 user.IsAdmin()),而非 user.SaveToDB()。
- DAO/Repository 二选一:DAO 更贴近底层 SQL(适合简单项目),Repository 更强调领域语义(如 FindActiveUsersByMonth()),推荐初学者从 Repository 入手。
- 依赖注入优于全局 DB 句柄:Controller 应通过构造函数接收 Repository 实例,便于测试与替换(如 mock 数据库)。
- 事务与上下文传递:SQL 操作需支持 context.Context(超时/取消)和可选事务(*sql.Tx),Repository 方法应设计为可组合。
✅ 总结
将 SQL 移出模型、交由独立 Repository 管理,是 Go MVC 工程化的基石。它带来三大收益:① 模型可复用(同一 User 可用于 HTTP、CLI、gRPC 多种场景);② 数据层可单独测试(无需启动 Web 服务);③ 数据库可替换(如从 PostgreSQL 迁移至 SQLite 仅需重写 Repository 实现)。真正的 MVC 弹性,始于清晰的边界划分。










