Revel框架默认目录结构不强制三层,但易导致分层混乱;推荐手动添加app/services/和app/repositories/,明确Controller只调度、Service封装业务流程与事务、Repository专注数据访问、Models仅作纯数据结构。

Revel 框架本身不强制你写成 Controller/Service/DAO 三层,但它的目录结构天然支持分层,关键在于你能不能把“机制逻辑”从“业务逻辑”里拎出来——否则 app/controllers 里很快就会塞满数据库操作、校验、日志、事务控制。
为什么 Revel 的默认结构容易导致分层混乱
Revel 的 app/ 目录下默认只有 controllers/、models/、views/、routes/,没有 services/ 或 repositories/。这会让开发者误以为“模型就是业务”,结果把订单创建、库存扣减、通知发送全塞进一个 OrderController 里。
常见错误现象包括:
-
app/controllers/OrderController.go中直接调用db.Exec和redis.Set -
app/models/Order.go里混着业务规则(如“用户等级 ≥ 3 才能使用优惠券”)和字段定义 - 同一个校验逻辑(如手机号格式、金额范围)在多个 controller 里 copy-paste 了 5 遍
推荐的四层物理结构与职责边界
别改 Revel 的目录名,就在它允许的范围内建子目录,让结构说话:
-
app/controllers/:只做三件事——接收请求、调用service、返回响应。不碰 DB、不写校验、不处理事务 -
app/services/(需手动建):放所有跨资源的业务流程。比如CreateOrderService里协调用户服务、库存服务、支付服务,统一控制事务边界 -
app/repositories/(需手动建):只封装数据访问。每个 repo 对应一张表或一个聚合根,方法名用FindById、Save,不带业务语义 -
app/models/:纯数据结构体,字段映射 DB 表或 API 请求体,不含方法(尤其不能有Validate()这种业务逻辑)
性能影响很小,但能避免 revel build 时因循环 import 报错——比如 controller 引 service,service 引 repository,但 repository 绝对不能反引 controller。
怎么让 Service 层真正起作用,而不是变成“Controller 壳子”
Service 不是加个目录就完事。它必须满足两个硬条件:
- 接收明确的输入参数(DTO 类型),不依赖
*revel.Controller或 HTTP 上下文 - 返回值是业务语义明确的结果(如
OrderResult{ID:123, Status:"created"}),不是error或map[string]interface{}
举个反例:func (c App) CreateOrder() revel.Result 是 controller 方法;正例是:func (s *OrderService) Create(req CreateOrderRequest) (CreateOrderResponse, error)。后者才能被单元测试覆盖,也才能被其他入口(比如定时任务、gRPC)复用。
routes.conf 里暴露的路径,应该对应哪一层
conf/routes 只绑定 controller 方法,这是 Revel 的约定,改不了。但你可以控制 controller 的厚度:
- 路由行写
POST /api/orders CreateOrder,对应app/controllers/AppController.CreateOrder - 这个方法体内只做三行:
req := parseRequest(c.Params)→resp, err := service.Create(req)→return c.JSON(201, resp) - 所有中间件(鉴权、幂等、审计)统一放在
app/init.go的FilterConfig里,别在 controller 里手写if !c.User.IsAdmin
最容易被忽略的一点:Revel 的 app/models/ 下如果放了带方法的 struct(比如 Order.Validate()),它会被自动注入到模板中——这意味着业务逻辑悄悄泄漏到了 view 层,后续想换前端框架或加 GraphQL 接口时,会发现校验规则根本抽不出来。











