Service 层里那堆 `if ($type == 1)`,让我把"业务逻辑"写成了"业务迷宫"

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

上周重构一个老项目,打开 Service 层差点没背过气去——同一个文件里,会员下单、游客下单、代理代下单三种逻辑拧成麻花,全靠 `$type` 和 `$is_vip` 两个变量走迷宫。改个优惠券规则,三个分支要各改一遍,漏一个就线上翻车。

痛定思痛,我把"怎么拆干净"这件事重新捋了一遍。不是教条式地套 MVC,而是让每层真正只干一件不纠结的事。

Controller:只当"前台接待",不当"业务经理"

以前我 Controller 里常顺手干这些:拼查询条件、算折扣金额、决定走哪个支付通道。现在强制自己只留四行:接参数、调 Service、收结果、吐响应。参数校验交给 Validate,异常捕获统一包一层,Controller 里不出现任何 `if` 跟业务有关。

有个具体做法:Controller 方法里禁止调用两个以上 Service 方法。如果感觉要调两个,说明这个"业务动作"本身该封装成第三个 Service。

Service:按"业务场景"拆,不按"数据表"拆

这是我踩过最大的坑。早期我搞了个 `UserService` 管用户表所有事,结果它膨胀到八百行,登录、注册、积分、等级、提现全塞里面。后来按场景拆成 `AuthService`、`WalletService`、`LevelService`,调用方才清爽。

关键原则:Service 的命名要能回答"用户在干什么",而不是"在操作哪张表"。`OrderCreateService` 比 `OrderService::create` 更直白,因为前者暗示这是一个完整的业务单元,后者只是表操作的一个方法。

遇到那种跨表事务,我现在的做法是:写一个"编排型" Service,里面只串调用,不写具体 SQL。比如 `GroupBuyService` 负责拼团下单,它调 `OrderCreateService`、`InventoryLockService`、`PaymentInitService`,但自己一行数据库都不碰。事务注解打在这个编排层。

Model:只当"数据翻译官",不当"业务智囊"

Model 层我现在卡得很死:只放跟表结构强相关的东西。字段类型转换、自动时间戳、软删除、简单的查询作用域(scope),这些可以留。但"如果用户是 VIP 则查询另一张表"这种逻辑,必须踢出去。

有个例外我保留了:关联定义。`user->orders()` 这种写在 Model 里没问题,因为确实是数据结构的一部分。但关联之后的筛选、排序、聚合,交给 Service 或单独抽个 Repository。

我现在的目录结构,供参考

``` app/ controller/ # 只接 HTTP,不碰业务 service/ order/ OrderCreateService.php OrderCancelService.php OrderQueryService.php user/ UserRegisterService.php UserBindPhoneService.php model/ # 纯数据层 repository/ # 复杂查询封装,可选 ```

Service 按动词(Create/Cancel/Query)拆分,不是按名词(Order/User)一个大类。这样新增"拼团创建"时,写 `GroupCreateService` 就行,不用去 `OrderService` 里再加个 `createGroup` 方法。

一个反直觉的发现

拆太细也有代价。我之前把"发送短信"拆成 `SmsSendService`,里面又分 `AliyunSmsService`、`TencentSmsService`,调用方要注入两个。后来改成策略模式:一个 `SmsService` 门面,底层根据配置自动路由。对外是"一个服务",对内是"多个实现"。

分层不是越碎越好,而是"修改时能精准定位到唯一一处"。改短信文案,只去 `AliyunSmsTemplate`;改发送逻辑,只去 `SmsDispatcher`。两者不互相污染,就是干净的拆分。

你们 Service 层最胖的时候到过多少行?我那个八百行的"传奇"至今不敢删,留着当警示碑。

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