如何有選擇的將事件監聽器 (Event Listeners) 推送到佇列中

Epona發表於2019-05-24

本文翻譯自Conditionally pushing event listeners to queue

情景模擬

假設有以下情況,你正在構建一個日成交量成千上萬個訂單的線上商城,一個很酷的功能是如果使用者購買超過 1萬元將會獲得一個優惠券,讓我們看看在Laravel中怎樣實現。

程式碼實現

在每一筆訂單支付成功的時候我們可以通過 Laravel 的 Event事件執行一系列的操作:

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        NewPurchase::class => [
            SendInvoice::class,
            UpdateMetrics::class,
            SendShippingOrder::class,
            SendGiftCoupon::class,
        ],
    ];
}

當每一個事件觸發時,SendGiftCoupon 將會呼叫,程式碼如下:

class SendGiftCoupon
{
    public function handle($event)
    {
        $customer = $event->customer;

        if ($customer->purchases >= 10000 && ! $customer->wasGifted()) {
            $coupon = Coupon::createForCustomer($customer);

            Mail::to($customer)->send(new CouponGift($customer));
        }
    }
}

在這個 Listener 中我們會檢查消費者能否獲得優惠券,如果可以,那麼傳送優惠券到消費者的郵箱中。

優化處理

上面程式碼的問題是我們必須要等到所有步驟處理完畢之後才會生成訂單。如果我們只同步處理訂單,將傳送郵件等動作稍後處理是一個更好的解決方案,在 Laravel 中 我們可以通過繼承 ShouldQueue 很簡單的實現:

class SendGiftCoupon implements ShouldQueue
{
    // 其餘程式碼
}

直到這裡,我們看似解決了問題。

問題浮現

那麼究竟有多少顧客會達到 1 萬元的消費呢?每次購買時將傳送優惠券推送到佇列是否合理(畢竟購買金額達到 1 萬元的始終是少數,因此大部分的 Listener 將不會進行任何的操作)?這樣你將會看到你的佇列中有很多無用的 Job 在執行。

解決方案

一個解決方案是隻在有必要的時候才進行佇列處理,其餘時候則可以完全忽略,幸運的是 Laravel 中我們可以非常容易的實現。我們僅需要做的就是在 Listener 類中加入 shouldQueue 方法即可:

public function shouldQueue($event)
{
    return $event->customer->purchases > 10000 &&
        ! $event->customer->wasGifted();
}

加入上面的程式碼後,Laravel 在推送佇列前會進行檢查,符合要求的才會進行處理,否則不會將其加入到佇列中。

結論

與其讓我們的佇列處理無數的無效 Job,不如在必要的時候才推送到佇列中。

There's nothing wrong with having a little fun.

相關文章