UseCase 模式:后端业务逻辑结构化最佳实践
UseCase 是一种架构模式,用于定义应用中的业务场景。它将代码围绕具体的系统操作组织起来——比如下单或用户注册,而不是抽象的技术操作。这种方法提升了代码可读性,简化了测试,并在复杂后端系统中有效扩展项目。
UseCase 是什么?为什么值得使用?
UseCase 在应用层代表一个完整的业务场景。它协调一系列步骤,将业务规则委托给领域服务和实体。例如,不是低级的“写入数据库”操作,而是描述“用户下单”。这样,随着项目增长,业务逻辑不会散布在控制器和服务中。没有明确的场景边界,代码会变得难以理解和维护。
UseCase 的核心优势:
- 清晰结构:系统由明确的业务能力定义,而不是模糊的 Service 或 Manager 等抽象概念。
- 更好可读性:架构镜像业务逻辑,便于回答“这个应用能做什么?”
- 更易测试:场景级别的集成测试验证完整用户路径,而不破坏业务流程。
局限性与最佳实践
避免在 UseCase 中调用另一个 UseCase,以防隐藏依赖并将其转为可复用逻辑块。在复杂场景中,只要无循环依赖且场景保持明确即可。对于可复用逻辑,提取到专用的服务或领域组件中,可跨多个 UseCase 使用。
UseCase 的调用位置:
- 控制器
- 控制台命令
- 任务队列
- 事件监听器
UseCase 作为应用业务逻辑的入口点,调解外部交互与系统内部。
仅用 Service 为什么不够用
Service 在简单项目中管用,但业务逻辑膨胀时问题就来了。原本简单的“保存订单到数据库”,会演变为检查库存、应用折扣、扣减余额、发送通知等。这会制造“上帝类”——依赖、方法和副作用臃肿的类,导致代码难读难测。
UseCase 遵循简单规则:一个 UseCase = 一个业务流程。重复逻辑放入共享服务或领域组件,随着复杂度增加保持代码可控。
PHP 和 Laravel 中的 UseCase 结构
典型的 UseCase 包含三部分:
- Handler:协调场景步骤和条件,不含领域业务逻辑。
- DataInput:输入数据对象,无逻辑——仅类型化、优选不可变数据。
- DataOutput:结果对象定义清晰契约(如 DTO)。
示例:创建订单
class StoreOrderDataInput
{
public function __construct(
public readonly int $userId,
public readonly array $items,
) {}
}
class StoreOrderDataOutput
{
public function __construct(
public readonly int $orderId,
public readonly string $status,
) {}
}
class StoreOrderHandler
{
public function __construct(
private OrderRepository $orders
) {}
public function handle(StoreOrderDataInput $input): StoreOrderDataOutput
{
$order = new Order(
userId: $input->userId,
);
$orderSaved = $this->orders->save($order);
return new StoreOrderDataOutput(
orderId: $orderSaved->id,
status: $orderSaved->status
);
}
}
项目中组织和测试 UseCase
在 Laravel 中,将 UseCase 存放在类似 app/UseCases/V1 的结构下,带版本控制以安全变更契约。子目录回答“系统能做什么?”——如 StoreOrder、CancelOrder、RegisterUser。这镜像业务能力,而非技术细节。
测试 UseCase 简单,使用场景级集成测试。StoreOrderHandler 示例测试:
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class StoreOrderFeatureTest extends TestCase
{
use RefreshDatabase;
public function testUserCanCreateOrder(): void
{
$user = User::factory()->create();
$payload = [
'items' => [
['id' => 10, 'qty' => 2],
['id' => 15, 'qty' => 1],
],
];
$response = $this->actingAs($user)
->postJson('/api/orders', $payload);
$response->assertStatus(200);
$response->assertJsonStructure([
'orderId',
'status'
]);
$this->assertDatabaseHas('orders', [
'user_id' => $user->id
]);
}
}
核心要点
- UseCase 定义业务场景,而非技术操作,实现更清晰的应用结构。
- 解决大型项目逻辑散乱,保持代码可维护。
- 一个 UseCase = 一个业务流程,共享逻辑放服务中。
- 测试更简单,通过覆盖完整场景的集成测试。
- 项目结构反映业务特性,加速新人上手。
— Editorial Team
暂无评论。