在Laravel中优化Actions的限制和不足
在过去的一年多时间里, 基于Action(行动)的方法在Laravel世界中越来越受欢迎.
我是这个方法的忠实粉丝,并在较早的时候就采用了它。
然而, 随着时间的推移, 我发现我把所有可以想象到的东西都放在Actions命名空间下, 而目录也越来越拥挤.
我从试图简化我的过程到把所有东西都提取到动作中--而每次找到正确的动作都变得越来越复杂。
我决定做点什么,找到一种方法,让我仍能从这种方法中受益,但又不至于把所有东西都归类为行动。
由于以前使用过各种架构模式,我回想了一些对我很有效的模式。
我是CQRS(Command Query Responsibility Segregation)的忠实粉丝。
然而,我不喜欢手动添加我所有的映射的方法,这样我就可以靠在命令总线或查询总线上。
但是我可以采取我喜欢的这种模式,靠着Laravels容器来做我需要的事情。
所以我决定把我归类为Actions的东西拆成更接近CQRS的方法。
命令是我想要执行的写动作,而查询是我的读动作。让我们看一下这些变化的例子。
namespace App\Actions;
final class CreateNewUserAction
{
public function handle(NewUser $user): Model|User
{
return DB::transaction(
callback: static fn () => User::query()->create($user->toArray()),
attempts: 2,
);
}
}
这是一个典型的写动作的例子。
它将接受一个DTO作为其有效载荷,并在数据库事务中执行一个写入查询。
这对我来说很有效,但作为一个命令,它看起来像什么呢?
namespace App\Commands\Users;
final class CreateNewUser
{
public function handle(NewUser $user): Model|User
{
return DB::transaction(
callback: static fn () => User::query()->create($user->toArray()),
attempts: 2,
);
}
}
这是同样的事情,但在不同的命名空间下,所以我的代码中有更好的分离。
你可以用类似的方法来处理动作,并创建嵌套的命名空间--但你不会得到与你分割读写操作时一样的意图分离。
接下来让我举一个更复杂的例子。
如果你看过我的Laravel业务流程建模教程,你就会明白这个过程。
让我们假设我们有一个流程,我们想为我们的用户创建一个新的团队。
我们为此采取的步骤如下:
1.创建一个新的团队模型.
2.将我们的用户作为一个团队成员。
3.给我们的用户发电子邮件,通知他们。
4.设置一个团队所需的任何额外资源,也许是计费。
如果你在控制器中看,这是一个很大的问题,如果你使用动作,甚至命令和查询--你最终会有很多东西被拉到一个地方,命名变得混乱,而且你没有获得多少好处。
相反,我使用不同的方法,建立了一个流程。
我们在应用程序中做的很多事情都是流程,是为实现一个结果而需要做的事情的顺序列表。
这也没什么不同。
首先,看一下底层代码--我们想要实现的抽象过程。
abstract class Process
{
protected array $tasks = [];
public function run(object $payload): mixed
{
return Pipeline::send(
passable: $payload,
)->through(
pipes: $this->tasks,
)->thenReturn();
}
}
我们要做的就是创建一个我们想要运行的任务集合--这意味着进程可以共享任务而不需要代码重复。
然后,我们通过管道门面运行所有这些。
不过,这与命令和查询有什么关系呢?让我们深入了解一下我们的例子。
final class TeamCreationProcess extends Process
{
protected array $tasks = [
CreateNewTeam::class,
AssignNewTeamMember::class,
NotifyTeamOwnerOfNewMember::class,
SetupBillingForTeam::class,
];
}
在我们的控制器中,我们所需要做的就是:
final class StoreController
{
public function __construct(
private readonly TeamCreationProcess $process,
) {}
public function __invoke(StoreRequest $request): Responsable
{
$this->process->run($request->payload());
return new MessageResponse(
message: 'Your team has been created',
);
}
}
很干净,对吗?
让我们看一下这些任务中的几个,看看我们在命令和查询方面的倚重。
final class CreateNewTeam
{
public function __construct(
private readonly NewTeamCreation $command,
) {}
public function __invoke(object $payload, Closure $next): mixed
{
$this->command->handle($payload);
return $next($payload);
}
}
我们的任务可以调用我们的命令,而不是实现同样的逻辑。
你可以在这里添加写入动作。
然而,通过仍然使用一个命令--你可以从API、Web和CLI轻松地创建一个新的团队,而不需要把它包裹在一个过程中。
如果你有一个简单的CLI命令,你很可能想避免一个过程--你想要一个快速的动作。
我现在使用的方法来自于在构建不同类型的应用程序时学到的经验,虽然创建多个类来实现一件事可能有点费力,但随着你的应用程序的发展,你会对这样做表示感谢。
通过将我们需要的东西分解成一个过程,我们可以随着时间的推移通过添加额外的步骤来微调这个过程--而不影响正在发生的事情。
我不知道这种方法是否有一个架构术语,但这是我非常喜欢的一种方法。
我的FormRequest负责创建我想通过的有效载荷。
我的流程负责我想运行的任务。
我的任务负责调用正确的读或写操作,而我的读和写操作只需要担心读或写数据。
这都是小的、构建良好的、可单元测试的代码片断,很容易复制和重构,而不会对我的整个应用产生不利影响。
转:
https://laravel-news.com/going-past-actions-in-laravel
相关文章