Microservices com Laravel: comunicação entre serviços
1. Introdução à Arquitetura de Microservices com Laravel
Microservices é um estilo arquitetural onde uma aplicação é composta por pequenos serviços independentes, cada um executando um processo único e se comunicando através de mecanismos leves, como HTTP ou mensageria. No ecossistema PHP/Laravel, essa abordagem permite escalar equipes e funcionalidades de forma granular, evitando os gargalos de monolitos tradicionais.
Os principais desafios incluem:
- Comunicação: como serviços trocam dados de forma confiável
- Consistência de dados: manter integridade em operações distribuídas
- Descoberta de serviços: localizar dinamicamente instâncias de serviços
As estratégias de comunicação dividem-se em síncrona (HTTP/REST) e assíncrona (filas, eventos, message brokers). Cada uma tem trade-offs específicos que exploraremos a seguir.
2. Comunicação Síncrona via HTTP com Laravel
O Laravel fornece um cliente HTTP expressivo baseado no Guzzle, ideal para consumir APIs REST entre microservices.
use Illuminate\Support\Facades\Http;
$response = Http::withToken(config('services.usuarios.token'))
->timeout(5)
->retry(3, 100)
->get('http://api-usuarios:8001/api/usuarios/'.$userId);
if ($response->successful()) {
$usuario = $response->json();
} else {
Log::error('Falha ao buscar usuário', [
'status' => $response->status(),
'body' => $response->body()
]);
// Fallback ou retorno de erro
}
Para maior resiliência, podemos implementar um Circuit Breaker com o pacote laravel-http-circuit-breaker:
use App\CircuitBreaker\CircuitBreaker;
$cb = new CircuitBreaker('servico-pagamentos', 5, 30); // 5 falhas, 30s de abertura
if ($cb->isAvailable()) {
try {
$response = Http::post('http://pagamentos:8002/api/cobrar', $data);
$cb->recordSuccess();
} catch (\Exception $e) {
$cb->recordFailure();
throw $e;
}
} else {
// Retornar resposta de fallback ou cache
}
Autenticação entre serviços pode ser feita com:
- API Tokens: simples, usando Authorization: Bearer com tokens pré-compartilhados
- JWT: com Laravel Sanctum para tokens expiráveis e claims personalizadas
- OAuth2: com Laravel Passport para fluxos mais complexos (client credentials, password grant)
Exemplo com Sanctum:
// Serviço A (cliente)
$token = 'servico-a-token-secreto';
$response = Http::withToken($token)->post('http://servico-b/api/dados');
// Serviço B (servidor)
Route::middleware('auth:sanctum')->get('/api/dados', function () {
return auth()->user();
});
3. Comunicação Assíncrona com Filas e Eventos
A comunicação assíncrona desacopla os serviços, aumentando a resiliência e a escalabilidade. O Laravel oferece suporte nativo a filas (Redis, SQS, RabbitMQ) e eventos.
Exemplo com Laravel Queues (Redis):
// Disparando um job para outro serviço
dispatch(new ProcessarPagamento($pedidoId))->onQueue('pagamentos');
// Job no serviço de pagamentos
class ProcessarPagamento implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function handle()
{
// Lógica de processamento
$this->release(30); // Re-tentar após 30s em caso de falha
}
public function failed(\Throwable $e)
{
Log::error('Falha no pagamento', ['pedido' => $this->pedidoId]);
}
}
Event-Driven Architecture com broadcasting:
// Serviço de Pedidos
class PedidoCriado
{
public function __construct(public int $pedidoId, public array $dados) {}
}
event(new PedidoCriado($pedido->id, $pedido->toArray()));
// Serviço de Estoque (ouvinte)
class AtualizarEstoque implements ShouldQueue
{
public function handle(PedidoCriado $event)
{
// Atualizar estoque baseado no pedido
}
}
Para garantir idempotência, cada mensagem deve conter um identificador único e o serviço receptor deve verificar se já processou aquele ID:
public function handle(PedidoCriado $event)
{
$messageId = $event->dados['message_id'] ?? uniqid();
if (Cache::has("processed:{$messageId}")) {
return; // Mensagem já processada
}
Cache::put("processed:{$messageId}", true, 3600);
// Processar...
}
4. Message Brokers Avançados: RabbitMQ e Kafka no Laravel
Para cenários de alta throughput e garantia de entrega, utilizamos message brokers especializados.
RabbitMQ com vladimir-yuldashev/laravel-queue-rabbitmq:
// config/queue.php
'rabbitmq' => [
'driver' => 'rabbitmq',
'host' => env('RABBITMQ_HOST', '127.0.0.1'),
'queue' => env('RABBITMQ_QUEUE', 'default'),
'exchange' => 'laravel_exchange',
'exchange_type' => 'topic',
'routing_key' => 'pedidos.*',
],
// Enviar mensagem com routing key
dispatch(new PedidoCriado($data))->onConnection('rabbitmq')->onQueue('pedidos.criados');
Kafka com mateusjunges/laravel-kafka:
// Producer
use Junges\Kafka\Facades\Kafka;
Kafka::publishOn('pedidos-stream')
->withHeaders(['correlation-id' => (string) Str::uuid()])
->withBodyKey('pedido_id', $pedidoId)
->send();
// Consumer
$consumer = Kafka::consumer(['pedidos-stream'])
->withHandler(function (\Junges\Kafka\Message $message) {
$correlationId = $message->getHeaders()['correlation-id'];
$pedidoId = $message->getBody()['pedido_id'];
// Processar...
})
->build();
$consumer->consume();
Padrões importantes:
- Command Bus: cada mensagem representa um comando (ex: CriarPedido)
- Event Sourcing: armazenar eventos como fonte da verdade
- Headers de correlação: rastrear requisições através dos serviços
5. Service Discovery e Balanceamento de Carga
Em ambientes dinâmicos (Kubernetes, Docker Swarm), os serviços mudam de endereço frequentemente. Service Discovery resolve esse problema.
Implementação com Consul:
// Serviço de descoberta
use SensioLabs\Consul\ServiceFactory;
$consul = new ServiceFactory(['base_uri' => 'http://consul:8500']);
$agent = $consul->get('agent');
// Registrar serviço
$agent->registerService([
'Name' => 'api-usuarios',
'Address' => gethostbyname(gethostname()),
'Port' => 8001,
'Check' => [
'HTTP' => 'http://localhost:8001/health',
'Interval' => '10s'
]
]);
// Descobrir serviço
$health = $consul->get('health');
$services = $health->service('api-usuarios')->json();
$endpoint = $services[0]['Service']['Address'].':'.$services[0]['Service']['Port'];
Para balanceamento de carga, podemos usar laravel-service-discovery:
// config/service-discovery.php
return [
'services' => [
'api-usuarios' => [
'driver' => 'consul',
'endpoints' => [], // Preenchido dinamicamente
'fallback' => ['http://api-usuarios-static:8001'],
'cache_ttl' => 60, // segundos
],
],
];
// Uso
$url = ServiceDiscovery::resolve('api-usuarios');
$response = Http::get("{$url}/api/usuarios");
6. Monitoramento e Observabilidade da Comunicação
Logging centralizado com Laravel Telescope:
// Adicionar correlation ID a todas as requisições
public function boot()
{
Request::macro('correlationId', function () {
return $this->header('X-Request-ID') ?? (string) Str::uuid();
});
}
// Repassar para serviços downstream
Http::macro('withCorrelation', function () {
return $this->withHeader('X-Request-ID', request()->correlationId());
});
Métricas com Prometheus:
use Prometheus\CollectorRegistry;
use Prometheus\RenderTextFormat;
$registry = app(CollectorRegistry::class);
$counter = $registry->getOrRegisterCounter(
'app',
'http_requests_total',
'Total de requisições HTTP',
['service', 'method', 'status']
);
$counter->incBy(1, ['api-usuarios', 'GET', '200']);
Distributed Tracing com OpenTelemetry:
use Spatie\OpenTelemetry\Facades\Tracer;
Tracer::startSpan('processar-pagamento')
->setAttribute('pedido.id', $pedidoId)
->setAttribute('servico.origem', 'api-pedidos');
try {
// Lógica...
} finally {
Tracer::endSpan();
}
7. Tratamento de Erros e Resiliência
Exponential Backoff com Jitter:
public function retryWithBackoff(callable $operation, int $maxRetries = 3): mixed
{
$attempt = 0;
while (true) {
try {
return $operation();
} catch (\Exception $e) {
$attempt++;
if ($attempt > $maxRetries) throw $e;
$delay = min(1000 * pow(2, $attempt) + random_int(0, 500), 10000);
usleep($delay * 1000);
}
}
}
Sagas para transações distribuídas:
// Serviço de Pedidos
class CriarPedidoSaga
{
public function execute(array $data)
{
DB::beginTransaction();
try {
$pedido = Pedido::create($data);
// Chamada compensável
$this->reservarEstoque($pedido);
// Chamada compensável
$this->cobrarPagamento($pedido);
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
// Executar compensações
$this->compensarReserva($pedido);
$this->compensarPagamento($pedido);
throw $e;
}
}
}
8. Boas Práticas e Padrões de Projeto
API First com Contratos:
// Interface compartilhada entre serviços (via pacote)
interface ServicoUsuariosContract
{
public function buscarUsuario(int $id): array;
public function criarUsuario(array $dados): array;
}
// Implementação no serviço de usuários
class ServicoUsuariosController implements ServicoUsuariosContract
{
public function buscarUsuario(int $id): array
{
return Usuario::findOrFail($id)->toArray();
}
}
Testes de integração com mocks:
public function test_pedido_integra_com_estoque()
{
Http::fake([
'api-estoque:8003/*' => Http::response(['reservado' => true], 200),
]);
$response = $this->postJson('/api/pedidos', ['produto_id' => 1]);
$response->assertStatus(201);
}
Documentação viva com OpenAPI:
/**
* @OA\Post(
* path="/api/pedidos",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/PedidoRequest")
* ),
* @OA\Response(response=201, ref="#/components/responses/PedidoResponse")
* )
*/
public function store(Request $request) { ... }
Com essas práticas, sua arquitetura de microservices em Laravel será robusta, escalável e de fácil manutenção. A escolha entre comunicação síncrona e assíncrona deve considerar os requisitos de latência, consistência e tolerância a falhas de cada contexto.
Referências
- Documentação oficial do Laravel - HTTP Client — Guia completo sobre o cliente HTTP do Laravel, incluindo retry, timeouts e mocks.
- Laravel Queues - Documentação Oficial — Referência completa sobre filas no Laravel, incluindo Redis, SQS e RabbitMQ.
- Laravel Sanctum - API Tokens — Documentação oficial sobre autenticação via tokens para APIs e SPA.
- vladimir-yuldashev/laravel-queue-rabbitmq — Pacote popular para integração do Laravel com RabbitMQ como driver de fila.
- mateusjunges/laravel-kafka — Pacote para integração do Laravel com Apache Kafka, suportando producers e consumers.
- Spatie Laravel OpenTelemetry — Pacote para instrumentação de tracing distribuído com OpenTelemetry no Laravel.
- Martin Fowler - Microservices — Artigo seminal sobre conceitos e padrões de microservices, aplicável a qualquer stack.
- Laravel Service Discovery (Consul) — SDK PHP para integração com Consul, útil para service discovery em microservices.