Controller 成了"调度员",Service 成了"大杂烩",Model 成了"查询器"——我这分层分了个寂寞

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

上周重构老项目,把代码按 controller / service / model 三层扒开,本以为能清爽点,结果越拆越别扭。Controller 是薄了,就剩个参数接收和返回值组装;Model 里塞了一堆 scope 和关联;Service 呢?好家伙,从支付回调到积分计算到消息推送全往里倒,600 行起步,跟个垃圾中转站似的。

问题出在哪?我琢磨了一下,之前那套"Controller 调 Service、Service 调 Model"的教条,本质上只是把代码平移了,没解决"谁该干什么"。比如一个下单流程:锁库存、算价格、用券、减积分、写订单、推消息。按老写法,这些全堆在 OrderService 里,一个方法跨了五张表,事务套事务,回滚都理不清。

这次我换了个思路,把 Service 再拆两层:一层叫 Domain Service(领域服务),只管纯业务规则,比如"优惠券能不能用""积分怎么换算";另一层叫 Application Service(应用服务),负责编排,告诉 A 领域做完调 B 领域,最后统一提交事务。Model 回归本质,只干数据映射和基础查询,复杂的联表扔给专门的 Query 类或者 Repository。

举个具体的改动。之前 OrderService::create 里直接 UserModel::where('id', $userId)->dec('score', $score)->update(),现在拆成:OrderDomain 生成订单实体,CouponDomain 校验并核销券,ScoreDomain 预扣积分,最后 Application 层开事务,三个 Domain 按顺序执行,任一抛异常统一回滚。Controller 只管 validate()->dto()->app(OrderAppService::class)->create($dto)->response(),干净得我自己都不习惯。

有个坑得提。刚开始我把"发送站内通知"也塞 Domain 里了,结果发现积分 Domain 里调消息 Domain,消息 Domain 里又触发了积分日志,循环引用差点没把我绕死。后来定了条规矩:Domain 层禁止直接调外部系统或副作用操作,发消息、发邮件、调第三方接口,统一由 Application 层收集事件,事务提交后再异步派发。相当于 Domain 只算"账",Application 才"动钱"。

Model 这块我也改了习惯。以前喜欢在 Model 里写大量业务方法,比如 UserModel::registerByPhone(),现在这类全迁到 UserDomain 里,Model 只保留 scopeActive()scopeRecent() 这种纯查询意图的封装。突然感觉 Model 轻了,单元测试也好写了,不用每次都 mock 数据库。

当然也不是无脑拆。有个小模块就三张表、两个接口,硬套四层反而啰嗦,我直接 Controller 调 Repository 完事。分层是手段,不是目的,核心是看"修改时能不能只动一层"。如果改个积分规则要穿透三四层,那这分层就是形式主义。

目前跑了两周,最直观的感受是 Code Review 时同事不再问我"这个方法到底在哪改的"了。Domain 层的方法名就是业务语言,Application 层一眼能看到完整流程,Model 打开不会蹦出一堆看不懂的业务判断。虽然文件数多了,但脑容量省下来了。

你们分层是怎么实践的?有没有遇到过"拆完更乱"的阶段?

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