Bernard the Tactician

Background processing in PHP

Márk Sági-Kazár

The Problem

Some ecommerce site

  • reseller
  • sell from multiple supplier
  • customer order -> report back
  • send orders to (multiple) remote APIs

Common issues...

  • uploading an image fails or takes too long
  • placing order fails or takes too long
  • page loading takes too long

...and their causes

  • improper error handling
  • badly scaled application
  • too much responsibilities on the frontend

Background processing...

  • runs in the background
  • runs without user interaction
  • (does not block the "foreground" process)

Definition

Parallel execution

  • run multiple actions in parallel
  • might block the "foreground" process
  • eg. multi-threading
image/svg+xml Process #0 Process #1 Process #2

Deferred execution

  • run multiple actions
  • different place/time/environment
  • the result of execution is not important for the original process
  • eg. job queues

Cron jobs

image/svg+xml Process #0 Process #1 Process #2

Deferred execution FTW

  • removes responsibilities from the frontend
  • may include retry strategy
  • improves scalability

...in PHP

Parallel execution

image/svg+xml Process #0 Process #1 Process #2

Multi-threading: pthreads

  • usual PHP builds are not thread-safe
  • many PHP extensions are not thread-safe

Deferred execution

image/svg+xml Process #0 Process #1 Process #2

Job queues

  • one or more queues
  • one or more workers
  • put the job in the queue and forget

Message Queues

  • inter-process communication
  • asynchronous
  • operated by a message broker

Message Broker

  • coordinates communication between parties
  • validates/routes/transforms/stores messages
  • ensures message delivery
  • eg. Rabbit MQ, Apache ActiveMQ/Kafka

Publish/Subscribe pattern

  • most common messaging pattern (in MQs)
  • sender publishes the message
  • message broker stores
  • receiver subscribes for specific messages

Beanstalk

Golden rule

Replace the first character with "ph"

<?php

use Pheanstalk\Pheanstalk;


// Configuration
$pheanstalk = new Pheanstalk('127.0.0.1');


// Publisher

$pheanstalk
  ->useTube('order')
  ->put("order product #1234 from supplier #5");


// Subscriber

$job = $pheanstalk
  ->watch('order')
  ->reserve();


// Process order
$data = $job->getData();


// Delete job from the queue
$pheanstalk->delete($job);

Bernard

<?php

// Configuration
$producer = ...;
$consumer = ...;
$queue = ...;


// Publisher (== producer)
$producer->produce(
    new Message\DefaultMessage('Order', [
        'product_id' => 1234,
        'supplier_id' => 5,
    ]),
    $queue->getName()
);


// Subscriber (== consumer)
$consumer->consume($queue);

Command pattern

  • message
  • information about an action to be executed
  • no return value, but can trigger events
  • improves testability

Tactician

<?php

declare(strict_types=1);

final class Order
{
    private $productId;
    private $supplierId;

    public function __construct(int $productId, int $supplierId)
    {
        $this->productId = $productId;
        $this->supplierId = $supplierId;
    }

    public function getProductId() : int
    {
        return $this->productId;
    }


    public function getSupplierId() : int
    {
        return $this->supplierId;
    }
}

final class OrderHandler
{
    // some dependencies

    public function handle(Order $command)
    {
        // do something
    }
}
<?php

// Configuration
$commandBus = ...;
$consumer = ...;
$queue = ...;


// Producer
$command = new Order(1234, 5);

$commandBus->handle($command);


// Consumer
$consumer->consume($queue);

Demo

Questions?

Thank you!

Bernard the Tactician

By Márk Sági-Kazár

Loading comments...

More from Márk Sági-Kazár