Beego项目应按业务域而非技术层拆分,controllers/和models/下设子包如order/、auth/,路由用前缀注册并分散到各域router.go,ORM操作移至services/层,model仅定义结构,错误与配置集中管理,service函数必须带context.Context。

Beego 项目一旦脱离 demo 阶段,控制器堆在 controllers/、模型混在 models/、路由全塞进 router.go,很快就会变成“改一处、查八处、不敢删”的状态。核心问题不是 Beego 不支持分层,而是默认结构没强制约束,开发者容易忽略职责边界和依赖流向。
按业务域拆分 controller 和 model,而非按技术层硬切
很多人照着 MVC 目录名机械建包:所有用户相关逻辑塞进 controllers/user.go 和 models/user.go,结果 UserController 里调 OrderModel、PaymentModel,还顺手写了个发邮件的工具函数——这已经不是 MVC,是 M×V×C×Everything。
- 真正可维护的拆分是以「业务动作」为单位,比如
order域下包含order/controllers(含创建、取消、查询)、order/models(订单主表、子表、状态机)、order/services(校验库存、扣减余额等跨模型逻辑) -
controllers/下不再放裸 struct,而是每个业务域一个子包,如controllers/order、controllers/auth;models/同理,避免全局import "models" - Beego 的
Router支持前缀注册,beego.Router("/api/orders", &order.Controller{})比beego.Router("/api/orders", &controllers.OrderController{})更清晰,也方便后续提取为独立微服务
把 ORM 操作从 controller 移到 service 层,model 只负责结构定义
常见错误是 UserController.Get 里直接写 o.QueryTable("user").Filter("id", id).One(&u),导致 controller 承担数据获取、校验、组合、响应组装全部职责,测试难、复用差、事务难控。
-
models/目录只放 struct 定义和orm.RegisterModel调用,不写任何查询逻辑 - 所有数据库操作收口到
services/包下,例如services/user.go提供GetUserByID(ctx, id)、CreateUser(ctx, u *models.User),内部用orm或原生sql.DB - controller 只做三件事:解析参数、调 service 方法、写响应。这样单元测试时可直接 mock service,不用启 DB
router.go 不要写满路由,用 init 函数按域注册
把几十条 beego.Router(...) 全堆在 router.go,会导致每次加接口都要开这个文件,冲突概率高,也看不出路由归属。
- 在各业务域目录下放
router.go,例如order/router.go写func init() { beego.Router("/api/orders", &order.Controller{}) } - 主
router.go只保留基础配置(如beego.BConfig.WebConfig.Session.SessionOn = true)和全局中间件注册,不写具体路由 - 注意 Beego v2 的
beego.Router是包级函数,init 执行顺序不可控,所以不要在 init 里依赖其他包的变量初始化完成;如有依赖,改用显式调用注册函数,比如order.RegisterRoutes()
config 和 error 定义必须集中管理,禁止散落各处
线上排查时最头疼的是看到 errors.New("db connect failed") 出现在五个不同文件里,日志搜不到统一标识,监控打点也漏掉。
- 新建
pkg/config存配置加载逻辑,用beego.AppConfig.String("mysql::host")封装,避免 controller 里硬写 key - 新建
pkg/errno定义错误码常量,如ErrUserNotFound = errors.New("user not found"),并提供Wrap(code, err)方法统一注入 traceID - 所有 HTTP 错误响应走统一
ErrorResponse结构体,由中间件拦截error类型返回,controller 里只管return ErrUserNotFound
Beego 本身不阻止你把所有代码写进一个文件,但它的 Controller 嵌入机制、Router 的灵活注册、AppConfig 的分环境能力,其实都在暗示一种更松耦合的组织方式。最容易被忽略的点是:service 层的函数签名要不要带 context.Context?答案是必须带——哪怕你现在没用超时或 cancel,因为 Beego 的 Controller 本身有 c.Ctx.Request.Context(),不带 context 的 service 未来加链路追踪就只能重写。


















