PHP 8.2 : Quoi de neuf ?
La nouvelle version de PHP arrive à grands pas. Quelles sont les nouvelles implémentations que vous devez connaître ? Consultez cet article pour le savoir !
![The Codest](https://thecodest.co/app/uploads/2024/05/testimony_mug_shot_photo.webp)
Lisez la première partie de notre série PHP consacrée à la communication entre microservices dans le framework Symfony et à la manière la plus populaire - la communication AMQP en utilisant RabbitMQ.
L'architecture moderne des applications a obligé les développeurs à changer leur façon de concevoir la communication entre les différents composants des systèmes informatiques. Autrefois, les choses étaient plus simples : la plupart des systèmes étaient créés sous la forme de structures monolithiques reliées entre elles par un réseau de connexions logiques. Le maintien de ces dépendances dans une PHP projet a été un défi de taille pour Développeurs PHPL'utilisation de la technologie de l'information, la popularité croissante des solutions SaaS et l'augmentation considérable de la popularité de la technologie nuage Les services de la Commission européenne ont fait en sorte qu'aujourd'hui, nous entendons de plus en plus parler de microservices et de modularité des applications.
Comment, en créant des microservices indépendants, faire en sorte qu'ils échangent des informations entre eux ?
Cet article est le premier d'une série de billets sur communication microservices dans Symfony et couvre le moyen le plus populaire - la communication AMQP à l'aide de RabbitMQ.
Créer deux applications indépendantes et assurer la communication entre elles en utilisant uniquement le bus de messages.
Nous avons deux applications imaginaires et indépendantes :
* app1
Le système d'information sur la santé : qui envoie des notifications par courrier électronique et par SMS aux employés
* app2
Le système de gestion des ressources humaines : il vous permet de gérer le travail de vos employés et de leur assigner des tâches.
Nous voulons créer un système moderne et simple qui permette d'attribuer un travail à un employé dans le cadre d'un contrat de travail. app2
enverra une notification au client en utilisant app1
. Malgré les apparences, c'est très simple !
Pour les besoins de cet article, nous utiliserons la dernière version de Symfony (version 6.1 au moment de la rédaction) et la dernière version de PHP (8.1). En quelques étapes très simples, nous allons créer un environnement Docker local fonctionnel avec deux microservices. Tout ce dont vous avez besoin est :
* un ordinateur en état de marche,
* Docker + installé Environnement Docker Compose
* et d'un CLI Symfony et du temps libre.
Nous utiliserons les capacités de Docker en tant qu'outil de virtualisation et de conteneurisation d'applications. Commençons par créer une arborescence de répertoires, un cadre pour deux applications. Applications Symfonyet décrire l'infrastructure de nos environnements à l'aide de l'outil docker-compose.yml
fichier.
cd ~
mkdir microservices-in-symfony
cd microservices-in-symfony
symfony new app1
symfony new app2
touch docker-compose.yml
Nous avons créé deux répertoires pour deux applications Symfony distinctes et créé un répertoire vide docker-compose.yml
pour lancer notre environnement.
Ajoutons les sections suivantes au docker-compose.yml
file:
version : '3.8'
services :
app1 :
nom d'utilisateur : app1
build : app1/.
restart : on-failure
envfile : app1/.env
environnement :
APPNAME : app1
tty : true
stdinopen : true
app2 :
nom d'utilisateur : app2
build : app2/.
restart : on-failure
envfile : app2/.env
environnement :
APPNAME : app2
tty : true
stdinopen : true
rabbitmq :
nom d'utilisateur : rabbitmq
image : rabbitmq:management
ports :
- 15672:15672
- 5672:5672
environnement :
- RABBITMQDEFAULTUSER=utilisateur
- RABBITMQDEFAULT_PASS=mot de passe
Source code disponible directement : thecodest-co/microservices-in-symfony/blob/main/docker-compose.yml
Mais attendez, que s'est-il passé ici ? Pour ceux qui ne connaissent pas Docker, le fichier de configuration ci-dessus peut sembler énigmatique, mais son objectif est très simple. En utilisant Docker Compose, nous construisons trois "services" :
Pour un fonctionnement correct, nous avons encore besoin de Fichier Docker
qui sont la source pour construire les images. Nous allons donc les créer :
touch app1/Dockerfile
touch app2/Dockerfile
Les deux fichiers ont exactement la même structure et se présentent comme suit :
Depuis php:8.1
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
COPY . /app/
WORKDIR /app/
ADD 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
Exécuter 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/*
Exécuter docker-php-ext-install zip ;
CMD bash -c "cd /app && composer install && php -a"
Le code source est disponible directement : /thecodest-co/microservices-in-symfony/blob/main/app1/Dockerfile
Le fichier ci-dessus est utilisé par Docker Compose pour construire un conteneur à partir d'une image PHP 8.1 avec Composer et l'extension AMQP installée. De plus, il exécute l'interpréteur PHP en mode append pour que le conteneur continue à fonctionner en arrière-plan.
Votre arborescence de répertoires et de fichiers devrait maintenant ressembler à ce qui suit :
.
├── app1
│ └── Dockerfile
| (...) # Symfony App Structure
├── app2
│ └── Dockerfile
| (...) # Symfony App Structure
└── docker-compose.yml
Commençons par le app1
et la première application.
Dans notre exemple, il s'agit d'une application qui écoute et consomme les messages de la file d'attente envoyée par app2
comme décrit dans les exigences :
l'attribution d'une tâche à un travailleur dans
app2
enverra une notification au client
Commençons par ajouter les bibliothèques nécessaires. AMQP est supporté nativement pour la bibliothèque symfony/messenger
l'extension. Nous installerons également monologue/monologue
pour garder une trace des journaux système afin de faciliter l'analyse du comportement des applications.
cd app1/
symfony composer req amqp ampq-messenger monolog
Après l'installation, un fichier supplémentaire a été ajouté sous config/packages/messenger.yaml
. Il s'agit d'un fichier de configuration pour le composant Symfony Messenger et nous n'avons pas besoin de son contenu. configuration complète.
Remplacez-le par le fichier YAML ci-dessous :
cadre :
messager :
# Décommentez ceci (et le transport failed ci-dessous) pour envoyer les messages échoués à ce transport en vue d'un traitement ultérieur.
# failure_transport : failed
transports :
# https://symfony.com/doc/current/messenger.html#transport-configuration
external_messages :
dsn : '%env(MESSENGER_TRANSPORT_DSN)%'
options :
auto_setup : false
exchange :
name : messages
type : direct
default_publish_routing_key : from_external
queues :
messages :
binding_keys : [from_external]
Le code source est disponible directement : thecodest-co/microservices-in-symfony/blob/main/app1/config/packages/messenger.yaml
Symfony Messenger est utilisé pour la communication synchrone et asynchrone dans les applications Symfony. Il supporte une variété de transportsou sources de vérité de la couche transport. Dans notre exemple, nous utilisons l'extension AMQP qui prend en charge le système de file d'attente d'événements RabbitMQ.
La configuration ci-dessus définit un nouveau transport nommé messages_externes
qui fait référence à la MESSENGER_TRANSPORT_DSN
et définit l'écoute directe sur le serveur messages
dans le bus de messages. À ce stade, modifiez également le app1/.env
et ajouter l'adresse de transport appropriée.
“`env
(…)
MESSENGERTRANSPORTDSN=amqp://user:password@rabbitmq:5672/%2f/messages
(…)
“
After preparing the application framework and configuring the libraries, it is time to implement the business logic. We know that our application must respond to the assignment of a job to a worker. We also know that assigning a job in theapp2system changes the status of the job. So let's create a model that mimics the status change and save it in theapp1/Message/StatusUpdate.php` path:
{
public function __construct(protected string $status){}
public function getStatus() : string
{
return $this->status ;
}
}
Le code source est disponible directement : /thecodest-co/microservices-in-symfony/blob/main/app1/src/Message/StatusUpdate.php
Nous avons toujours besoin d'une classe qui implémentera la logique métier lorsque notre microservice recevra l'événement ci-dessus de la file d'attente. Créons donc une classe Gestionnaire de messages dans le app1/Handler/StatusUpdateHandler.php
chemin :
use PsrLogLoggerInterface ;
use SymfonyComponentMessengerAttributeAsMessageHandler ;
[AsMessageHandler]
classe StatusUpdateHandler
{
public function __construct(
protected LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate) : void
{
$statusDescription = $statusUpdate->getStatus() ;
$this->logger->warning('APP1 : {STATUS_UPDATE} - '.$statusDescription) ;
// le reste de la logique commerciale, c'est-à-dire l'envoi de l'email à l'utilisateur
// $this->emailService->email()
}
}
Le code source est disponible directement : /thecodest-co/microservices-in-symfony/blob/main/app1/src/Handler/StatusUpdateHandler.php
PHP rendent les choses beaucoup plus faciles et signifient que dans ce cas particulier, nous n'avons pas à nous soucier de l'autowiring ou de la déclaration de service. Notre microservice pour gérer les événements du domaine est prêt, il est temps de passer à la deuxième application.
Nous allons examiner les app2
et le second Application Symfony. Notre idée est d'envoyer un message à la file d'attente lorsqu'un travailleur se voit assigner une tâche dans le système. Nous allons donc configurer rapidement AMQP et faire en sorte que notre deuxième microservice commence à publier des messages Mise à jour de l'état
au bus de messages.
L'installation des bibliothèques est exactement la même que pour la première application.
cd ...
cd app2/
symfony composer req amqp ampq-messenger monolog
Veillons à ce que le app2/.env
contient une entrée DSN valide pour RabbitMQ :
(...)
MESSENGER_TRANSPORT_DSN=amqp://user:password@rabbitmq:5672/%2f/messages
(...)
Il ne reste plus qu'à configurer Symfony Messenger dans le fichier app2/config/packages/messenger.yaml
file:
cadre :
messager :
# Décommentez ceci (et le transport failed ci-dessous) pour envoyer les messages échoués à ce transport en vue d'un traitement ultérieur.
# failure_transport : failed
transports :
# https://symfony.com/doc/current/messenger.html#transport-configuration
async :
dsn : '%env(MESSENGER_TRANSPORT_DSN)%'
routing :
# Routez vos messages vers les transports
AppMessageStatusUpdate' : async
Comme vous pouvez le voir, cette fois-ci, la définition du transport pointe directement vers asynchrone
et définit le routage sous la forme de l'envoi de notre Mise à jour de l'état
vers le DSN configuré. Il ne reste plus qu'à créer la logique et la couche d'implémentation de la file d'attente AMQP. Pour ce faire, nous allons créer le jumeau StatusUpdateHandler
et Mise à jour de l'état
classes en app2
.
use PsrLogLoggerInterface ;
use SymfonyComponentMessengerAttributeAsMessageHandler ;
[AsMessageHandler]
classe StatusUpdateHandler
{
public function __construct(
private readonly LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate) : void
{
$statusDescription = $statusUpdate->getStatus() ;
$this->logger->warning('APP2 : {STATUS_UPDATE} - '.$statusDescription) ;
## logique métier, c'est-à-dire envoi d'une notification interne ou mise en file d'attente pour d'autres systèmes
}
}
{
public function __construct(protected string $status){}
public function getStatus() : string
{
return $this->status ;
}
}
Le code source est disponible directement : /thecodest-co/microservices-in-symfony/blob/main/app2/src/Message/StatusUpdate.php
Enfin, il ne reste plus qu'à créer un moyen d'envoyer un message au bus de messages. Nous allons créer un simple Commande Symfony pour cela :
use SymfonyComponentConsoleAttributeAsCommand ;
use SymfonyComponentConsoleCommandCommand ;
use SymfonyComponentConsoleInputInputInterface ;
use SymfonyComponentConsoleOutputOutputInterface ;
use SymfonyComponentMessengerMessageBusInterface ;
[AsCommand(
name : "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 = "Worker X assigned to Y" ;
$this->messageBus->dispatch(
message : new StatusUpdate($status)
) ;
return Command::SUCCESS ;
}
}
Merci à Injection de dépendance nous pouvons utiliser une instance de MessageBusInterface
dans notre commande et envoyer un Mise à jour de l'état
par l'intermédiaire du dispatch()
à notre file d'attente. En outre, nous utilisons également les attributs PHP ici.
Voilà, il ne reste plus qu'à lancer notre environnement Docker Compose et à voir comment se comportent nos applications.
Avec Docker Compose, les conteneurs contenant nos deux applications seront construits et exécutés en tant qu'instances séparées. lapinmq
et notre mise en œuvre du bus de messages.
Depuis le répertoire racine du projet, exécutons les commandes suivantes :
cd ../ # assurez-vous d'être dans le répertoire principal
docker-compose up --build -d
Cette commande peut prendre un certain temps, car elle construit deux conteneurs séparés avec PHP 8.1 + AMQP et tire l'image RabbitMQ. Soyez patient. Une fois les images construites, vous pouvez lancer notre commande à partir de app2
et envoyer des messages dans une file d'attente.
docker exec -it app2 php bin/console app:send
Vous pouvez le faire autant de fois que vous le souhaitez. Tant qu'il n'y a pas de consommateur vos messages ne seront pas traités. Dès que vous lancez le app1
et consommer tous les messages qu'ils afficheront sur votre écran.
docker exec -it app1 php bin/console messenger:consume -vv external_messages
L'ensemble code source ainsi que le README peuvent être trouvés dans notre dépôt public The Codest Github
Symfony, avec ses bibliothèques et ses outils, permet une approche rapide et efficace du développement d'applications modernes. applications web. Avec quelques commandes et quelques lignes de code, nous sommes en mesure de créer un système de communication moderne entre les applications. Symfony, comme PHPest idéal pour le développement d'applications web et grâce à son écosystème et à sa facilité de mise en œuvre, cet écosystème atteint certains des meilleurs indicateurs de délai de mise sur le marché.
Cependant, la rapidité n'est pas toujours synonyme de qualité - dans l'exemple ci-dessus, nous avons présenté le moyen de communication le plus simple et le plus rapide. Les plus curieux remarqueront certainement qu'il n'y a pas de déconnexion des événements de domaine en dehors de la couche d'application - dans la version actuelle, ils sont dupliqués, et il n'y a pas de support complet pour les événements de domaine. Enveloppe
Entre autres, il n'y a pas de Timbres
. Pour ceux-là et les autres, je vous invite à lire la partie II, où nous aborderons le sujet de l'unification de la structure de domaine des applications Symfony dans un environnement microservices, et discuterons de la deuxième méthode de communication microservices populaire - cette fois-ci synchrone, basée sur l'API REST.