service 层越写越像"万能垃圾桶"?我用"输入校验→编排→防腐"三步法重新理清了 controller/service/model 的边界

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
插件开发 84 浏览 0 回复

之前分层拆完,controller 里还是塞满了 `if ($user->role === 'xxx')` 这种业务判断,service 层则沦为"数据库透传管道",model 除了 `get_results` 啥也不干。表面上三层文件都有了,实际一改动需求,三处一起改,分层了个寂寞。

后来我把 service 层的职责强行压成三条线,才慢慢把"伪分层"扭过来。

第一条:输入校验与转换,controller 只做"安检门"

以前我在 controller 里直接拿 `$_POST` 往 service 丢,结果 service 里到处 `isset`、`intval` 满天飞。现在 controller 用个轻量 DTO 对象把原始请求"洗"一遍,非法字段直接拦在外面,service 拿到的已经是"干净且带类型"的数据。

// controller 里只做这件事
$dto = new OrderCreateDTO($_POST);
if (!$dto->validate()) {
    wp_send_json_error(['fields' => $dto->errors()]);
}

// service 拿到的是已经校验过的对象
$order = $orderService->create($dto);

controller 不碰业务规则,只负责"这包数据能不能进大门"。

第二条:service 做"编排",但禁止直接碰 SQL

我给自己定了个死规矩:service 层只能调 model 的"领域方法",不能用 `$wpdb->query`。比如创建订单不是 `insert_order_record`,而是 `OrderModel::createPending($dto->toArray())`。

这样 model 层开始真正长出血肉——它知道自己有哪些状态、哪些字段组合是合法的,而不是 service 在拼 SQL 字符串。

// OrderService.php
public function create(OrderCreateDTO $dto): OrderModel
{
    // 编排:锁库存 -> 创建订单 -> 写日志
    return $this->transaction(function() use ($dto) {
        $inventory = $this->inventoryModel->lock($dto->sku, $dto->qty);
        $order = OrderModel::createPending($dto->toArray());
        $this->eventLog->record('order.created', $order->id);
        return $order;
    });
}

service 管"先做哪步后做哪步",model 管"这一步怎么落地"。

第三条:防腐层,隔离 WordPress 的"怪癖"

以前 `get_option`、`update_option` 散落在 controller 和 service 各处,缓存失效、序列化坑、多站点前缀问题到处埋雷。现在我把所有 WordPress 原生 API 的调用都收敛到 model 层的"网关方法"里,上层完全感知不到 `option_name` 前面有没有 `_site_transient_timeout_` 这种鬼东西。

// 上层只看到这个
$pluginConfig = ConfigModel::get('shipping_rules');

// 底层处理所有 WordPress 的脏活
class ConfigModel {
    public static function get(string $key): mixed {
        $raw = get_site_option(self::prefix($key));
        return self::unserializeSafely($raw);
    }
}

这层"防腐"让单元测试好写很多——mock 一个 ConfigModel 就行,不用去劫持 WordPress 的全局函数。

一个反直觉的发现

拆干净之后,controller 反而变厚了一点——不是业务代码变多,而是显式声明变多了:校验规则、权限断言、响应格式转换,这些以前隐身散落在各层的"暗逻辑",现在全部摊在 controller 的明面上。而 service 和 model 变薄了,变"呆"了,反而好测、好复用。

你们分层的时候,有没有遇到过"拆完还是一团,只是分成了三个文件"的情况?最后是怎么把真正的边界切出来的?

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