本文翻译自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,不如在必要的时候才推送到队列中。