# 控制器臃肿、模型失焦、服务层变“杂物间”:一次订单退款流程的拆分复盘

小助手
小助手 揽星漫步繁花
社区会员
教程 22 浏览 4 回复

很多项目的分层退化,不是从“没分层”开始的,而是从“先放进去再说”开始的。起初 controller 里只写参数接收,service 里只做编排,model 里只映射数据。几轮需求过后,controller 开始拼 SQL 条件,service 里塞满了邮件模板和签名算法,model 则成了万能工具类。本文不复述分层定义,而是用一条订单退款流程,拆解 controller / service / model 各自该守住的边界。

先看一个常见的退化样本。退款接口收到请求后,controller 里做了四件事:校验订单状态、计算可退金额、调用支付网关、写退款流水。service 层几乎为空,model 里则挂了一个 `getRefundableAmount()` 方法,内部又去查了优惠券和积分抵扣。这个结构能跑,但一旦退款规则变化,改动会同时落在三个文件里。

问题出在职责的错位上。controller 的核心职责是协议适配:把 HTTP 输入转成内部调用参数,把内部结果转成响应格式。它不应该知道“已发货订单只能退部分金额”这类业务规则,也不应该直接调用支付网关。把状态校验和金额计算放进 controller,等于把业务规则暴露给了传输层,换一个入口(比如后台手动退款)就得复制一遍逻辑。

service 的边界是“一次完整的业务动作”。退款这件事,完整的动作包括:锁定订单、校验可退性、计算金额、发起支付退款、记录流水、通知用户。这些步骤可以拆成多个方法,但编排权应该在 service。它不关心请求来自 HTTP 还是消息队列,只关心业务动作是否完整、事务边界是否清晰。如果 service 里出现了 `$_POST` 或 `Request` 对象,说明协议细节泄漏了。

model 的边界是“数据与数据关系的表达”。它应该回答“这个订单当前状态是什么”“这条流水属于哪个订单”,而不是“这个订单能退多少钱”。可退金额是业务规则,依赖优惠券、积分、已退次数等多个数据源,放在 model 里会让数据层承担策略职责。更干净的做法是:model 提供原子查询和状态变更,金额计算放在领域服务或独立的策略类中。

回到退款流程的拆分。controller 只做三件事:接收 order_id 和退款原因,调用 `RefundService::handle()`,返回统一响应。service 编排:加载订单、调用 `RefundPolicy` 判断可退性、调用 `AmountCalculator` 计算金额、调用支付网关、写入退款流水、触发通知。model 只保留:`Order` 的状态字段和关联查询,`RefundRecord` 的写入和查询。策略和计算器作为独立类存在,不混入任何一层。

这样拆完后,改动的影响面变得可预测。退款规则变化,只改 `RefundPolicy`;支付网关更换,只改网关适配器;新增退款入口,复用同一个 service。controller 和 model 几乎不动。判断分层是否干净,有一个简单的检验方法:如果换掉 HTTP 框架,业务逻辑是否需要重写?如果换掉数据库,业务规则是否需要重写?两个答案都是“否”,说明边界守住了。

你现在的项目里,有没有哪个 service 方法已经超过了 200 行,或者哪个 model 里藏着 if-else 业务分支?从那条最长的链路开始拆,通常比重新设计整个分层更有效。

评论4
最新打赏 共 1 次 · 5 金币
Jack
Jack 萌芽
社区会员
+5金币
评论 · 4
Miles
Miles 萌芽 · #4 ·
写得很清楚,收藏了
Jack
Jack 萌芽 · #3 ·
学到了,顶一下
予安
予安 萌芽 · #2 ·
写得很清楚,收藏了
小满
小满 拾柴 · #1 ·
感谢分享!