1. 订单取消逻辑的典型分层架构
在PHP项目中,订单取消功能通常涉及三个主要层级:Controller层、Service层和领域对象层(如Order对象)。每个层级都有其明确的职责边界,理解这些边界是做出合理设计决策的前提。
1.1 Controller层的核心职责
Controller作为HTTP请求的入口点,主要职责包括:
- 接收和验证HTTP请求参数
- 调用适当的Service方法
- 处理HTTP响应(成功/错误返回)
- 管理会话和用户认证
典型的订单取消Controller可能如下:
php复制class OrderController
{
public function cancelAction(Request $request)
{
$orderId = $request->get('order_id');
$userId = $this->getCurrentUserId();
try {
$this->orderService->cancelOrder($orderId, $userId);
return new JsonResponse(['success' => true]);
} catch (OrderException $e) {
return new JsonResponse(['error' => $e->getMessage()], 400);
}
}
}
Controller不应包含任何业务逻辑判断,它只是业务逻辑的"接线员"。
1.2 Service层的业务协调作用
Service层是业务逻辑的主要承载者,其职责包括:
- 协调多个领域对象的交互
- 处理事务边界
- 实现跨聚合的业务规则
- 调用基础设施层(如数据库、外部API)
订单取消在Service层的典型实现:
php复制class OrderService
{
public function cancelOrder($orderId, $userId)
{
$order = $this->orderRepository->find($orderId);
if (!$order->belongsTo($userId)) {
throw new OrderException('无权操作此订单');
}
$this->transactionManager->begin();
try {
$order->cancel();
$this->inventoryService->releaseStock($order);
$this->notificationService->sendCancelNotice($order);
$this->transactionManager->commit();
} catch (Exception $e) {
$this->transactionManager->rollback();
throw $e;
}
}
}
1.3 Order领域对象的自治能力
Order作为领域对象,应该封装与订单直接相关的状态变更规则:
- 订单状态的有效转换
- 订单金额计算
- 与订单直接相关的业务规则验证
Order对象中的取消逻辑示例:
php复制class Order
{
private $status;
private $items;
public function cancel()
{
if ($this->status === 'shipped') {
throw new OrderException('已发货订单不可取消');
}
$this->status = 'cancelled';
$this->cancelledAt = new DateTime();
}
public function isCancellable()
{
return in_array($this->status, ['pending', 'paid']);
}
}
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 逻辑放置的决策框架
2.1 判断逻辑性质的四个维度
-
技术性 vs 业务性
- 技术性逻辑(如参数校验、权限检查)适合放在Controller
- 业务性逻辑(如库存释放、支付撤销)属于Service层
- 领域规则(如状态转换)应放在领域对象中
-
单一职责 vs 跨聚合操作
- 只涉及单个聚合内部状态的变更应放在领域对象
- 需要协调多个聚合的操作应放在Service
-
事务边界需求
- 需要事务管理的操作必须放在Service层
- 无事务需求的简单操作可考虑放在领域对象
-
复用性考量
- 高频复用的核心业务规则应下沉到领域对象
- 特定场景的一次性逻辑可放在Service
2.2 典型决策流程
- 判断是否涉及HTTP协议处理 → 是:Controller
- 判断是否需要协调多个领域对象 → 是:Service
- 判断是否属于聚合根内部状态管理 → 是:领域对象
- 判断是否需要事务管理 → 是:Service
2.3 代码异味识别
错误放置的警示信号:
- Controller中出现业务条件判断(如if(status === 'paid'))
- Service中出现对领域对象内部属性的直接操作
- 领域对象中调用Repository或外部服务
- 相同的业务规则在多个Service中重复出现
3. 订单取消的合理分解方案
3.1 职责的黄金分割
基于上述框架,订单取消功能的理想分解:
-
Controller层
- 验证订单ID格式
- 检查用户权限(基础权限)
- 捕获并转换异常为HTTP响应
-
Service层
- 加载订单聚合
- 验证高级业务权限
- 管理事务边界
- 协调库存释放、通知发送等跨聚合操作
-
Order领域对象
- 维护状态转换规则
- 计算应退款金额
- 验证取消时间限制
- 记录取消原因
3.2 实战代码示例
完整的分层实现:
php复制// Controller
class OrderController
{
public function cancelAction(Request $request)
{
$this->validateRequest($request);
try {
$this->orderService->userCancelOrder(
$request->get('order_id'),
$this->getCurrentUserId(),
$request->get('reason', '')
);
return $this->jsonSuccess();
} catch (OrderException $e) {
return $this->jsonError($e->getMessage());
}
}
private function validateRequest(Request $request)
{
// 基础参数验证...
}
}
// Service
class OrderService
{
public function userCancelOrder($orderId, $userId, $reason)
{
$order = $this->orderRepository->find($orderId);
if (!$order->isOwnedBy($userId)) {
throw new OrderException('无权操作此订单');
}
$this->transactionManager->execute(function() use ($order, $reason) {
$order->cancelByUser($reason);
$this->orderRepository->save($order);
if ($order->isPaid()) {
$this->paymentService->processRefund($order);
}
$this->inventoryService->releaseItems($order);
});
}
}
// Order领域对象
class Order
{
public function cancelByUser($reason)
{
if (!$this->isCancellable()) {
throw new OrderException('当前状态不可取消');
}
$this->status = 'cancelled';
$this->cancellationReason = $reason;
$this->cancelledAt = new DateTime();
}
public function isCancellable()
{
return !in_array($this->status, ['shipped', 'completed', 'cancelled']);
}
}
4. 特殊场景的进阶处理
4.1 带有时效性的取消规则
对于如"下单30分钟内可免费取消"这类规则,推荐实现方式:
php复制class Order
{
private const FREE_CANCEL_WINDOW = 1800; // 30分钟
public function getCancelFee()
{
if ($this->isInFreeCancelWindow()) {
return 0;
}
return $this->calculateFeeBasedOnItems();
}
private function isInFreeCancelWindow()
{
return (time() - $this->createdAt->getTimestamp()) < self::FREE_CANCEL_WINDOW;
}
}
4.2 需要外部验证的取消
当取消操作需要调用外部服务验证时:
php复制class OrderService
{
public function cancelWithExternalValidation($orderId, $userId)
{
$order = $this->getValidOrder($orderId, $userId);
if (!$this->fraudDetectionService->validateCancellation($order)) {
throw new OrderException('取消请求被风控系统拒绝');
}
$order->cancel();
// ...其他处理
}
}
4.3 带补偿逻辑的取消
对于需要执行补偿操作的场景:
php复制class OrderService
{
public function compensateCancel($orderId)
{
$order = $this->orderRepository->find($orderId);
$this->transactionManager->execute(function() use ($order) {
$order->cancel();
$this->couponService->issueCompensation(
$order->getUserId(),
$order->calculateCompensationAmount()
);
$this->orderRepository->save($order);
});
}
}
5. 分层架构的演进策略
5.1 从贫血模型到充血模型
许多项目初期采用贫血模型(逻辑全在Service),随着复杂度增长,可逐步重构:
- 第一阶段:识别领域对象中的状态转换规则
- 第二阶段:将简单规则迁移到领域对象
- 第三阶段:建立聚合根,封装更复杂的业务不变式
重构示例:
php复制// 重构前
class OrderService
{
public function cancelOrder($order)
{
if ($order->status === 'shipped') {
throw new OrderException('已发货订单不可取消');
}
$order->status = 'cancelled';
}
}
// 重构后
class Order
{
public function cancel()
{
if ($this->status === 'shipped') {
throw new OrderException('已发货订单不可取消');
}
$this->status = 'cancelled';
}
}
5.2 事务管理的优化
复杂场景下的事务管理策略:
- 小事务原则:每个Service方法管理自己的事务
- 嵌套事务:对于多层调用,使用保存点(Savepoint)
- 最终一致性:对非核心操作采用异步处理
php复制class OrderService
{
public function complexCancelOperation($orderId)
{
$this->transactionManager->execute(function() use ($orderId) {
$order = $this->getOrder($orderId);
$order->cancel();
// 核心操作
$this->paymentService->processRefund($order);
// 非核心操作标记为异步
$this->eventDispatcher->dispatch(
new OrderCancelledEvent($orderId)
);
});
}
}
5.3 测试策略的调整
分层架构下的测试重点:
-
Controller测试:
- HTTP状态码和响应格式
- 参数验证逻辑
- 异常转换
-
Service测试:
- 事务完整性
- 跨聚合协调
- 异常场景恢复
-
领域对象测试:
- 状态转换规则
- 业务规则验证
- 计算逻辑
php复制class OrderTest extends TestCase
{
public function testCancelWithinFreeWindow()
{
$order = new Order();
$order->setCreatedAt(new DateTime('-10 minutes'));
$order->cancelByUser('change mind');
$this->assertEquals('cancelled', $order->getStatus());
$this->assertEquals(0, $order->getCancelFee());
}
}
在实际项目中,我倾向于将核心状态转换规则放在Order对象中,Service层负责协调和事务管理,Controller保持精简。这种分层的优势在业务复杂后愈发明显:当需要增加新的取消原因类型时,只需修改Order对象;当需要添加新的补偿逻辑时,只需调整Service层。
