PHP 8.2: Hva er nytt?
Den nye versjonen av PHP er rett rundt hjørnet. Hvilke nye implementeringer bør du vite om? Sjekk denne artikkelen for å finne ut av det!
Les den første delen av vår PHP-serie om mikrotjenestekommunikasjon i Symfony-rammeverket og den mest populære måten - AMQP-kommunikasjon ved hjelp av RabbitMQ.
Moderne applikasjonsarkitektur har tvunget utviklere til å endre måten å tenke på når det gjelder kommunikasjonen mellom ulike komponenter i IT-systemer. En gang var saken enklere - de fleste systemer ble laget som monolittiske strukturer som var forbundet med hverandre gjennom et nettverk av forretningslogiske forbindelser. Å opprettholde slike avhengigheter i en PHP prosjekt var en stor utfordring for PHP-utviklereog den økende populariteten til SaaS-løsninger og den enorme økningen i populariteten til sky tjenester førte til at vi i dag hører mer og mer om mikrotjenester og applikasjonsmodularitet.
Hvordan kan vi få dem til å utveksle informasjon med hverandre ved å lage uavhengige mikrotjenester?
Denne artikkelen er den første i en serie innlegg om mikrotjenestekommunikasjon i Symfony rammeverket, og den dekker den mest populære måten - AMQP-kommunikasjon ved hjelp av RabbitMQ.
Å opprette to uavhengige applikasjoner og oppnå kommunikasjon mellom dem kun ved hjelp av Message Bus.
Vi har to, imaginære, uavhengige applikasjoner:
* app1
: som sender e-post- og SMS-varsler til de ansatte
* app2
: som lar deg administrere de ansattes arbeid og tildele dem oppgaver.
Vi ønsker å skape et moderne og enkelt system der tildelingen av arbeid til en medarbeider i app2
vil sende et varsel til kunden ved hjelp av app1
. Til tross for at det ser slik ut, er dette veldig enkelt!
I denne artikkelen bruker vi den nyeste versjonen av Symfony (versjon 6.1 i skrivende stund) og den nyeste versjonen av PHP (8.1). Med noen få, enkle trinn kan vi opprette et fungerende lokalt Docker-miljø med to mikrotjenester. Alt du trenger er:
* en fungerende datamaskin,
* installert Docker + Docker Compose-miljø
* og en lokalt konfigurert Symfony CLI og litt fritid.
Vi vil bruke Dockers egenskaper som et verktøy for applikasjonsvirtualisering og containerisering. La oss starte med å lage et katalogtre, et rammeverk for to Symfony-applikasjonerog beskrive infrastrukturen i miljøene våre ved hjelp av docker-compose.yml
fil.
cd ~
mkdir microservices-in-symfony
cd microservices-in-symfony
symfony ny app1
symfony ny app2
touch docker-compose.yml
Vi har opprettet to kataloger for to separate Symfony-applikasjoner og opprettet en tom docker-compose.yml
filen for å starte miljøet vårt.
La oss legge til følgende seksjoner i docker-compose.yml
file:
versjon: '3.8'
tjenester
app1:
containername: app1
build: app1/.
restart: on-failure
envfile: app1/.env
environment:
APPNAME: app1
tty: true
stdinopen: true
app2
containername: app2
build: app2/.
restart: on-failure
envfile: app2/.env
environment:
APPNAME: app2
tty: true
stdinopen: true
rabbitmq:
containername: rabbitmq
image: rabbitmq:management
ports:
- 15672:15672
- 5672:5672
environment:
- RABBITMQDEFAULTUSER=bruker
- RABBITMQDEFAULT_PASS=passord
Kilde kode tilgjengelig direkte: thecodest-co/microservices-in-symfony/blob/main/docker-compose.yml
Men vent, hva skjedde her? For de som ikke er kjent med Docker, kan konfigurasjonsfilen ovenfor virke gåtefull, men formålet med den er veldig enkelt. Ved hjelp av Docker Compose bygger vi tre "tjenester":
For riktig drift trenger vi fortsatt Dockerfil
filer som er kilden til å bygge bildene. Så la oss lage dem:
touch app1/Dockerfile
touch app2/Dockerfile
Begge filene har nøyaktig samme struktur og ser ut 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-utvidelser amqp
KJØ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/*
Kjør docker-php-ext-install zip;
CMD bash -c "cd /app && composer install && php -a"
Kildekoden er tilgjengelig direkte: /thecodest-co/mikrotjenester-i-symfony/blob/main/app1/Dockerfile
Filen ovenfor brukes av Docker Compose til å bygge en container fra et PHP 8.1-bilde med Composer og AMQP-utvidelsen installert. I tillegg kjører den PHP intepreter i append-modus for å holde beholderen i gang i bakgrunnen.
Katalog- og filtreet ditt skal nå se ut som følger:
.
├─── app1
│ └── Dockerfile
| (...) # Symfony-appstruktur
├── app2
│ └── Dockerfile
| (...) # Symfony-appstruktur
└── docker-compose.yml
La oss begynne med app1
katalogen og den første applikasjonen.
I vårt eksempel er det en applikasjon som lytter til og bruker meldinger fra køen som sendes av app2
som beskrevet i kravene:
tildele en jobb til en arbeider i
app2
vil sende et varsel til klienten
La oss begynne med å legge til de nødvendige bibliotekene. AMQP støttes naturlig for symfony/messenger
forlengelse. Vi vil i tillegg installere monolog/monolog
for å holde oversikt over systemlogger slik at det blir enklere å analysere programatferd.
cd app1/
symfony composer req amqp ampq-messenger monolog
Etter installasjonen ble det lagt til en ekstra fil under config/packages/messenger.yaml
. Det er en konfigurasjonsfil for Symfony Messenger-komponenten, og vi trenger ikke dens full konfigurasjon.
Erstatt den med YAML-filen nedenfor:
rammeverk:
messenger:
# Fjern kommentarene for denne (og den mislykkede transporten nedenfor) for å sende mislykkede meldinger til denne transporten for senere håndtering.
# failure_transport: mislyktes
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
køer
messages:
binding_keys: [from_external]
Kildekoden er tilgjengelig direkte: thecodest-co/microservices-in-symfony/blob/main/app1/config/packages/messenger.yaml
Symfony Messenger brukes til synkron og asynkron kommunikasjon i Symfony-applikasjoner. Den støtter en rekke forskjellige transportereller sannhetskilder i transportlaget. I vårt eksempel bruker vi AMQP-utvidelsen som støtter hendelseskø-systemet RabbitMQ.
Konfigurasjonen ovenfor definerer en ny transport med navnet eksterne_meldinger
, som refererer til MESSENGER_TRANSPORT_DSN
miljøvariabel og definerer direkte lytting på meldinger
kanalen i Message Bus. På dette tidspunktet redigerer du også app1/.env
filen og legge til riktig 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){}
public function getStatus(): string
{
return $this->status;
}
}
Kildekoden er tilgjengelig direkte: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Message/StatusUpdate.php
Vi trenger fortsatt en klasse som implementerer forretningslogikken når mikrotjenesten vår mottar ovennevnte hendelse fra køen. Så la oss opprette en Meldingsbehandler i app1/Handler/StatusUpdateHandler.php
sti:
use PsrLogLoggerInterface;
use SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
class StatusUpdateHandler
{
offentlig funksjon __construct(
protected LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->warning('APP1: {STATUS_UPDATE} - '.$statusDescription);
// resten av forretningslogikken, dvs. sende e-post til brukeren
// $this->emailService->email()
}
}
Kildekoden er tilgjengelig direkte: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Handler/StatusUpdateHandler.php
PHP attributtene gjør ting mye enklere og betyr at vi i dette tilfellet ikke trenger å bekymre oss for autokobling eller tjenesteerklæring. Mikrotjenesten vår for håndtering av domenehendelser er klar, og det er på tide å gå i gang med den andre applikasjonen.
Vi vil ta en titt på app2
katalogen og den andre Symfony-applikasjon. Tanken vår er å sende en melding til køen når en arbeider får tildelt en oppgave i systemet. Så la oss gjøre en rask konfigurasjon av AMQP og få vår andre mikrotjeneste til å begynne å publisere StatusOppdatering
hendelser til meldingsbussen.
Installasjonen av bibliotekene foregår på nøyaktig samme måte som for den første applikasjonen.
cd ...
cd app2/
symfony composer req amqp ampq-messenger monolog
La oss sørge for at app2/.env
filen inneholder en gyldig DSN-oppføring for RabbitMQ:
(...)
MESSENGER_TRANSPORT_DSN=amqp://user:password@rabbitmq:5672/%2f/messages
(...)
Alt som gjenstår er å konfigurere Symfony Messenger i app2/config/packages/messenger.yaml
file:
rammeverk:
messenger:
# Fjern kommentarene for denne (og den mislykkede transporten nedenfor) for å sende mislykkede meldinger til denne transporten for senere håndtering.
# failure_transport: mislyktes
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
# Rute meldingene dine til transport
'AppMessageStatusUpdate': async
Som du kan se, peker transportdefinisjonen denne gangen direkte til asynkron
og definerer ruting i form av å sende vår StatusOppdatering
meldingen til det konfigurerte DSN-et. Dette er det eneste konfigurasjonsområdet, alt som gjenstår er å lage logikken og implementasjonslaget for AMQP-køen. For dette vil vi opprette tvillingen StatusUpdateHandler
og StatusOppdatering
klasser i app2
.
use PsrLogLoggerInterface;
use SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
class StatusUpdateHandler
{
offentlig funksjon __construct(
private readonly LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->warning('APP2: {STATUS_UPDATE} - '.$statusDescription);
## forretningslogikk, f.eks. sende intern varsling eller sette andre systemer i kø
}
}
{
public function __construct(protected string $status){}
public function getStatus(): string
{
return $this->status;
}
}
Kildekoden er tilgjengelig direkte: /thecodest-co/microservices-in-symfony/blob/main/app2/src/Message/StatusUpdate.php
Til slutt gjenstår det bare å lage en måte å sende en melding til meldingsbussen på. Vi vil lage en enkel Symfony-kommando for dette:
bruk SymfonyComponentConsoleAttributeAsCommand;
use SymfonyComponentConsoleConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface; use SymfonyComponentMessenger
use SymfonyComponentMessengerMessageBusInterface;
[AsCommand(
navn: "app:send"
)]
class SendStatusCommand utvides til Command
{
public function construct(private readonly MessageBusInterface $messageBus, string $name = null)
{
parent::construct($name);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$status = "Arbeider X tilordnet Y";
$this->messageBus->dispatch(
message: new StatusUpdate($status)
);
return Command::SUCCESS;
}
}
Takk til Avhengighetsinjeksjon kan vi bruke en forekomst av MessageBusInterface
i vår kommando og sende en StatusOppdatering
melding via utsendelse()
metoden til køen vår. I tillegg bruker vi også PHP-attributter her.
Det er alt som gjenstår, nemlig å kjøre Docker Compose-miljøet og se hvordan applikasjonene våre oppfører seg.
Med Docker Compose vil containerne med de to applikasjonene våre bli bygget og kjørt som separate instanser, og det eneste mellomvarelaget vil være rabbitmq
container og vår Message Bus-implementering.
Fra rotkatalogen til prosjektet kjører vi følgende kommandoer:
cd ../ # sørg for at du er i hovedkatalogen
docker-compose up --build -d
Denne kommandoen kan ta litt tid, ettersom den bygger to separate containere med PHP 8.1 + AMQP og henter RabbitMQ-image. Vær tålmodig. Etter at bildene er bygget, kan du avfyre kommandoen vår fra app2
og sende noen meldinger i en kø.
docker exec -it app2 php bin/console app:send
Du kan gjøre det så mange ganger du kan. Så lenge det ikke er noen forbruker vil ikke meldingene dine bli behandlet. Så snart du starter opp app1
og konsumere alle meldingene de viser på skjermen.
docker exec -it app1 php bin/console messenger:consume -vv external_messages
Den komplette kildekode sammen med README finnes i vårt offentlige arkiv The Codest Github
Symfony med sine biblioteker og verktøy gir en rask og effektiv tilnærming til utvikling av moderne webapplikasjoner. Med noen få kommandoer og noen få kodelinjer kan vi lage et moderne kommunikasjonssystem mellom applikasjoner. Symfony, i likhet med PHP, er ideell for utvikling av webapplikasjoner og takket være økosystemet og den enkle implementeringen oppnår dette økosystemet noen av de beste indikatorene for time-to-market.
Rask betyr imidlertid ikke alltid bra - i eksemplet ovenfor presenterte vi den enkleste og raskeste måten å kommunisere på. De mer nysgjerrige vil sikkert legge merke til at det mangler frakobling av domenehendelser utenfor applikasjonslaget - i den nåværende versjonen er de duplisert, og det er ingen full støtte for Konvolutt
Blant annet er det ingen Frimerker
. For disse og andre vil jeg invitere deg til å lese del II, der vi tar for oss temaet om å forene domenestrukturen til Symfony-applikasjoner i et mikrotjenestemiljø, og diskuterer den andre populære kommunikasjonsmetoden for mikrotjenester - denne gangen synkron, basert på REST API.