本文目录导读:

在 Laravel 中实施用例驱动开发(Use Case Driven Development),关键在于将业务用例作为核心,围绕用户与系统的交互场景来设计和构建代码,以下是具体的实施步骤和最佳实践:
理解用例驱动开发的核心思想
用例驱动开发强调从用户的视角出发,识别系统需要完成的特定功能场景(即用例),然后围绕这些用例来驱动代码的编写、测试和重构,它的核心理念是:
- 先定义行为,再实现细节:在写一行代码之前,先明确用户想要做什么(用例描述),以及系统应该如何响应。
- 测试是契约:每个用例都对应一组验收测试,它定义了用例成功或失败的标准,这些测试在开发过程中作为驱动。
- 关注点分离:用例层将业务逻辑与框架(如路由、控制器、视图)以及基础设施(数据库、外部API)解耦。
在 Laravel 中的具体实施步骤
步骤 1:识别并定义用例
不要直接从路由或控制器开始,先思考:
- 用户是谁?(管理员、普通用户、访客)
- 他们想要达成什么目标?(创建文章、上传头像、修改订单状态)
- 成功场景是什么?(文章保存成功并发布、头像更新为新图片、订单状态变为“已发货”)
- 失败场景是什么?为空、图片格式错误、订单已被取消无法修改)
示例用例:用户注册
- 用例名称:
用户注册 - 前提条件:用户未登录。
- 主成功场景:
- 用户填写注册表单(姓名、邮箱、密码)。
- 系统验证输入数据(邮箱唯一、密码强度合格)。
- 系统创建新用户。
- 系统发送欢迎邮件。
- 系统返回成功状态,用户登录。
- 替代/失败场景:
- 邮箱已存在:返回错误提示。
- 密码太弱:返回错误信息。
步骤 2:设计用例类(Action / UseCase)
在 Laravel 中,通常用一个独立的类来实现一个用例,这类类常被命名为 Action、UseCase、Handler 或 Service。
app/
└── UseCases/
└── Auth/
├── RegisterUserUseCase.php
├── LoginUseCase.php
└── SendWelcomeEmail.php
// app/UseCases/Auth/RegisterUserUseCase.php
namespace App\UseCases\Auth;
use App\Models\User;
use App\Exceptions\UserRegistrationException;
use Illuminate\Support\Facades\Hash;
use App\Actions\Auth\SendWelcomeEmail;
class RegisterUserUseCase
{
public function __construct(
private SendWelcomeEmail $sendWelcomeEmail // 依赖注入其他用例或服务
) {}
/**
* 执行注册用例
*
* @param array{name: string, email: string, password: string} $data
* @return User
* @throws UserRegistrationException
*/
public function execute(array $data): User
{
// 1. 数据验证(通常在控制器或 FormRequest 做,但用例也可以做业务校验)
// 注意:用例内不要直接抛出 ValidationException,而是抛出自定义业务异常
// 2. 核心业务逻辑
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
// 3. 调用其他服务/用例
$this->sendWelcomeEmail->execute($user);
/** 4. 触发领域事件(可选) */
// event(new UserRegistered($user));
return $user;
}
}
步骤 3:创建测试来驱动用例实现(TDD/BDD)
在编写实现代码之前,先为用例编写测试,这是“驱动”的关键。
// tests/Unit/UseCases/Auth/RegisterUserUseCaseTest.php
namespace Tests\Unit\UseCases\Auth;
use Tests\TestCase;
use App\UseCases\Auth\RegisterUserUseCase;
use App\Exceptions\UserRegistrationException;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
class RegisterUserUseCaseTest extends TestCase
{
use RefreshDatabase;
private RegisterUserUseCase $useCase;
protected function setUp(): void
{
parent::setUp();
// 注意:需要模拟 SendWelcomeEmail 以避免实际发邮件
// 这通常通过依赖注入容器或 Mockery 完成
$this->useCase = app(RegisterUserUseCase::class);
}
/** @test */
public function it_can_register_a_new_user()
{
// Arrange
$data = ['name' => 'John', 'email' => 'john@example.com', 'password' => 'password123!'];
// Act
$user = $this->useCase->execute($data);
// Assert
$this->assertInstanceOf(User::class, $user);
$this->assertDatabaseHas('users', ['email' => 'john@example.com']);
$this->assertNotEquals($data['password'], $user->password); // 密码已哈希
// 可以添加 Mock 验证:$this->sendWelcomeEmailMock->shouldHaveReceived('execute');
}
/** @test */
public function it_throws_exception_when_email_is_duplicate()
{
// Arrange
User::factory()->create(['email' => 'john@example.com']);
$data = ['name' => 'John', 'email' => 'john@example.com', 'password' => 'password123!'];
// Assert
$this->expectException(UserRegistrationException::class);
$this->expectExceptionMessage('The email has already been taken.');
// Act
$this->useCase->execute($data);
}
/** @test */
public function it_calls_send_welcome_email_after_registration()
{
// 更高级的 Mock 验证示例
// 此处略
}
}
然后运行测试(php artisan test),测试失败,然后去编写 RegisterUserUseCase 的实现代码,直到测试通过。
步骤 4:创建薄控制器(Thin Controller)
控制器应该保持“薄”,它只负责:
- 接收输入(来自请求)。
- 验证输入(通过 FormRequest 或手动验证)。
- 调用用例(执行业务逻辑)。
- 返回响应(根据用例执行结果)。
// app/Http/Controllers/Api/AuthController.php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\RegisterRequest; // 验证在此处
use App\UseCases\Auth\RegisterUserUseCase;
use App\Exceptions\UserRegistrationException;
use Illuminate\Http\JsonResponse;
class AuthController extends Controller
{
public function __construct(
private RegisterUserUseCase $registerUser // 注入用例
) {}
public function register(RegisterRequest $request): JsonResponse
{
try {
// 控制器只负责:获取已验证数据,调用用例,处理响应
$user = $this->registerUser->execute($request->validated());
return response()->json([
'message' => 'Registration successful.',
'user' => $user->only(['name', 'email']),
'token' => $user->createToken('auth')->plainTextToken,
], 201);
} catch (UserRegistrationException $e) {
// 捕获用例抛出的业务异常,返回适当的 HTTP 响应
return response()->json(['message' => $e->getMessage()], 422);
}
}
}
步骤 5:路由与依赖注入
在 routes/api.php 中注册路由,Laravel 的依赖注入容器会自动解析 AuthController 和它构造函数中的 RegisterUserUseCase。
Route::post('/register', [AuthController::class, 'register'])->withoutMiddleware('auth:sanctum');
进阶实践与模式
- 分层架构:通常将项目分为:
- 应用层(UseCase 层):用例类,包含业务逻辑、工作流编排。
- 领域层:模型(Model)、值对象、领域服务(复杂业务规则)、存储库接口。
- 基础设施层:Eloquent 实现、外部 API 客户端、邮件服务等。
- 接口层:控制器、路由、视图、表单请求。
- 命令/查询职责分离(CQRS):对于复杂的系统,可以将写入(用例/命令)和读取(查询)分开,使用单独的查询类或数据投影。
- 使用 DTO(Data Transfer Object):用例的
execute方法最好传递一个 DTO 而非简单数组,以提供类型安全。 - 使用 Pipeline / Middleware:对于需要复用的横切关注点(如日志记录、事务管理),可以使用 Laravel Pipeline 来装饰用例执行过程。
// 使用 DTO
class RegisterUserDTO
{
public function __construct(
public readonly string $name,
public readonly string $email,
public readonly string $password
) {}
}
// UseCase 变为
public function execute(RegisterUserDTO $dto): User
优点与挑战
优点
- 可测试性强:用例类极易单元测试,不依赖 Laravel HTTP 请求/响应。
- 可维护性高:业务逻辑集中在用例中,方便定位和修改。
- 清晰的结构:代码的职责划分非常明确,新人容易理解和上手。
- 易于迁移或重建:业务逻辑和框架绑定较弱,如需从 Laravel 迁移到其他框架,只需重写接口层。
挑战
- 增加的代码量:相比传统的 MVC,多了用例类、DTO、异常类等。
- 学习曲线:团队需要理解并接受这种模式,以及对测试驱动开发有实践经验。
- 避免过度设计:对于简单的增删改查(CRUD)操作,直接使用 Eloquent 和控制器可能更合适,用例驱动更适合包含复杂业务逻辑的场景。
- 保持层间边界清晰:避免在用例中直接注入
Request或依赖 Http 上下文,这会导致测试困难。
总结实施路线图
- 培训团队:理解用例驱动开发的核心思想。
- 调整项目结构:创建
app/UseCases/等文件夹。 - 编写第一个用例测试:
tests/Unit/UseCases/...。 - 实现用例类:让测试通过。
- 更新控制器:使其变薄,仅负责调用用例并处理 HTTP 响应。
- 重构现有代码:逐步将现有 Controller 中的复杂业务逻辑抽取到用例中。
- 坚持测试优先:每个新功能都从测试用例开始。
用例驱动开发不是为了增加代码量,而是为了提高代码的可维护性、可测试性和架构的清晰度,当你的项目包含核心业务规则和复杂的用户流程时,这种模式会带来显著的价值。