Microservices in PHP: From Theory to Production
The transition from a monolith to a microservices architecture in PHP is not just a technological upgrade, but a fundamental shift in a developer's mindset. At an advanced level, we face real challenges: distributed transactions, data consistency, network latency, and debugging complexity. In this article, we will break down key patterns and practical techniques that will help build a reliable and scalable system in PHP.
1. Communication Between Services: Synchronous vs Asynchronous
Choosing a communication protocol is the first thing an architect encounters. In the PHP ecosystem, there are two main approaches.
Synchronous Communication via REST/gRPC
Ideal for operations requiring an immediate response (e.g., checking a user's balance). However, it creates tight dependencies: if service B goes down, service A will also fail. It is recommended to use the Circuit Breaker pattern.
// Example of Circuit Breaker in PHP using the Guzzle libraryuse GuzzleHttp\\Client;use GuzzleHttp\\Exception\\ConnectException;
class CircuitBreaker { private int $failureCount = 0; private int $threshold = 3; private int $timeout = 30; // seconds private bool $open = false; private int $lastFailureTime = 0;
public function call(string $url): ?array { if ($this->isOpen()) { return null; // Fast short-circuit }
try { $client = new Client(['timeout' => 2.0]); $response = $client->get($url); $this->reset(); return json_decode($response->getBody(), true); } catch (ConnectException $e) { $this->recordFailure(); return null; } }
private function isOpen(): bool { if (!$this->open) return false; if (time() - $this->lastFailureTime > $this->timeout) { $this->open = false; // Half-open state return false; } return true; }
private function recordFailure(): void { $this->failureCount++; $this->lastFailureTime = time(); if ($this->failureCount >= $this->threshold) { $this->open = true; } }
private function reset(): void { $this->failureCount = 0; $this->open = false; }}Asynchronous Communication via Queues (RabbitMQ, Kafka)
For operations that do not require an immediate response (sending emails, generating reports). Use the php-amqplib library.
// Publishing an event to RabbitMQuse PhpAmqpLib\\Connection\\AMQPStreamConnection;use PhpAmqpLib\\Message\\AMQPMessage;
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');$channel = $connection->channel();
$channel->queue_declare('order_created', false, true, false, false);
$data = json_encode([ 'order_id' => 12345, 'user_id' => 678, 'total' => 1500.00]);$msg = new AMQPMessage($data, ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]);$channel->basic_publish($msg, '', 'order_created');
$channel->close();$connection->close();2. Data Management: CQRS and Event Sourcing
In microservices, each service should own its data. But what to do when you need to combine data from different services for a report? The CQRS (Command Query Responsibility Segregation) pattern comes to the rescue.
Implementing Simple CQRS in PHP
We separate commands (write) and queries (read). For writing, we use Doctrine ORM; for reading, we use direct SQL queries to the read model.
// Command: creating an orderclass CreateOrderCommand { public function __construct( public readonly int $userId, public readonly array $items, public readonly float $total ) {}}
// Handler