Service 层沦为"高级搬运工"之后,我把分层逻辑重新捋了一遍

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
站长杂谈 132 浏览 0 回复

上周重构一个老项目,Controller 平均 800 行,Model 里藏着 SQL 拼接、缓存逻辑、甚至发邮件。拆完之后 Service 层倒是有了,但仔细一看——好家伙,Service 就是换了个地方写 `Db::table()->where()->select()`,跟 Controller 的区别只在于少了一层 `return json()`。

这种"伪分层"坑了我两次。第一次是订单模块,Service 里直接 `Db::name('order')->where('status', 1)->update(['status' => 2])`,后来要加库存回滚,发现 Service 已经耦合了具体表结构,改起来跟拆炸弹似的。第二次更离谱,两个 Controller 调同一个 Service 方法,一个要关联用户详情,一个不要,Service 干脆搞了个 `$withUser = false` 的参数开关,越传越多,最后变成俄罗斯套娃。

这次重构我换了个思路:按"用例边界"拆,而不是按"代码位置"拆。

Controller 只干三件事

验参、调 Service、格式化输出。以前我会在 Controller 里 `if ($user['vip'] == 1)`,现在这种判断全部下沉。Controller 甚至不应该知道有 `vip` 这个字段存在,它只认 `OrderService::canPlaceOrder($userId)` 返回的布尔值。有个取巧的检验标准:如果单元测试 Controller 时需要 mock 数据库,说明它越界了。

Service 按业务用例组织,不是按表

以前我的 Service 叫 `UserService`、`OrderService`,现在改成 `PlaceOrderService`、`CancelOrderService`。一个 Service 只负责一个完整的业务动作,内部可以跨表,但对外屏蔽细节。比如 `PlaceOrderService` 里会调 `OrderModel` 建单、`InventoryModel` 扣库存、`PointModel` 加积分,但这些调用方都不感知。

关键规则:Service 之间不互相调用。需要复用的逻辑抽成 `Domain` 或 `Helper`,避免循环依赖。我之前 `OrderService` 调 `PayService`,`PayService` 回调又调 `OrderService`,最后靠事件解耦,但事件多了又成隐形调用链,排查起来更烦。

Model 回归数据网关

我的 Model 现在只干:定义关联、写 scope、封装原子查询。复杂业务查询?去 Repository 或者直接在 Service 里用查询构造器,但 Model 本身不碰业务。有个例外是软删除、全局状态过滤这种横切逻辑,放 Model 的 `baseQuery` 或 `onBeforeSelect` 里。

以前试过在 Model 里加 `afterInsert` 自动发通知,结果批量导入时触发几百条队列,差点把 Redis 打爆。现在这种副作用全部提到 Service 层显式调用,宁可多写两行,也要一眼能看到哪里会"炸"。

拆完后的一个具体例子

社区发帖接口,以前 Controller 里校验完参数,调 `PostService::create($data)`,Service 里塞了 200 行:查用户权限、过滤敏感词、写主表、写关联标签表、更新用户发帖数、加积分、发 WebSocket 通知。

现在拆成:

- `CreatePostService`:编排整个流程,像导演
- `PostContentFilter`:纯工具类,输入字符串输出过滤结果,无状态
- `UserReputationChecker`:查用户能否发帖,内部可能调缓存或风控接口
- `PostModel`、`TagModel`、`UserStatModel`:只负责各自表的读写

Service 层代码反而变多了,但每个类的职责窄到能闭眼描述清楚。测试时 `CreatePostService` 可以注入 mock 的 `UserReputationChecker`,不用碰数据库。

还没想明白的

查询场景怎么拆?列表页那种十几个过滤条件、联四五张表的,放 Service 里臃肿,抽 Repository 又感觉多了层抽象。目前我的妥协是:简单查询 Model 直接 scope,复杂报表类单独放一个 `Query` 目录,不归 Service 也不归 Model,算灰色地带。

你们 Service 层会放纯查询逻辑吗,还是另有拆法?

评论0
回复 · 0
还没有回复
微信客服 微信客服