PHP 8.2: Vad är nytt?
Den nya versionen av PHP är precis runt hörnet. Vilka är de nya implementeringar som du bör känna till? Kolla in den här artikeln för att ta reda på det!
![Codest](https://thecodest.co/app/uploads/2024/05/testimony_mug_shot_photo.webp)
Läs den första delen av vår PHP-serie som handlar om kommunikation mellan mikrotjänster i Symfony-ramverket och det mest populära sättet - AMQP-kommunikation med RabbitMQ.
Modern applikationsarkitektur har tvingat utvecklare att ändra sitt sätt att tänka kring kommunikationen mellan olika komponenter i IT-system. En gång i tiden var det enklare - de flesta system skapades som monolitiska strukturer som var kopplade till varandra genom ett nätverk av affärslogiska kopplingar. Att underhålla sådana beroenden i en PHP projekt var en stor utmaning för PHP utvecklareoch den växande populariteten för SaaS-lösningar och den enorma ökningen av populariteten för moln att vi idag hör mer och mer om mikrotjänster och applikationsmodularitet.
Hur kan vi genom att skapa oberoende mikrotjänster få dem att utbyta information med varandra?
Denna artikel är den första i en serie inlägg om kommunikation med mikrotjänster i Symfony och den täcker det mest populära sättet - AMQP-kommunikation med hjälp av RabbitMQ.
Att skapa två oberoende applikationer och uppnå kommunikation mellan dem med hjälp av endast Message Bus.
Vi har två, imaginära, oberoende applikationer:
* app1
: som skickar e-post- och SMS-meddelanden till anställda
* app2
: som gör att du kan hantera medarbetarnas arbete och tilldela dem uppgifter.
Vi vill skapa ett modernt och enkelt system där tilldelningen av arbete till en anställd i app2
kommer att skicka ett meddelande till kunden med hjälp av app1
. Trots att det ser ut så är detta mycket enkelt!
I den här artikeln använder vi den senaste versionen av Symfony (version 6.1 i skrivande stund) och den senaste versionen av PHP (8.1). I några mycket enkla steg skapar vi en fungerande lokal Docker-miljö med två mikrotjänster. Allt du behöver är:
* en fungerande dator,
* installerade Docker + Docker Compose-miljö
* och en lokalt konfigurerad Symfony CLI och lite fritid.
Vi kommer att använda Dockers kapacitet som ett verktyg för virtualisering av applikationer och containerisering. Låt oss börja med att skapa ett katalogträd, ett ramverk för två Symfony-applikationeroch beskriva infrastrukturen i våra miljöer med hjälp av docker-compose.yml
fil.
cd ~
mkdir microservices-in-symfony
cd mikrotjänster-i-symfony
symfony ny app1
symfony ny app2
tryck på docker-compose.yml
Vi har skapat två kataloger för två separata Symfony-applikationer och skapat en tom docker-compose.yml
filen för att starta vår miljö.
Låt oss lägga till följande avsnitt i docker-compose.yml
file:
version: '3.8'
tjänster:
app1:
containername: app1
bygga: app1/.
restart: på fel
envfile: app1/.env
miljö:
APPNAME: app1
tty: sant
stdinopen: true
app2:
containername: app2
bygga: app2/.
omstart: vid misslyckande
envfile: app2/.env
miljö: app2/.env
APPNAME: app2
tty: sant
stdinopen: true
rabbitmq:
containername: rabbitmq
bild: rabbitmq:management
portar:
- 15672:15672
- 5672:5672
environment (miljö):
- RABBITMQDEFAULTUSER=användare
- RABBITMQDEFAULT_PASS=lösenord
Källa kod direkt tillgängliga: thecodest-co/microservices-in-symfony/blob/main/docker-compose.yml
Men vänta, vad hände här? För den som inte är bekant med Docker kan konfigurationsfilen ovan verka gåtfull, men dess syfte är mycket enkelt. Med hjälp av Docker Compose bygger vi tre "tjänster":
För korrekt drift behöver vi fortfarande Dockerfil
filer som är källan för att bygga bilderna. Så låt oss skapa dem:
touch app1/Dockerfile
tryck på app2/Dockerfile
Båda filerna har exakt samma struktur och ser ut på följande sätt:
FRÅN php:8.1
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
KOPIERA . /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 &&
installera-php-utvidgningar amqp
KÖR apt-get uppdatering
&& apt-get install -y libzip-dev wget --no-install-recommends
&& apt-get rensa
&& 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"
Källkod tillgänglig direkt: /thecodest-co/microservices-in-symfony/blob/main/app1/Dockerfile
Ovanstående fil används av Docker Compose för att bygga en container från en PHP 8.1-avbildning med Composer och AMQP-tillägget installerat. Dessutom kör den PHP intepreter i append-läge för att hålla behållaren igång i bakgrunden.
Ditt katalog- och filträd bör nu se ut på följande sätt:
.
app1
│ └─── Dockerfile
| (...) # Symfony App-struktur
├── app2
│ └─── Dockerfile
| (...) # Symfony App-struktur
└── docker-compose.yml
Låt oss börja med app1
och den första applikationen.
I vårt exempel är det en applikation som lyssnar på och konsumerar meddelanden från kön som skickas av app2
enligt beskrivningen i kraven:
tilldela ett jobb till en arbetare i
app2
kommer att skicka ett meddelande till klienten
Låt oss börja med att lägga till de nödvändiga biblioteken. AMQP har inbyggt stöd för symfony/messenger
förlängning. Vi kommer dessutom att installera monolog/monolog
för att hålla reda på systemloggar för enklare analys av programbeteende.
cd app1/
symfony composer req amqp ampq-messenger monolog
Efter installationen lades en ytterligare fil till under config/packages/messenger.yaml
. Det är en konfigurationsfil för Symfony Messenger-komponenten och vi behöver inte dess fullständig konfiguration.
Ersätt den med YAML-filen nedan:
ramverk:
messenger:
# Kommentera bort detta (och failed transport nedan) för att skicka misslyckade meddelanden till denna transport för senare hantering.
# fel_transport: misslyckades
transporter:
# https://symfony.com/doc/current/messenger.html#transport-configuration
externa_meddelanden:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
alternativ:
auto_setup: false
exchange:
namn: meddelanden
typ: direkt
default_publish_routing_key: från_extern
köer:
messages:
bindande_nycklar: [från_extern]
Källkod tillgänglig direkt: thecodest-co/microservices-in-symfony/blob/main/app1/config/packages/messenger.yaml
Symfony Messenger används för synkron och asynkron kommunikation i Symfony-applikationer. Den stöder en mängd olika transportereller sanningskällor för transportlagret. I vårt exempel använder vi AMQP-tillägget som stöder händelsekösystemet RabbitMQ.
Ovanstående konfiguration definierar en ny transport med namnet externa_meddelanden
, som hänvisar till BUDBÄRARE_TRANSPORT_DSN
miljövariabel och definierar direkt lyssning på meddelanden
kanal i meddelandebussen. Vid denna punkt redigerar du också app1/.env
filen och lägg till lämplig transportadress.
“`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){}
publik funktion getStatus(): sträng
{
return $this->status;
}
}
Källkod tillgänglig direkt: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Message/StatusUpdate.php
Vi behöver fortfarande en klass som kommer att implementera affärslogiken när vår mikrotjänst får ovanstående händelse från kön. Så låt oss skapa en Meddelandehanterare i app1/Handler/StatusUpdateHandler.php
väg:
använd PsrLogLoggerInterface;
använd SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
klass StatusUpdateHandler
{
offentlig funktion __construct(
protected LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->warning('APP1: {STATUS_UPDATE} - '.$statusDescription);
// resten av affärslogiken, dvs. skicka e-post till användaren
// $this->emailService->email()
}
}
Källkod tillgänglig direkt: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Handler/StatusUpdateHandler.php
PHP attribut gör saker och ting mycket enklare och innebär att vi i det här fallet inte behöver oroa oss för autowiring eller servicedeklaration. Vår mikrotjänst för hantering av domänhändelser är klar, det är dags att gå vidare till den andra applikationen.
Vi kommer att ta en titt på app2
katalog och den andra Symfony-applikation. Vår idé är att skicka ett meddelande till kön när en arbetare tilldelas en uppgift i systemet. Så låt oss göra en snabb konfiguration av AMQP och få vår andra mikrotjänst att börja publicera Statusuppdatering
händelser till meddelandebussen.
Installationen av biblioteken går till på exakt samma sätt som för den första applikationen.
cd ...
cd app2/
symfony composer req amqp ampq-messenger monolog
Låt oss se till att app2/.env
filen innehåller en giltig DSN-post för RabbitMQ:
(...)
MESSENGER_TRANSPORT_DSN=amqp://user:password@rabbitmq:5672/%2f/messages
(...)
Allt som återstår är att konfigurera Symfony Messenger i app2/config/packages/messenger.yaml
file:
ramverk:
messenger:
# Kommentera bort detta (och failed transport nedan) för att skicka misslyckade meddelanden till denna transport för senare hantering.
# fel_transport: misslyckades
transporter:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
# Routa dina meddelanden till transporterna
'AppMessageStatusUpdate': async
Som du kan se pekar transportdefinitionen den här gången direkt till asynkron
och definierar routing i form av att vi skickar våra Statusuppdatering
meddelande till det konfigurerade DSN:et. Detta är det enda konfigurationsområdet, allt som återstår är att skapa logik- och implementeringslagret för AMQP-kön. För detta kommer vi att skapa tvillingen StatusUppdateringsHandler
och Statusuppdatering
klasser i app2
.
använd PsrLogLoggerInterface;
använd SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
klass StatusUpdateHandler
{
offentlig funktion __construct(
private readonly LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->warning('APP2: {STATUS_UPDATE} - '.$statusDescription);
## affärslogik, dvs. skicka intern avisering eller köa till något annat system
}
}
{
public function __construct(protected string $status){}
publik funktion getStatus(): sträng
{
return $this->status;
}
}
Källkod tillgänglig direkt: /thecodest-co/microservices-in-symfony/blob/main/app2/src/Message/StatusUpdate.php
Slutligen återstår det bara att skapa ett sätt att skicka ett meddelande till meddelandebussen. Vi kommer att skapa en enkel Symfony-kommando för detta:
använd SymfonyComponentConsoleAttributeAsCommand;
använd SymfonyComponentConsoleCommandCommand;
använd SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
använd SymfonyComponentMessengerMessageBusInterface;
[AsCommand(
namn: "app:skicka"
)]
klass SendStatusCommand utökar Kommando
{
public function construct(private readonly MessageBusInterface $messageBus, string $name = null)
{
parent::construct($name);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$status = "Arbetare X tilldelad Y";
$this->messageBus->dispatch(
message: new StatusUpdate($status)
);
return Command::SUCCESS;
}
}
Tack till Injektion av beroenden kan vi använda en instans av MessageBus-gränssnitt
i vårt Command och skicka ett Statusuppdatering
meddelande via skicka()
metod till vår kö. Dessutom använder vi också PHP-attribut här.
Det var allt - nu återstår bara att köra vår Docker Compose-miljö och se hur våra applikationer beter sig.
Med Docker Compose kommer containrarna med våra två applikationer att byggas och köras som separata instanser, det enda middleware-lagret kommer att vara rabbitmq
container och vår implementering av Message Bus.
Från projektets rotkatalog kör vi följande kommandon:
cd ../ # se till att du är i huvudkatalogen
docker-compose up --build -d
Det här kommandot kan ta lite tid, eftersom det bygger två separata behållare med PHP 8.1 + AMQP och drar RabbitMQ-bilden. Ha lite tålamod. När avbildningarna har byggts kan du starta vårt kommando från app2
och skicka några meddelanden på en kö.
docker exec -it app2 php bin/console app:send
Du kan göra det hur många gånger som helst. Så länge det inte finns någon konsument kommer dina meddelanden inte att behandlas. Så snart du startar upp app1
och konsumera alla meddelanden som de visar på din skärm.
docker exec -it app1 php bin/console messenger:consume -vv externa_meddelanden
Den kompletta källkod tillsammans med README finns i vårt offentliga arkiv The Codest Github
Symfony med sina bibliotek och verktyg möjliggör ett snabbt och effektivt tillvägagångssätt för att utveckla moderna webbapplikationer. Med några få kommandon och några rader kod kan vi skapa ett modernt kommunikationssystem mellan applikationer. Symfony, precis som PHP, är idealisk för utveckla webbapplikationer och tack vare sitt ekosystem och enkla implementering uppnår detta ekosystem några av de bästa indikatorerna för tid till marknad.
Men snabbt betyder inte alltid bra - i exemplet ovan presenterade vi det enklaste och snabbaste sättet att kommunicera. Vad den mer nyfikne säkert kommer att märka är att det saknas en frånkoppling av domänhändelser utanför applikationslagret - i den nuvarande versionen dupliceras de, och det finns inget fullt stöd för Kuvert
bland annat finns det ingen Frimärken
. För dessa och andra inbjuder jag dig att läsa del II, där vi kommer att täcka ämnet att förena domänstrukturen för Symfony-applikationer i en mikrotjänstmiljö och diskutera den andra populära kommunikationsmetoden för mikrotjänster - den här gången synkron, baserad på REST API.