Service 层膨胀成"万能垃圾堆"之后,我重新画了一张职责边界图

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

上周重构一个三年老项目,打开 Service 目录差点窒息——`UserService.php` 两千多行,里面塞着发邮件、算提成、同步 ERP、写日志,甚至还有个生成 PDF 的方法。这哪是 Service,分明是代码界的瑞士军刀,还是锈死的那种。

之前我也觉得"控制器要瘦"就是终极目标,结果把控制器里的 if-else 一股脑倒进 Service,换来的是另一个更难拆的巨无霸。这次痛定思痛,重新理了理 controller / service / model 到底该各自活成什么样。

Controller:只做"接线员",不当"包工头"

我现在给 controller 定的规矩就三条:接参数、调 service、返回格式。超过二十行就要警惕。有个反面教材,之前某接口里直接调了 OSS 上传、又拼 SQL 查关联、还顺手写了条消息通知——这种 controller 瘦是瘦了,只是把肥肉挪了位置。

现在我的 controller 长这样:

```php
public function store(UserRequest $request)
{
$dto = UserCreateDTO::fromRequest($request);
$user = $this->userService->create($dto);
return $this->success(new UserResource($user));
}
```

参数校验扔给 Request 类,DTO 负责数据打包,controller 就是个传话筒。有人觉得这样太薄,但薄才有边界——厚了就容易手痒往里面塞业务。

Service:管"流程编排",不管"脏活累活"

这是这次重构最核心的认知翻转。Service 不该是"所有业务代码的默认归宿",而是跨模型协作的调度层

举个例子:用户注册要创建账号、初始化积分、发欢迎邮件、推送到 CRM。这些单拎出来都是独立能力,Service 只做一件事——按顺序调用,处理事务边界:

```php
public function register(UserCreateDTO $dto)
{
return Db::transaction(function () use ($dto) {
$user = $this->userRepository->create($dto);
$this->creditService->initFor($user);
$this->mailer->sendWelcome($user);
$this->crmSync->push($user);
return $user;
});
}
```

发邮件具体怎么拼模板、CRM 的 API 怎么鉴权,Service 不关心。它只保证"这四件事要么全成,要么全回滚"。

Model / Repository:把"数据脾气"封死在里面

ThinkPHP 的模型我越来越倾向于当数据关系定义器用,复杂查询往 Repository 里迁。不是非要搞 DDD 那套,而是有些查询确实太妖——比如要联四张表、按权重排序、还要做 geo 距离计算,这种写在模型里或者 Service 里都是污染。

Repository 里可以放飞,但对外暴露的要收敛。我习惯每个 Repository 只给 Service 暴露几个语义明确的方法:`getActiveByRegion`、`batchUpdateStatus` 这种,而不是把查询构造器裸抛出去。

拆完之后的新问题:DTO 和 VO 要不要造?

这次重构我试了两种风格。小项目直接用数组传参,DTO 用 PHP 8 的 named arguments 勉强替代;复杂点的还是建了 DTO 类,主要是 IDE 能追踪,改字段名的时候不用全文搜索。

但 VO(视图对象)我目前用得克制,只有前端要的数据结构和数据库差很远时才包装,不然多一层就是多一层维护成本。

一个还在纠结的点:事件监听算不算 Service?

比如用户注册后发邮件,是写在 Service 里同步调,还是抛事件让 Listener 异步处理?目前我的做法:事务内必须成功的放 Service 同步执行,能容忍延迟的走事件队列。但这样代码就散在两处,有时候找链路要翻三个文件,还没想好怎么平衡。

你们 Service 层最长一个文件多少行?有没有比我这两千行更离谱的,说出来让我平衡一下。

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