Aller au contenu principal

Séparer les commandes et la facturation avec les microservices BowPHP

· 5 minutes de lecture
Franck DAKIA
Principal maintainer

À mesure qu'une application grandit, certaines responsabilités souhaitent vivre de leur côté — leur propre rythme de déploiement, leur propre montée en charge, leur propre équipe. La facturation en est un exemple classique. Dans ce billet, nous allons extraire la facturation dans un service distinct et faire dialoguer notre application Commandes avec celui-ci grâce au package bowphp/microservice via Redis.

Nous utiliserons les deux modes de communication offerts par le package :

  • Requête/Réponse (RPC) avec send() — lorsque Commandes a besoin d'une réponse en retour.
  • Fire-and-forget (Événement) avec emit() — lorsque Commandes veut simplement annoncer qu'un événement s'est produit.

Le scénario

Deux applications :

  • Commandes — l'application orientée client. Lorsqu'un client valide son panier, elle a besoin d'un montant de facture immédiatement (RPC), et elle veut annoncer order.placed pour que d'autres services puissent réagir de manière asynchrone (événement).
  • Facturation — possède les factures. Elle expose un gestionnaire RPC invoice.create et écoute les événements order.placed.

Les deux partagent un courtier Redis.

Étape 1 — Installer le package (les deux applications)

composer require bowphp/microservice

Enregistrez le fournisseur dans le kernel de chaque application :

app/Kernel.php
public function configurations(): array
{
return [
\Bow\Microservice\Bow\MicroserviceConfiguration::class,
];
}

Publiez ensuite le fichier de configuration :

php bow microservice:publish-config

Étape 2 — Pointer les deux applications vers Redis

Dans config/microservice.php, sélectionnez le transport Redis et les coordonnées du courtier. C'est identique des deux côtés :

config/microservice.php
return [
'transport' => 'redis',
'timeout' => 3.0,

'controllers' => [
// La Facturation enregistre son consommateur ici (voir l'Étape 4)
],

'redis' => [
'host' => app_env('MICROSERVICE_HOST', 'redis.internal'),
'port' => (int) app_env('MICROSERVICE_PORT', 6379),
'password' => app_env('MICROSERVICE_REDIS_PASSWORD', null),
],
];

Étape 3 — Le côté Commandes (le client)

Le fournisseur lie un ClientProxy partagé dans le conteneur. Injectez-le par son type et vous êtes prêt à dialoguer avec la Facturation.

app/Controllers/CheckoutController.php
namespace App\Controllers;

use Bow\Microservice\Client\ClientProxy;

class CheckoutController
{
public function __construct(private ClientProxy $client) {}

public function store(int $orderId)
{
// 1. RPC : demander à la Facturation de créer la facture et attendre le montant.
$invoice = $this->client->send('invoice.create', [
'order_id' => $orderId,
'lines' => $this->orderLines($orderId),
]);

// 2. Événement : annoncer la commande ; nous n'attendons personne.
$this->client->emit('order.placed', [
'order_id' => $orderId,
'total' => $invoice['total'],
]);

return response()->json([
'invoice_id' => $invoice['id'],
'total' => $invoice['total'],
]);
}
}

send() bloque jusqu'à ce que la Facturation réponde (ou jusqu'au déclenchement du timeout) ; emit() retourne immédiatement et n'attend jamais.

Les appels lents disposent de leur propre timeout

Le timeout par défaut provient de config('microservice.timeout'), mais un seul appel lourd peut le surcharger :

$report = $this->client->send('billing.report', $payload, timeout: 30.0);

emit() est de type fire-and-forget, il n'accepte donc pas de timeout.

Étape 4 — Le côté Facturation (le serveur)

Générez un consommateur :

php bow add:consumer InvoiceConsumer

Annotez les gestionnaires avec #[MessagePattern] pour le RPC et #[EventPattern] pour les événements. Le nom de la méthode importe peu — c'est le pattern qui route le message :

app/Consumers/InvoiceConsumer.php
namespace App\Consumers;

use App\Models\Invoice;
use Bow\Microservice\Consumer\MessagePattern;
use Bow\Microservice\Consumer\EventPattern;

class InvoiceConsumer
{
/**
* RPC : créer une facture et la retourner à l'appelant.
*/
#[MessagePattern('invoice.create')]
public function create(array $payload): array
{
$invoice = Invoice::create([
'order_id' => $payload['order_id'],
'total' => $this->sum($payload['lines']),
]);

$invoice->persist();

return [
'id' => $invoice->id,
'total' => $invoice->total,
];
}

/**
* Événement : réagir à une commande passée — aucune réponse retournée.
*/
#[EventPattern('order.placed')]
public function onOrderPlaced(array $payload): void
{
// par ex. mettre en file un e-mail de reçu, mettre à jour les analytics, etc.
}

private function sum(array $lines): float
{
return array_sum(array_column($lines, 'amount'));
}
}

Enregistrez le consommateur dans le config/microservice.php de la Facturation :

config/microservice.php
'controllers' => [
\App\Consumers\InvoiceConsumer::class,
],

Étape 5 — Lancer l'écouteur de la Facturation

La Facturation exécute un processus de longue durée qui s'abonne au courtier et distribue les messages entrants vers vos gestionnaires :

php bow microservice:listen

Besoin de surcharger la configuration pour un processus ponctuel ? La commande accepte des options :

php bow microservice:listen \
--transport=redis \
--controllers="App\Consumers\InvoiceConsumer" \
--patterns="invoice.create,order.placed"

Comment les pièces s'assemblent

Validation du panier client


Commandes ──send('invoice.create')──▶ Facturation (RPC, attend le montant)
◀──────── facture ─────────

└─emit('order.placed')────▶ Facturation (événement, fire-and-forget)
+ tout autre abonné

Pourquoi est-ce important

  • Déploiements indépendants — la Facturation peut être livrée selon son propre calendrier.
  • Le bon outil par appelsend() synchrone là où vous avez besoin d'une réponse, emit() asynchrone là où vous n'en avez pas besoin.
  • Changer de transport, pas de code — démarrez sur Redis ; passez plus tard à RabbitMQ ou Kafka en modifiant config/microservice.php, sans toucher à vos gestionnaires.

Lorsqu'un service a besoin de monter en charge, d'échouer ou de se déployer indépendamment du reste, c'est ainsi que vous tracez la frontière — sans quitter la boîte à outils BowPHP. La documentation complète des microservices couvre les autres transports, les clients multiples et le réglage par processus.

Il manque quelque chose ?

Si vous rencontrez des problèmes avec la documentation ou si vous avez des suggestions pour améliorer la documentation ou le projet en général, veuillez déposer une issue pour nous, ou envoyer un tweet mentionnant le compte Twitter @bowframework ou directement sur le github.