Command Bus

2018-12-17 10:49 更新

簡介

Command bus 提供一個簡便的方法來封裝任務(wù),使你的程序更加容易閱讀與執(zhí)行,為了幫助我們更加了解使用「命令」的目的,讓我們來模擬建立一個可以購買 podcast 的網(wǎng)站。

用戶購買 podcasts 的過程中需要做很多事。例如,我們需要從用戶的信用卡扣款,將紀錄添加到數(shù)據(jù)庫以表示購買,并發(fā)送購買確認的電子郵件,或許,我們還需要進行許多驗證來確認用戶是否可以購買。

我們可以將這些邏輯通通放在控制器的方法內(nèi),然而,這樣做會有一些缺點,首先,控制器可能還需要處理許多其他的 HTTP 請求,包含復(fù)雜的邏輯,這會讓控制器變得很臃腫且難易閱讀,第二點,這些邏輯無法在這個控制器以外被重復(fù)使用,第三,這些命令無法被單元測試,為此我們還 得額外產(chǎn)生一個 HTTP 請求,并向網(wǎng)站進行完整購買 podcast 的流程。

比起將邏輯放在控制器內(nèi),我們可以選擇使用一個「命令」對象來封裝它,如 PurchasePodcast 命令。

           

建立命令

使用 make:command 這個 Artisan 命令可以產(chǎn)生一個新的命令類 :

php artisan make:command PurchasePodcast

           

新產(chǎn)生的類會被放在 app/Commands 目錄中,命令默認包含了兩個方法:構(gòu)造器和 handle 。當然,handle 方法執(zhí)行命令時,你可以使用構(gòu)造器傳入相關(guān)的對象到這個命令中。例如:

class PurchasePodcast extends Command implements SelfHandling {

    protected $user, $podcast;

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct(User $user, Podcast $podcast)
    {
        $this->user = $user;
        $this->podcast = $podcast;
    }

    /**
     * Execute the command.
     *
     * @return void
     */
    public function handle()
    {
        // Handle the logic to purchase the podcast...
        event(new PodcastWasPurchased($this->user, $this->podcast));
    }}

           

handle 方法也可以使用類型提示依賴,并且通過 服務(wù)容器 機制自動進行依賴注入。例如:

    /**
     * Execute the command.
     *
     * @return void
     */
    public function handle(BillingGateway $billing)
    {
        // Handle the logic to purchase the podcast...    }

           

           

調(diào)用命令

所以,我們建立的命令該如何調(diào)用它呢?當然,我們可以直接調(diào)用 handle 方法,然而使用 Laravel 的 "command bus" 來調(diào)用命令將會有許多優(yōu)點,待會我們會討論這個部分。

如果你有瀏覽過內(nèi)置的基本控制器,將會發(fā)現(xiàn) DispatchesCommands trait ,它將允許我們在控制器內(nèi)調(diào)用 dispatch 方法,例如:

public function purchasePodcast($podcastId){
    $this->dispatch(
        new PurchasePodcast(Auth::user(), Podcast::findOrFail($podcastId))
    );}

           

Command bus 將會負責執(zhí)行命令和調(diào)用 IoC 容器來將所需的依賴注入到 handle 方法。

你也可以將 Illuminate\Foundation\Bus\DispatchesCommands trait 加入任何要使用的類內(nèi)。若你想要在任何類的構(gòu)造器內(nèi)接收 command bus 的實體 ,你可以使用類型提示 Illuminate\Contracts\Bus\Dispatcher 這個接口。 最后,你也可以使用 Bus facade 來快速派發(fā)命令:

    Bus::dispatch(
        new PurchasePodcast(Auth::user(), Podcast::findOrFail($podcastId))
    );

           

從請求映射要注入命令的屬性

映射 HTTP 請求到命令是很常見的,所以,與其要你針對每個請求苦命地進行手動對應(yīng),Laravel 則提供一些有用的方法來輕松達到,讓我們來看一下 DispatchesCommands trait 提供的 dispatchFrom 方法:

$this->dispatchFrom('Command\Class\Name', $request);

           

這個方法將會檢查這個被傳入的命令類的構(gòu)造器,并取出來自于 HTTP 請求的變量(或其他任何的 ArrayAccess 對象) 并將其填入構(gòu)造器,所以,若命令類在構(gòu)造器接受 firstName 參數(shù),command bus 將會試圖從 HTTP 請求取出 firstName 參數(shù)。

dispatchFrom 方法的第三個參數(shù)允許你傳入數(shù)組,那些不在 HTTP 請求內(nèi)的參數(shù)可用這個數(shù)組來填入構(gòu)造器:

$this->dispatchFrom('Command\Class\Name', $request, [
    'firstName' => 'Taylor',]);

           

           

命令隊列

Command bus 不僅僅作為當下請求的同步作業(yè),也可以作為 Laravel 隊列任務(wù)的主要方法,所以,我們要如何指示 command bus 在背景作業(yè)而不是同步處理呢?非常簡單,首先,在建立新的命令時加上 --queued 參數(shù):

php artisan make:command PurchasePodcast --queued

           

正如你所見的,這讓命令增加了一點功能,即 Illuminate\Contracts\Queue\ShouldBeQueued 接口和SerializesModels trait 。 他們指示 command bus 使用隊列來執(zhí)行命令,以及優(yōu)雅的序列化和反序列化任何在命令內(nèi)被保存的 Eloquent 模型。

若你想將已存在的命令轉(zhuǎn)換為隊列命令,只需手動修改讓命令類實現(xiàn) Illuminate\Contracts\Queue\ShouldBeQueued 接口,它不包含方法,而是僅僅給調(diào)用員作為"標記接口"。

然后,一如往常撰寫你的命令,當你將命令派發(fā)到 bus,它將會自動將命令丟到背景隊列執(zhí)行,沒有比這個更容易的方法了。

想了解更多關(guān)于隊列命令的方法,請見隊列文檔.

           

命令管道

在命令被派發(fā)到處理器之前,你也可以將它通過"命令管道"傳遞到其他類去。命令管道操作上如 HTTP 中間件,除了是專門來給命令用的,例如,一個命令管道能夠在數(shù)據(jù)庫事務(wù)處理期間包裝全部的命令操作,或者僅作為執(zhí)行紀錄。

要將管道添加到 bus,只要從App\Providers\BusServiceProvider::boot 方法調(diào)用調(diào)用員的pipeThrough 方法:

$dispatcher->pipeThrough(['UseDatabaseTransactions', 'LogCommand']);

           

一個命令管道被定義在 handle 方法,就如個中間件:

class UseDatabaseTransactions {

    public function handle($command, $next)
    {
        return DB::transaction(function() use ($command, $next)
        {
            return $next($command);
        });
    }}

           

命令管道是通過 IoC 容器來達成,所以請自行在構(gòu)造器類型提示所需的依賴。

你甚至可以定義一個 閉包 來作為命令管道:

$dispatcher->pipeThrough([function($command, $next){
    return DB::transaction(function() use ($command, $next)
    {
        return $next($command);
    });}]);


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號