Exposing webhooks via mezzio-swoole
I was first introduced to the concept of webhooks via a 2009 blog post by John Herren, a former colleague at Zend. At the time, they were in their infancy; today, they're ubiquituous, as they provide a mechanism for a service to notify interested parties of events. This saves traffic; instead of consumers polling an API for event changes, the service notifies them directly. It also means that the consumer does not need to setup things like cronjobs; they instead setup a webhook endpoint, register it with the service provider, and their application takes care of the rest.
The thing is, handling a webhook can often lead to additional processing, and you are expected to send an immediate response to the provider indicating you received the event.
How can you achieve this?
Offloading processing
It's likely no secret that I'm a fan of Mezzio and OpenSwoole1. Running PHP in a persistent process forces me to think about state in my applications, which in turn generally forces me to be more careful and explicit in how I code things. On top of that, I get the benefit of persistent caching, better performance, and more.
One feature I pushed into mezzio-swoole (the Swoole and OpenSwoole bindings for Mezzio) was functionality for working with swoole task workers. There's a variety of ways to use the functionality, but my favorite is by using a PSR-14 EventDispatcher to dispatch an event to which I attach deferable listeners.
What does that look like?
Let's say I have a GitHubWebhookEvent
, for which I have associated a GitHubWebhookListener
2 in my event dispatcher.
I would dispatch this event as follows:
/** @var GitHubWebhookEvent $event */
$dispatcher->dispatch($event);
The nice part about this is that the code dispatching the event does not need to know how the event is processed, or even when. It just dispatches the event and moves on.
To make the listener deferable, in Mezzio applications, I can associate a special delegator factory provided by the mezzio-swoole package with the listener. This is done with standard Mezzio dependency configuration:
use Mezzio\Swoole\Task\DeferredServiceListenerDelegator;
return [
'dependencies' => [
'delegators' => [
GitHubWebhookListener::class => [
DeferredServiceListenerDelegator::class,
],
],
],
];
This approach means that my listener can have any number of dependencies, and be wired into the container, but when I request it, I'll be returned a Mezzio\Swoole\Task\DeferredServiceListener
instead.
This class will create a swoole task from the listener and event, which defers execution to the task workers, offloading it from the web workers.
Event state
Task workers receive a copy of the event, not the original instance. Any state changes your listener makes in the event instance will not be reflected in the instance present in your web workers. As such, you should only defer listeners that do not communicate state back to the dispatching code via the event.
Sharing an event dispatcher with the web server
mezzio-swoole defines a marker interface,
Mezzio\Swoole\Event\EventDispatcherInterface
. This interface is used to define an event-dispatcher service consumed byMezzio\Swoole\SwooleRequestHandlerRunner
for the purpose of dispatching swoole HTTP server events, getting around the "one event, one handler" rule swoole follows. However, that can mean that you end up with two different dispatchers in your application: one used by the swoole web server, and one by the application, and that means you cannot delegate tasks.To get around this, alias the
Mezzio\Swoole\Event\EventDispatcherInterface
service to thePsr\EventDispatcher\EventDispatcherInterface
service:use Mezzio\Swoole\Event\EventDispatcherInterface as SwooleEventDispatcher; use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcher; return [ 'dependencies' => [ 'alias' => [ SwooleEventDispatcher::class => PsrEve
Truncated by Planet PHP, read more at the original (another 6872 bytes)