PHP 8.2: O que há de novo?
A nova versão do PHP está quase a chegar. Quais são as novas implementações que deve conhecer? Consulte este artigo para ficar a saber!
Leia a primeira parte da nossa série PHP dedicada à comunicação de microsserviços na estrutura Symfony e a forma mais popular - comunicação AMQP usando RabbitMQ.
A arquitetura moderna das aplicações obrigou os programadores a mudar a forma de pensar sobre a comunicação entre os diferentes componentes dos sistemas de TI. Antigamente, a questão era mais simples - a maioria dos sistemas era criada como estruturas monolíticas ligadas entre si por uma rede de ligações de lógica empresarial. A manutenção dessas dependências numa PHP projeto foi um enorme desafio para Desenvolvedores PHPe a crescente popularidade do SaaS e o enorme aumento da popularidade das nuvem serviços causou que hoje em dia se ouça cada vez mais falar de microsserviços e a modularidade das aplicações.
Como é que, ao criar microsserviços independentes, podemos fazê-los trocar informações entre si?
Este artigo é o primeiro de uma série de publicações sobre comunicação de microsserviços em Symfony e abrange a forma mais popular - a comunicação AMQP utilizando o RabbitMQ.
Criar duas aplicações independentes e conseguir a comunicação entre elas utilizando apenas o barramento de mensagens.
Temos duas aplicações imaginárias e independentes:
* app1que envia notificações por correio eletrónico e SMS aos empregados
* app2O sistema de gestão do trabalho: permite-lhe gerir o trabalho dos empregados e atribuir-lhes tarefas.
Pretendemos criar um sistema moderno e simples através do qual a atribuição de trabalho a um empregado em app2 enviará uma notificação ao cliente utilizando app1. Apesar das aparências, isto é muito simples!
Para efeitos deste artigo, utilizaremos o Symfony mais recente (versão 6.1 no momento da redação) e a versão mais recente do PHP (8.1). Em alguns passos muito simples, criaremos um site local funcional Docker com dois microsserviços. Tudo o que precisa é de:
* um computador funcional,
* Docker + instalado Ambiente do Docker Compose
* e um CLI do Symfony e algum tempo livre.
Usaremos os recursos do Docker como uma ferramenta de virtualização de aplicativos e de contêineres. Vamos começar criando uma árvore de diretórios, uma estrutura para dois Aplicações Symfonye descrever a infraestrutura dos nossos ambientes utilizando o docker-compose.yml ficheiro.
cd ~
mkdir microservices-in-symfony
cd microservices-in-symfony
symfony nova app1
symfony nova app2
toque em docker-compose.yml
Criámos dois diretórios para duas aplicações Symfony separadas e criámos um diretório docker-compose.yml para iniciar o nosso ambiente.
Vamos adicionar as seguintes secções ao ficheiro docker-compose.yml file:
versão: "3.8
serviços:
app1:
nome de utilizador: app1
compilação: app1/.
restart: on-failure
ficheiro env: app1/.env
ambiente:
APPNAME: app1
tty: true
stdinopen: true
app2:
nome de utilizador: app2
construir: app2/.
restart: on-failure
ficheiro env: app2/.env
ambiente:
APPNAME: app2
tty: true
stdinopen: true
rabbitmq:
nome de utilizador: rabbitmq
imagem: rabbitmq:management
portas:
- 15672:15672
- 5672:5672
ambiente:
- RABBITMQDEFAULTUSER=utilizador
- RABBITMQDEFAULT_PASS=senha
Fonte código disponível diretamente: thecodest-co/microservices-in-symfony/blob/main/docker-compose.yml
Mas espere, o que aconteceu aqui? Para quem não está familiarizado com o Docker, o ficheiro de configuração acima pode parecer enigmático, no entanto o seu objetivo é muito simples. Usando o Docker Compose, estamos construindo três "serviços":
Para um funcionamento correto, continuamos a precisar de Dockerfile que são a fonte para construir as imagens. Portanto, vamos criá-los:
touch app1/Dockerfile
toque em app2/Dockerfile
Ambos os ficheiros têm exatamente a mesma estrutura e têm o seguinte aspeto:
DE php:8.1
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
COPIAR . /app/
WORKDIR /app/
ADICIONAR https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN chmod +x /usr/local/bin/install-php-extensions && sync &&
install-php-extensions amqp
EXECUTAR apt-get update
&& apt-get install -y libzip-dev wget --no-install-recommends
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
EXECUTAR docker-php-ext-install zip;
CMD bash -c "cd /app && composer install && php -a"
Código fonte disponível diretamente: /thecodest-co/microservices-in-symfony/blob/main/app1/Dockerfile
O ficheiro acima é utilizado pelo Docker Compose para construir um contentor a partir de uma imagem PHP 8.1 com o Composer e a extensão AMQP instalada. Além disso, ele executa o intepretador PHP no modo append para manter o contêiner em execução em segundo plano.
O seu diretório e árvore de ficheiros devem agora ter o seguinte aspeto:
.
├── app1
│ └── Dockerfile
| (...) # Estrutura do aplicativo Symfony
├─── app2
│ └── Dockerfile
| (...) # Estrutura do aplicativo Symfony
└── docker-compose.yml
Vamos começar com o app1 e a primeira aplicação.
No nosso exemplo, é uma aplicação que ouve e consome mensagens da fila enviadas por app2 como descrito nos requisitos:
atribuição de um trabalho a um trabalhador em
app2enviará uma notificação ao cliente
Vamos começar por adicionar as bibliotecas necessárias. O AMQP é suportado nativamente para o symfony/messenger extensão. Adicionalmente, instalaremos monólogo/monólogo para manter o registo dos registos do sistema para facilitar a análise do comportamento das aplicações.
cd app1/
symfony composer req amqp ampq-messenger monolog
Após a instalação, foi adicionado um ficheiro adicional em config/packages/messenger.yaml. É um ficheiro de configuração para o componente Symfony Messenger e não precisamos do seu configuração completa.
Substitua-o pelo ficheiro YAML abaixo:
quadro:
messenger:
# Descomente isto (e o transporte falhado abaixo) para enviar mensagens falhadas para este transporte para tratamento posterior.
# failure_transport: failed
transportes:
# https://symfony.com/doc/current/messenger.html#transport-configuration
external_messages:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
opções:
auto_setup: false
exchange:
nome: mensagens
tipo: direct
default_publish_routing_key: from_external
queues:
messages:
binding_keys: [from_external]
Código fonte disponível diretamente: thecodest-co/microservices-in-symfony/blob/main/app1/config/packages/messenger.yaml
O Symfony Messenger é utilizado para comunicação síncrona e assíncrona em aplicações Symfony. Ele suporta uma variedade de transportesou fontes de verdade da camada de transporte. No nosso exemplo, utilizamos a extensão AMQP que suporta o sistema de filas de eventos RabbitMQ.
A configuração acima define um novo transporte chamado mensagens_externas, que faz referência ao MESSENGER_TRANSPORT_DSN e define a escuta direta no mensagens no barramento de mensagens. Nesta altura, edite também o app1/.env e adicionar o endereço de transporte adequado.
"`env
(...)
MESSENGERTRANSPORTDSN=amqp://user:password@rabbitmq:5672//messages
(...)
"
Depois de preparar a estrutura da aplicação e de configurar as bibliotecas, é altura de implementar a lógica comercial. Sabemos que a nossa aplicação deve responder à atribuição de um trabalho a um trabalhador. Também sabemos que a atribuição de um trabalho noapp2system altera o estado do trabalho. Portanto, vamos criar um modelo que imita a alteração de status e salvá-lo no caminhoapp1/Message/StatusUpdate.php`:

{
public function __construct(protected string $status){}
public function getStatus(): string
{
return $this->status;
}
}
Código fonte disponível diretamente: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Message/StatusUpdate.php
Ainda precisamos de uma classe que implemente a lógica comercial quando o nosso microserviço receber o evento acima da fila. Então, vamos criar uma classe Manipulador de mensagens no app1/Handler/StatusUpdateHandler.php caminho:

use PsrLogLoggerInterface;
utilizar SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
class StatusUpdateHandler
{
public function __construct(
protected LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->warning('APP1: {STATUS_UPDATE} - '.$statusDescription);
// o resto da lógica de negócio, ou seja, enviar o email ao utilizador
// $this->emailService->email()
}
}
Código fonte disponível diretamente: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Handler/StatusUpdateHandler.php
PHP tornam as coisas muito mais fáceis e significam que, neste caso específico, não temos de nos preocupar com a ligação automática ou a declaração de serviço. O nosso microsserviço para tratar eventos de domínio está pronto, é altura de passar à segunda aplicação.
Vamos dar uma vista de olhos no app2 e o segundo diretório Aplicação Symfony. A nossa ideia é enviar uma mensagem para a fila quando um trabalhador recebe uma tarefa no sistema. Então, vamos fazer uma configuração rápida do AMQP e fazer com que nosso segundo microsserviço comece a publicar StatusUpdate eventos para o barramento de mensagens.
A instalação das bibliotecas é exatamente igual à da primeira aplicação.
cd ..
cd app2/
symfony composer req amqp ampq-messenger monolog
Vamos certificar-nos de que o app2/.env contém uma entrada DSN válida para o RabbitMQ:
(...)
MESSENGER_TRANSPORT_DSN=amqp://user:password@rabbitmq:5672//messages
(...)
Tudo o que resta é configurar o Symfony Messenger na pasta app2/config/packages/messenger.yaml file:
quadro:
messenger:
# Descomente isto (e o transporte falhado abaixo) para enviar mensagens falhadas para este transporte para tratamento posterior.
# failure_transport: failed
transportes:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
# Encaminhar as mensagens para os transportes
'AppMessageStatusUpdate': assíncrono
Como se pode ver, desta vez a definição de transporte aponta diretamente para assíncrono e define o encaminhamento sob a forma de envio do nosso StatusUpdate mensagem para o DSN configurado. Esta é a única área de configuração, faltando apenas criar a camada lógica e de implementação da fila AMQP. Para isso, vamos criar o gêmeo StatusUpdateHandler e StatusUpdate aulas em app2.

use PsrLogLoggerInterface;
utilizar SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
class StatusUpdateHandler
{
public function __construct(
private readonly LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->warning('APP2: {STATUS_UPDATE} - '.$statusDescription);
## lógica comercial, ou seja, envio de notificação interna ou colocação em fila de espera de outros sistemas
}
}

{
public function __construct(protected string $status){}
public function getStatus(): string
{
return $this->status;
}
}
Código fonte disponível diretamente: /thecodest-co/microservices-in-symfony/blob/main/app2/src/Message/StatusUpdate.php
Por fim, basta criar uma forma de enviar uma mensagem para o barramento de mensagens. Vamos criar um simples Comando Symfony para isso:

use SymfonyComponentConsoleAttributeAsCommand;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;;
use SymfonyComponentMessengerMessageBusInterface;
[AsCommand(
nome: "app:send"
)]
class SendStatusCommand extends Command
{
public function construct(private readonly MessageBusInterface $messageBus, string $name = null)
{
parent::construct($name);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$status = "Trabalhador X atribuído a Y";
$this->messageBus->dispatch(
mensagem: new StatusUpdate($status)
);
return Command::SUCCESS;
}
}
Agradecimentos a Injeção de dependência podemos utilizar uma instância de Interface de barramento de mensagens no nosso Command e enviar um StatusUpdate mensagem através do despacho() para a nossa fila. Além disso, também utilizamos aqui os atributos PHP.
É isso - tudo o que resta é executar nosso ambiente Docker Compose e ver como nossos aplicativos se comportam.
Com o Docker Compose, os contentores com as nossas duas aplicações serão construídos e executados como instâncias separadas, a única camada de middleware será o rabbitmq e a nossa implementação do barramento de mensagens.
A partir do diretório raiz do projeto, vamos executar os seguintes comandos:
cd ../ # certifique-se de que está no diretório principal
docker-compose up --build -d
Esse comando pode levar algum tempo, pois ele constrói dois containers separados com o PHP 8.1 + AMQP e puxa a imagem do RabbitMQ. Seja paciente. Depois que as imagens são construídas, você pode disparar nosso comando a partir de app2 e enviar algumas mensagens para uma fila.
docker exec -it app2 php bin/console app:send
Podes fazê-lo tantas vezes quantas as que conseguires. Desde que não haja consumidor as suas mensagens não serão processadas. Assim que abrir o app1 e consumir todas as mensagens que lhe forem apresentadas no ecrã.
docker exec -it app1 php bin/console messenger:consume -vv external_messages

A versão completa código fonte juntamente com o README pode ser encontrado no nosso repositório público The Codest Github
O Symfony, com as suas bibliotecas e ferramentas, permite uma abordagem rápida e eficiente ao desenvolvimento de aplicações web. Com alguns comandos e algumas linhas de código, é possível criar um sistema de comunicação moderno entre aplicações. O Symfony, assim como o PHPé ideal para desenvolvimento de aplicações web e graças ao seu ecossistema e facilidade de implementação, este ecossistema atinge alguns dos melhores indicadores de tempo de colocação no mercado.
No entanto, rápido nem sempre significa bom - no exemplo acima, apresentámos a forma mais simples e rápida de comunicação. O que os mais curiosos certamente notarão é a falta de desconexão dos eventos de domínio fora da camada de aplicação - na versão atual, eles são duplicados e não há suporte completo para Envelopeentre outros, não existe Selos. Para estes e outros, convido-o a ler a Parte II, onde abordaremos o tema da unificação da estrutura de domínio das aplicações Symfony num ambiente de microsserviços, e discutiremos o segundo método popular de comunicação de microsserviços - desta vez síncrono, baseado na API REST.
