PHP 8.2: Hvad er nyt?
Den nye version af PHP er lige om hjørnet. Hvad er de nye implementeringer, du bør kende til? Tjek denne artikel for at finde ud af det!
Læs første del af vores PHP-serie om kommunikation mellem mikrotjenester i Symfony-framework og den mest populære måde - AMQP-kommunikation ved hjælp af RabbitMQ.
Moderne applikationsarkitektur har tvunget udviklere til at ændre deres måde at tænke på, når det gælder kommunikationen mellem forskellige komponenter i IT-systemer. Engang var sagen enklere - de fleste systemer blev skabt som monolitiske strukturer, der var forbundet med hinanden via et netværk af forretningslogiske forbindelser. Vedligeholdelse af sådanne afhængigheder i en PHP projekt var en stor udfordring for PHP-udviklereog den voksende popularitet af SaaS-løsninger og den enorme stigning i populariteten af sky Tjenesterne er skyld i, at vi i dag hører mere og mere om mikrotjenester og applikationsmodularitet.
Hvordan kan vi ved at skabe uafhængige mikrotjenester få dem til at udveksle information med hinanden?
Denne artikel er den første i en række indlæg om Kommunikation mellem mikrotjenester i Symfony og den dækker den mest populære måde - AMQP-kommunikation ved hjælp af RabbitMQ.
At skabe to uafhængige applikationer og opnå kommunikation mellem dem ved kun at bruge Message Bus.
Vi har to imaginære, uafhængige applikationer:
* app1
: som sender e-mail- og sms-beskeder til medarbejderne
* app2
: som giver dig mulighed for at styre medarbejdernes arbejde og tildele dem opgaver.
Vi ønsker at skabe et moderne og enkelt system, hvor tildelingen af arbejde til en medarbejder i app2
vil sende en meddelelse til kunden ved hjælp af app1
. På trods af udseendet er det meget enkelt!
I denne artikel bruger vi den nyeste Symfony (version 6.1 i skrivende stund) og den nyeste version af PHP (8.1). Med et par meget enkle trin opretter vi et fungerende lokalt Docker-miljø med to mikrotjenester. Alt, hvad du har brug for, er:
* en fungerende computer,
* installeret Docker + Docker Compose-miljø
* og en lokalt konfigureret Symfony CLI og lidt fritid.
Vi vil bruge Dockers evner som et værktøj til virtualisering og containerisering af applikationer. Lad os starte med at oprette et katalogtræ, en ramme for to Symfony-applikationerog beskrive infrastrukturen i vores miljøer ved hjælp af docker-compose.yml
fil.
cd ~
mkdir microservices-in-symfony
cd microservices-i-symfony
symfony ny app1
symfony ny app2
tryk på docker-compose.yml
Vi har oprettet to mapper til to separate Symfony-applikationer og oprettet en tom docker-compose.yml
fil for at starte vores miljø.
Lad os tilføje følgende afsnit til docker-compose.yml
file:
version: '3.8'
tjenester:
app1:
containername: app1
build: app1/.
restart: on-failure
envfil: app1/.env
environment:
APPNAME: app1
tty: true
stdinopen: true
app2:
containername: app2
build: app2/.
restart: on-failure
envfil: app2/.env
environment:
APPNAME: app2
tty: true
stdinopen: true
rabbitmq:
containername: rabbitmq
image: rabbitmq:management
porte:
- 15672:15672
- 5672:5672
miljø:
- RABBITMQDEFAULTUSER=bruger
- RABBITMQDEFAULT_PASS=password
Kilde Kode tilgængelig direkte: thecodest-co/microservices-in-symfony/blob/main/docker-compose.yml
Men vent, hvad skete der her? For dem, der ikke er fortrolige med Docker, kan ovenstående konfigurationsfil virke gådefuld, men dens formål er meget enkelt. Ved hjælp af Docker Compose bygger vi tre "tjenester":
For at fungere ordentligt har vi stadig brug for Dockerfil
filer, som er kilden til at bygge billederne. Så lad os oprette dem:
touch app1/Dockerfile
tryk på app2/Dockerfile
Begge filer har nøjagtig samme struktur og ser ud som følger:
FRA 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-udvidelser amqp
KØR 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/*
Kør docker-php-ext-install zip;
CMD bash -c "cd /app && composer install && php -a"
Kildekoden er tilgængelig direkte: /thecodest-co/microservices-in-symfony/blob/main/app1/Dockerfile
Ovenstående fil bruges af Docker Compose til at bygge en container fra et PHP 8.1-image med Composer og AMQP-udvidelsen installeret. Derudover kører den PHP intepreter i append-tilstand for at holde containeren kørende i baggrunden.
Dit mappe- og filtræ bør nu se ud som følger:
.
├── app1
│ └── Dockerfile
| (...) # Symfony App-struktur
├── app2
│ └── Dockerfile
| (...) # Symfony App-struktur
└── docker-compose.yml
Lad os starte med app1
og den første applikation.
I vores eksempel er det et program, der lytter og bruger beskeder fra køen sendt af app2
som beskrevet i kravene:
tildele et job til en medarbejder i
app2
vil sende en meddelelse til klienten
Lad os starte med at tilføje de nødvendige biblioteker. AMQP understøttes naturligt af symfony/messenger
udvidelse. Vi vil desuden installere monolog/monolog
til at holde styr på systemlogs for lettere at kunne analysere programadfærd.
cd app1/
symfony composer req amqp ampq-messenger monolog
Efter installationen blev der tilføjet en ekstra fil under config/packages/messenger.yaml
. Det er en konfigurationsfil til Symfony Messenger-komponenten, og vi har ikke brug for den. Fuld konfiguration.
Erstat den med YAML-filen nedenfor:
rammer:
messenger:
# Fjern kommentarerne til denne (og den mislykkede transport nedenfor) for at sende mislykkede beskeder til denne transport til senere håndtering.
# fejl_transport: fejlede
transporter:
# https://symfony.com/doc/current/messenger.html#transport-configuration
eksterne_meddelelser:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
options:
auto_setup: false
udveksling:
navn: beskeder
type: direkte
default_publish_routing_key: from_external
køer:
messages:
binding_keys: [from_external]
Kildekoden er tilgængelig direkte: thecodest-co/microservices-in-symfony/blob/main/app1/config/packages/messenger.yaml
Symfony Messenger bruges til synkron og asynkron kommunikation i Symfony-applikationer. Den understøtter en række transportereller kilder til sandhed i transportlaget. I vores eksempel bruger vi AMQP-udvidelsen, der understøtter RabbitMQ-eventkøsystemet.
Ovenstående konfiguration definerer en ny transport med navnet eksterne_beskeder
som refererer til MESSENGER_TRANSPORT_DSN
miljøvariabel og definerer direkte lytning på Beskeder
kanal i Message Bus. På dette tidspunkt skal du også redigere app1/.env
og tilføj den relevante transportadresse.
“`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){}
offentlig funktion getStatus(): string
{
return $this->status;
}
}
Kildekoden er tilgængelig direkte: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Message/StatusUpdate.php
Vi har stadig brug for en klasse, der implementerer forretningslogikken, når vores mikrotjeneste modtager ovenstående hændelse fra køen. Så lad os oprette en Beskedhåndtering i app1/Handler/StatusUpdateHandler.php
vej:
brug PsrLogLoggerInterface;
brug SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
klasse StatusUpdateHandler
{
offentlig funktion __construct(
protected LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->warning('APP1: {STATUS_UPDATE} - '.$statusDescription);
// resten af forretningslogikken, dvs. at sende e-mail til brugeren
// $this->emailService->email()
}
}
Kildekoden er tilgængelig direkte: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Handler/StatusUpdateHandler.php
PHP attributter gør tingene meget nemmere og betyder, at vi i dette tilfælde ikke behøver at bekymre os om autowiring eller serviceerklæring. Vores mikroservice til håndtering af domænebegivenheder er klar, og det er tid til at gå i gang med den anden applikation.
Vi tager et kig på app2
mappe og den anden Symfony-applikation. Vores idé er at sende en besked til køen, når en medarbejder får tildelt en opgave i systemet. Så lad os lave en hurtig konfiguration af AMQP og få vores anden mikrotjeneste til at begynde at udgive Statusopdatering
begivenheder til beskedbussen.
Installationen af bibliotekerne er nøjagtig den samme som for den første applikation.
cd ...
cd app2/
symfony composer req amqp ampq-messenger monolog
Lad os sørge for, at app2/.env
filen indeholder en gyldig DSN-post for RabbitMQ:
(...)
MESSENGER_TRANSPORT_DSN=amqp://user:password@rabbitmq:5672/%2f/messages
(...)
Det eneste, der er tilbage, er at konfigurere Symfony Messenger i app2/config/packages/messenger.yaml
file:
rammer:
messenger:
# Fjern kommentarerne til denne (og den mislykkede transport nedenfor) for at sende mislykkede beskeder til denne transport til senere håndtering.
# fejl_transport: fejlede
transporter:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
# Send dine beskeder videre til transporterne
'AppMessageStatusUpdate': async
Som du kan se, peger transportdefinitionen denne gang direkte på asynkron
og definerer routing i form af at sende vores Statusopdatering
besked til det konfigurerede DSN. Dette er det eneste konfigurationsområde, og det eneste, der er tilbage, er at skabe AMQP-køens logik- og implementeringslag. Til dette vil vi oprette tvillingen StatusUpdateHandler
og Statusopdatering
klasser i app2
.
brug PsrLogLoggerInterface;
brug SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
klasse StatusUpdateHandler
{
offentlig funktion __construct(
private readonly LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->warning('APP2: {STATUS_UPDATE} - '.$statusDescription);
## forretningslogik, dvs. sende intern notifikation eller sætte andre systemer i kø
}
}
{
public function __construct(protected string $status){}
offentlig funktion getStatus(): string
{
return $this->status;
}
}
Kildekoden er tilgængelig direkte: /thecodest-co/microservices-in-symfony/blob/main/app2/src/Message/StatusUpdate.php
Til sidst mangler vi bare at skabe en måde at sende en besked til beskedbussen på. Vi vil oprette en simpel Symfony-kommando for dette:
brug SymfonyComponentConsoleAttributeAsCommand;
brug SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
brug SymfonyComponentMessengerMessageBusInterface;
[AsCommand(
navn: "app:send"
)]
klassen SendStatusCommand udvider Command
{
public function construct(private readonly MessageBusInterface $messageBus, string $name = null)
{
parent::construct($name);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$status = "Medarbejder X tildelt Y";
$this->messageBus->dispatch(
message: new StatusUpdate($status)
);
return Command::SUCCESS;
}
}
Tak til Afhængighedsindsprøjtning kan vi bruge en forekomst af BeskedBusInterface
i vores kommando og sende en Statusopdatering
meddelelse via afsendelse()
metode til vores kø. Derudover bruger vi også PHP-attributter her.
Det var det - nu er der kun tilbage at køre vores Docker Compose-miljø og se, hvordan vores applikationer opfører sig.
Med Docker Compose vil containerne med vores to applikationer blive bygget og kørt som separate instanser, og det eneste middleware-lag vil være kaninmq
container og vores Message Bus-implementering.
Lad os køre følgende kommandoer fra projektets rodmappe:
cd ../ # sørg for, at du er i hovedmappen
docker-compose up --build -d
Denne kommando kan tage lidt tid, da den bygger to separate containere med PHP 8.1 + AMQP og trækker RabbitMQ-image. Vær tålmodig. Når billederne er bygget, kan du starte vores kommando fra app2
og sende nogle beskeder til en kø.
docker exec -it app2 php bin/console app:send
Du kan gøre det så mange gange, du kan. Så længe der ikke er nogen forbruger vil dine beskeder ikke blive behandlet. Så snart du starter app1
og forbruge alle de beskeder, de viser på din skærm.
docker exec -it app1 php bin/console messenger:consume -vv external_messages
Den komplette Kildekode sammen med README kan findes i vores offentlige repository The Codest Github
Symfony med sine biblioteker og værktøjer giver mulighed for en hurtig og effektiv tilgang til udvikling af moderne webapplikationer. Med nogle få kommandoer og nogle få kodelinjer kan vi skabe et moderne kommunikationssystem mellem applikationer. Symfony, ligesom PHPer ideel til udvikling af webapplikationer og takket være sit økosystem og lette implementering opnår dette økosystem nogle af de bedste time-to-market-indikatorer.
Men hurtigt betyder ikke altid godt - i eksemplet ovenfor præsenterede vi den enkleste og hurtigste måde at kommunikere på. De mere nysgerrige vil helt sikkert bemærke, at der mangler en frakobling af domænebegivenheder uden for applikationslaget - i den nuværende version er de duplikeret, og der er ikke fuld understøttelse af Konvolut
Der er blandt andet ingen Frimærker
. For dem og andre inviterer jeg dig til at læse del II, hvor vi dækker emnet om at forene domænestrukturen i Symfony-applikationer i et mikrotjenestemiljø og diskuterer den anden populære mikrotjenestekommunikationsmetode - denne gang synkron, baseret på REST API.