Um dia eu estava tentando aprender os conceitos avançados do RabbitMQ, os casos de uso e as diferenças para os outros message brokers. Comecei lendo a boa documentação e então eu estava ansioso para experimentar em uma aplicação real.
Entretanto não foi tão simples de criar uma aplicação Symfony e conectar com o RabbitMQ. Os resultados da busca do Google não eram tão precisos e eu ainda precisei da ajuda do StackOverflow para instalar dependências adicionais.
Espero que eu tenha conseguido condensar toda a informação aqui e mostrá-la de uma forma simples e divertida.
O que eu vou construir?
Inicialmente eu pensei em criar uma aplicação web para explorar os diferentes padrões do RabbitMQ. Depois de sofrer com a integração do RabbitMQ com framework web popular, eu decidi dar um passo para trás e simplesmente só criar um endpoint que publica uma mensagem, e um consumidor que loga o conteúdo recebido. Então vamos construir!
Código final no github:
Symfony
Mas por que Symfony? É um framework PHP popular e no final eu gostei de como ficou a integração e a arquitetura com o RabbitMQ.
Primeiramente eu instalei o Symfony CLI e através dele, criei uma aplicação web tradicional:
symfony new --full php-symfony-rabbitmq
Posso rodar minha aplicação através do seguinte comando no diretório do projeto:
symfony serve
Symfony messenger
Symfony Messenger é a abstração de mensageria provido por uma dependência separada. Vamos instalá-la!
composer require symfony/messenger
Seguindo a boa documentação, eu criei uma classe simples para encapsular a mensagem a ser publicada:
final class SampleMessage
{
public function __construct(private string $content)
{
}
public function getContent(): string
{
return $this->content;
}
}
E a sua respectiva classe handler:
final class SampleMessangeHandler implements MessageHandlerInterface
{
public function __invoke(SampleMessage $message)
{
// magically invoked when an instance of SampleMessage is dispatched
print_r('Handler handled the message!');
}
}
Agora para verificar que tudo está funcionando até agora, eu adicionei um endpoint simples para enviar uma mensagem:
final class SampleController extends AbstractController
{
#[Route('/sample', name: 'sample')]
public function sample(MessageBusInterface $bus): Response
{
$message = new SampleMessage('content');
$bus->dispatch($message);
return new Response(sprintf('Message with content %s was published', $message->getContent()));
}
}
Ao testar o endpoint, verifico que a saída impressa pelo handler e pela resposta http está correta:
curl http://localhost:8000/sample
Handler handled the message!Message with content content was published
Onde está o RabbitMQ?
Até agora não temos nem um sinal do RabbitMQ, mas não se preocupe porque deixei todo o terreno preparado para introduzi-lo.
Instalação das dependências
Imagem docker
Vou utilizar o docker para subir uma instância do RabbitMQ porque é muito fácil dessa forma. Só é preciso instalar o Docker Compose e então editar o arquivo .docker-compose.yml
no diretório do projeto PHP para adicionar um novo serviço:
version: '3'
services:
rabbitmq:
image: rabbitmq:3.9-management
ports:
- '5672:5672'
- '15672:15672'
Ao rodar docker-compose up
é possível verificar que a instância do RabbitMQ está rodando.
Extensão PECL AMQP
AMQP (Advanced Message Queuing Protocol), é o protocolo que sustenta o RabbitMQ. A instalação da sua extensão utilizando PECL (PHP Extension Community Language) foi um pouco difícil, pelo menos no MacOS:
Primeiro, instalei o HomeBrew:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Depois instalei rabbitmq-c
:
brew install rabbitmq-c
O que por fim permitiu a instalação da extensão:
pecl install amqp
Quando foi pedido para digitar o caminho para librabbitmq
, você precisa verificar qual é a versão instalada dentro do diretório /usr/local/Cellar/rabbitmq-c/
. A minha versão era a 0.11.0
:
Set the path to librabbitmq install prefix [autodetect] : /usr/local/Cellar/rabbitmq-c/0.11.0
Finalmente a última dependência:
composer require symfony/amqp-messenger
Que alívio! Agora posso voltar a programar.
Utilizando o poder da assincronicidade
Por padrão, o usuário e a senha criados na imagem docker são guest
, o que coincidentemente é a linha exata que eu preciso descomentar no arquivo .env
, que expõe a conexão ao RabbitMQ como uma variável de ambiente:
###> symfony/messenger ###
# Choose one of the transports below
# MESSENGER_TRANSPORT_DSN=doctrine://default
MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
###< symfony/messenger ###
Agora com essa nova variável, eu preciso dizer a aplicação quais mensagens deveriam ser tratadas por esse transporte.
Então no arquivo config/packages/messanger.yaml
eu defino um novo transport
e o tipo de mensagem a ser tratada:
framework:
messenger:
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
# Route your messages to the transports
'App\Message\SampleMessage': async
Nesse ponto eu posso testar o endpoint novamente:
curl http://localhost:8000/sample
E em outro terminal eu ligo o consumidor:
php bin/console messenger:consume async -vv
Esse comando imprime várias mensagens bem verbosas, mas as partes importantes são:
:
[messenger] Received message App\Message\SampleMessage
[messenger] App\Message\SampleMessage was handled successfully (acknowledging to transport).
[messenger] Message App\Message\SampleMessage handled by App\MessageHandler\SampleMessangeHandler
E para ter certeza que a aplicação está realmente usando o RabbitMQ, eu posso acessar o admin no endereço http://localhost:15672 e verificar esse gráfico bonito:
Conclusão
Finalmente eu tenho um setup básico do RabbitMQ e Symfony! Agora eu posso dar vida a vários side projects e explorar os padrões de mensageria. Espero que tenha gostado e também aprendido algo!