PHP 8.2: cosa c'è di nuovo?
La nuova versione di PHP è alle porte. Quali sono le nuove implementazioni da conoscere? Consultate questo articolo per scoprirlo!
Leggete la prima parte della nostra serie PHP dedicata alla comunicazione dei microservizi nel framework Symfony e al modo più popolare: la comunicazione AMQP con RabbitMQ.
La moderna architettura delle applicazioni ha costretto gli sviluppatori a cambiare il modo di pensare alla comunicazione tra i diversi componenti dei sistemi IT. Un tempo la questione era più semplice: la maggior parte dei sistemi veniva creata come strutture monolitiche collegate tra loro da una rete di connessioni di logica aziendale. Il mantenimento di tali dipendenze in un PHP progetto è stata una grande sfida per Sviluppatori PHPe la crescente popolarità delle soluzioni SaaS e l'enorme aumento di popolarità delle soluzioni SaaS. nuvola servizi ha fatto sì che oggi si senta parlare sempre più spesso di microservizi e modularità delle applicazioni.
In che modo, creando microservizi indipendenti, possiamo far sì che si scambino informazioni tra loro?
Questo articolo è il primo di una serie di post su comunicazione dei microservizi in Symfony e copre il modo più diffuso: la comunicazione AMQP con RabbitMQ.
Creare due applicazioni indipendenti e realizzare la comunicazione tra di esse utilizzando solo Message Bus.
Abbiamo due applicazioni immaginarie e indipendenti:
* app1
che invia notifiche via e-mail e SMS ai dipendenti.
* app2
che consente di gestire il lavoro dei dipendenti e di assegnare loro dei compiti.
Vogliamo creare un sistema moderno e semplice che consenta di assegnare il lavoro a un dipendente in un'unica soluzione. app2
invierà una notifica al cliente utilizzando app1
. Nonostante le apparenze, è molto semplice!
Per lo scopo di questo articolo, utilizzeremo l'ultima versione di Symfony (la 6.1 al momento della scrittura) e l'ultima versione di PHP (8.1). In pochi e semplici passi creeremo un ambiente Docker locale funzionante con due microservizi. Tutto ciò che serve è:
* un computer funzionante,
* installato Docker + Ambiente Docker Compose
* e una configurazione locale Symfony CLI e un po' di tempo libero.
Utilizzeremo le capacità di Docker come strumento di virtualizzazione e containerizzazione delle applicazioni. Cominciamo con la creazione di un albero di directory, una struttura per due Applicazioni Symfonye descrivere l'infrastruttura dei nostri ambienti utilizzando l'opzione docker-compose.yml
file.
cd ~
mkdir microservizi-in-symfony
cd microservizi-in-symfony
symfony nuova app1
symfony nuova app2
toccare docker-compose.yml
Abbiamo creato due cartelle per due applicazioni Symfony separate e abbiamo creato una cartella vuota docker-compose.yml
per lanciare il nostro ambiente.
Aggiungiamo le seguenti sezioni al file docker-compose.yml
file:
versione: '3.8'
servizi:
app1:
containername: app1
build: app1/.
restart: on-failure
envfile: app1/.env
ambiente:
APPNAME: app1
tty: true
stdinopen: true
app2:
containername: app2
build: app2/.
restart: on-failure
envfile: app2/.env
ambiente:
APPNAME: app2
tty: true
stdinopen: true
rabbitmq:
containername: rabbitmq
immagine: rabbitmq:management
porte:
- 15672:15672
- 5672:5672
ambiente:
- RABBITMQDEFAULTUSER=utente
- RABBITMQDEFAULT_PASS=password
Fonte codice disponibili direttamente: thecodest-co/microservizi-in-symfony/blob/main/docker-compose.yml
Ma aspettate, cosa è successo qui? Per chi non ha familiarità con Docker, il file di configurazione di cui sopra può sembrare enigmatico, tuttavia il suo scopo è molto semplice. Utilizzando Docker Compose stiamo costruendo tre "servizi":
Per un corretto funzionamento, abbiamo ancora bisogno di Profilo Docker
che sono il sorgente per costruire le immagini. Quindi creiamoli:
toccare app1/Dockerfile
toccare app2/Dockerfile
Entrambi i file hanno esattamente la stessa struttura e si presentano come segue:
DA php:8.1
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
COPIA . /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 &&
installa-php-estensioni amqp
ESEGUIRE 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/*
ESEGUIRE docker-php-ext-install zip;
CMD bash -c "cd /app && composer install && php -a"
Il codice sorgente è disponibile direttamente: /thecodest-co/microservizi-in-symfony/blob/main/app1/Dockerfile
Il file di cui sopra viene usato da Docker Compose per costruire un contenitore da un'immagine PHP 8.1 con Composer e l'estensione AMQP installata. Inoltre, esegue l'inteprete PHP in modalità append per mantenere il contenitore in esecuzione in background.
La struttura delle directory e dei file dovrebbe ora apparire come segue:
.
├── app1
│ └── Dockerfile
| # Struttura dell'applicazione Symfony
├── app2
│ └─── Dockerfile
| # Struttura dell'applicazione Symfony
└── docker-compose.yml
Iniziamo con il app1
e la prima applicazione.
Nel nostro esempio, si tratta di un'applicazione che ascolta e consuma i messaggi della coda inviati da app2
come descritto nei requisiti:
assegnare un lavoro a un lavoratore in
app2
invierà una notifica al client
Iniziamo aggiungendo le librerie necessarie. AMQP è supportato in modo nativo per il sistema symfony/messenger
estensione. Inoltre, installeremo monologo/monologo
per tenere traccia dei registri di sistema e facilitare l'analisi del comportamento delle applicazioni.
cd app1/
symfony composer req amqp ampq-messenger monolog
Dopo l'installazione, è stato aggiunto un file supplementare sotto la voce config/packages/messenger.yaml
. Si tratta di un file di configurazione per il componente Symfony Messenger e non abbiamo bisogno del suo configurazione completa.
Sostituirlo con il file YAML seguente:
quadro:
messenger:
# Deselezionare questo (e il trasporto failed sotto) per inviare i messaggi falliti a questo trasporto per una successiva gestione.
# trasporto_fallito: fallito
trasporti:
# https://symfony.com/doc/current/messenger.html#transport-configuration
messaggi_esterni:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
opzioni:
auto_setup: false
scambio:
nome: messaggi
tipo: diretto
chiave di instradamento predefinita: from_external
code:
messaggi:
binding_keys: [da_esterno]
Il codice sorgente è disponibile direttamente: thecodest-co/microservices-in-symfony/blob/main/app1/config/packages/messenger.yaml
Symfony Messenger è usato per la comunicazione sincrona e asincrona nelle applicazioni Symfony. Supporta una varietà di trasportio fonti di verità del livello di trasporto. Nel nostro esempio, utilizziamo l'estensione AMQP che supporta il sistema di code di eventi RabbitMQ.
La configurazione precedente definisce un nuovo trasporto chiamato messaggi_esterni
che fa riferimento al file MESSENGER_TRANSPORT_DSN
e definisce l'ascolto diretto sul server messaggi
in Message Bus. A questo punto, modificare anche l'opzione app1/.env
e aggiungere l'indirizzo di trasporto appropriato.
“`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
{
restituisce $this->status;
}
}
Il codice sorgente è disponibile direttamente: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Message/StatusUpdate.php
Abbiamo ancora bisogno di una classe che implementi la logica di business quando il nostro microservizio riceve l'evento di cui sopra dalla coda. Creiamo quindi una classe Gestore dei messaggi nel app1/Handler/StatusUpdateHandler.php
percorso:
utilizzare 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);
// il resto della logica aziendale, cioè l'invio di e-mail all'utente
// $this->emailService->email()
}
}
Il codice sorgente è disponibile direttamente: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Handler/StatusUpdateHandler.php
PHP rende le cose molto più semplici e significa che in questo caso particolare non dobbiamo preoccuparci dell'autowiring o della dichiarazione del servizio. Il nostro microservizio per la gestione degli eventi del dominio è pronto, è ora di passare alla seconda applicazione.
Daremo un'occhiata al app2
e la seconda cartella Applicazione Symfony. La nostra idea è di inviare un messaggio alla coda quando a un lavoratore viene assegnato un compito nel sistema. Quindi, facciamo una rapida configurazione di AMQP e facciamo partire la pubblicazione del nostro secondo microservizio Aggiornamento dello stato
eventi al bus dei messaggi.
L'installazione delle librerie è esattamente la stessa della prima applicazione.
cd ..
cd app2/
symfony composer req amqp ampq-messenger monolog
Assicuriamoci che il app2/.env
contiene una voce DSN valida per RabbitMQ:
(...)
MESSENGER_TRANSPORT_DSN=amqp://user:password@rabbitmq:5672/%2f/messages
(...)
Rimane solo da configurare Symfony Messenger nella cartella app2/config/packages/messenger.yaml
file:
quadro:
messenger:
# Deselezionare questo (e il trasporto failed sotto) per inviare i messaggi falliti a questo trasporto per una successiva gestione.
# trasporto_fallito: fallito
trasporti:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
instradamento:
# Instradare i messaggi ai trasporti
AppMessageStatusUpdate': async
Come si può vedere, questa volta la definizione di trasporto punta direttamente a asincrono
e definisce l'instradamento sotto forma di invio del nostro Aggiornamento dello stato
al DSN configurato. Questa è l'unica area di configurazione, tutto ciò che rimane è creare la logica e il livello di implementazione della coda AMQP. Per questo creeremo il gemello Gestore dell'aggiornamento dello stato
e Aggiornamento dello stato
classi in app2
.
utilizzare 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);
## logica aziendale, ad esempio invio di una notifica interna o messa in coda ad altri sistemi
}
}
{
public function __construct(protected string $status){}
public function getStatus(): string
{
restituisce $this->status;
}
}
Il codice sorgente è disponibile direttamente: /thecodest-co/microservices-in-symfony/blob/main/app2/src/Message/StatusUpdate.php
Infine, non resta che creare un modo per inviare un messaggio al Message Bus. Creeremo un semplice oggetto Comando Symfony per questo:
utilizzare SymfonyComponentConsoleAttributeAsCommand;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
use SymfonyComponentMessengerMessageBusInterface;
[AsCommand(
nome: "app:send"
)]
classe SendStatusCommand estende Command
{
public function construct(private readonly MessageBusInterface $messageBus, string $name = null)
{
parent::construct($name);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$status = "Lavoratore X assegnato a Y";
$this->messageBus->dispatch(
messaggio: nuovo StatusUpdate($status)
);
restituire Command::SUCCESS;
}
}
Grazie a Iniezione di dipendenza possiamo utilizzare un'istanza di Interfaccia MessageBus
nel nostro comando e inviare un messaggio Aggiornamento dello stato
tramite il messaggio dispatch()
alla nostra coda. Inoltre, qui utilizziamo anche gli attributi PHP.
Tutto qui: non resta che eseguire il nostro ambiente Docker Compose e vedere come si comportano le nostre applicazioni.
Con Docker Compose i container con le nostre due applicazioni saranno costruiti ed eseguiti come istanze separate, l'unico livello di middleware sarà il conigliomq
e la nostra implementazione del Bus dei messaggi.
Dalla directory principale del progetto, eseguiamo i seguenti comandi:
cd ../ # assicurarsi di essere nella directory principale
docker-compose up --build -d
Questo comando può richiedere un po' di tempo, poiché costruisce due contenitori separati con PHP 8.1 + AMQP e tira l'immagine di RabbitMQ. Siate pazienti. Dopo che le immagini sono state costruite, si può lanciare il nostro comando da app2
e inviare alcuni messaggi in una coda.
docker exec -it app2 php bin/console app:send
Potete farlo quante volte volete. Finché non c'è consumatore i messaggi non verranno elaborati. Non appena si accende il programma app1
e consumare tutti i messaggi che verranno visualizzati sullo schermo.
docker exec -it app1 php bin/console messenger:consume -vv external_messages
L'opera completa codice sorgente insieme al README è disponibile nel nostro repository pubblico The Codest Github
Symfony, con le sue librerie e i suoi strumenti, permette un approccio veloce ed efficiente allo sviluppo di moderne applicazioni web. Con pochi comandi e poche righe di codice siamo in grado di creare un moderno sistema di comunicazione tra applicazioni. Symfony, come PHPè ideale per sviluppare applicazioni web e grazie al suo ecosistema e alla facilità di implementazione, questo ecosistema raggiunge alcuni dei migliori indicatori di time-to-market.
Tuttavia, veloce non sempre significa buono: nell'esempio precedente abbiamo presentato il modo più semplice e veloce di comunicazione. I più curiosi noteranno sicuramente che manca la disconnessione degli eventi di dominio al di fuori del livello applicativo: nella versione attuale sono duplicati e non c'è un supporto completo per Busta
, tra l'altro non c'è Francobolli
. Per questi e altri argomenti, vi invito a leggere la Parte II, dove tratteremo l'argomento dell'unificazione della struttura del dominio delle applicazioni Symfony in un ambiente di microservizi e discuteremo il secondo metodo di comunicazione dei microservizi, questa volta sincrono, basato sulle API REST.