Service类应按真实协作边界和事务一致性范围拆分,如OrderCreationService对应“创建订单”完整链路,涵盖库存锁定、余额扣减等原子操作,跨域调用通过事件解耦,内部方法需可测试、可复用、副作用可控。

Service类该按业务域还是功能类型拆分?
别按“用户”“订单”“支付”这种实体名粗暴建类,容易后期膨胀成巨无霸。应该按**真实协作边界**和**事务一致性范围**来切——比如“创建订单”涉及库存锁定、余额扣减、优惠券核销、消息通知,这整条链必须在一个 Service 类里完成,而不是拆成 UserOrderService、StockService、PaymentService 各自为政。
常见错误是把“查用户信息”和“发短信验证码”塞进同一个 UserService,结果改个短信渠道就得动用户核心逻辑。
- 一个 Service 类对应一个**有明确开始和结束的业务动作**,如
OrderCreationService、RefundProcessingService - 跨域调用(如订单创建后要通知物流)用事件或消息队列解耦,Service 内部不直接调第三方 SDK
- 纯数据读取类操作(如“获取用户最近3笔订单”)应归入 Repository 或 Model,不是 Service 的职责
方法粒度怎么控制?createOrder() 里要不要拆 checkStock()、deductBalance()?
要拆,但不是为了“看起来更细”,而是为了**可测试、可复用、可组合**。一个 createOrder() 方法如果内部混着校验、锁库、扣款、写日志、推消息,出错时根本不知道卡在哪一步,也无法在退款流程中复用“扣余额”逻辑。
ThinkPHP 的 Service 方法命名必须是动词开头、语义明确、副作用可控:
-
checkStockAndLock($skuId, $quantity)—— 返回布尔或抛异常,不写日志、不发消息 -
deductBalance($userId, $amount)—— 只做扣减,失败时回滚自己那部分,不牵连其他步骤 -
recordOrderLog($orderId, $action)—— 纯记录,不参与事务 - 禁止出现
getOrderInfo()这种名字:那是 Model 或 Query 层的事
DTO 和数组传参,到底该用哪个?
一律用 DTO 对象,别用关联数组。数组看着省事,但加个字段、改个类型、缺个键,运行时才报错;DTO 能在 IDE 中跳转、自动补全、静态分析,还能复用验证规则。
例如下单参数,不要这样传:
将 .eth 名称解析为以太坊地址,反之亦然。适用于用户提供 .eth 名称(如“发送给 vitalik.eth”)、显示地址时展示 ENS 名称、查询 ENS 档案,或帮助注册、续期、管理 .eth 名称。
$data = [
'user_id' => 123,
'items' => [['sku_id' => 'A001', 'qty' => 2]],
'address_id' => 456,
'coupon_code' => 'DISC2026'
];
而应定义 CreateOrderDTO 类,带属性类型声明和构造函数约束:
class CreateOrderDTO
{
public function __construct(
public int $userId,
public array $items,
public int $addressId,
public ?string $couponCode = null
) {}
}
Controller 里 new 出来传给 Service,Service 方法签名就能强制类型检查:public function createOrder(CreateOrderDTO $dto)。
为什么 Service 里不能 new 实例,也不能在方法里 app()->make()?
因为容器管理的生命周期、AOP 拦截(比如事务代理)、单例绑定都会失效。你在 OrderCreationService::createOrder() 里手动 app()->make(PaymentService::class),等于绕过容器,导致:
- PaymentService 的构造函数依赖不会被自动注入(比如它依赖的
Cache或Request是 null) - 如果 PaymentService 声明了事务拦截器,手动 make 后事务注解完全不生效
- 单元测试时无法 mock 依赖,只能测“整条链”,一挂全挂
正确做法是在 Service 构造函数里用类型提示声明依赖:public function __construct(private PaymentService $paymentService),让容器在实例化时一次性注入干净、隔离、可测的实例。
最常被忽略的是:一旦 Service 构造函数里注入了请求上下文对象(比如 Request),它就不能再作为单例存在——否则并发请求会共享状态。这时候得配合容器绑定策略,比如用 bind('OrderCreationService')->toFactory([...]) 动态构建,而不是默认单例。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










