Symfony: Mikroservisų komunikacija I dalis
Perskaitykite pirmąją PHP serijos dalį, skirtą mikroservisų komunikacijai Symfony sistemoje ir populiariausiam būdui - AMQP komunikacijai naudojant RabbitMQ.
Šiuolaikinė taikomųjų programų architektūra privertė kūrėjus pakeisti mąstymą apie skirtingų IT sistemų komponentų ryšį. Kadaise viskas buvo paprasčiau - dauguma sistemų buvo kuriamos kaip monolitinės struktūros, tarpusavyje sujungtos verslo logikos jungčių tinklu. Tokių priklausomybių palaikymas PHP projektas buvo didžiulis iššūkis PHP kūrėjai, ir vis didėjantis populiarumas SaaS sprendimus ir labai išaugusį populiarumą debesis paslaugos lėmė, kad šiandien vis dažniau girdime apie mikroservisai ir taikomųjų programų moduliškumą.
Kaip, kurdami nepriklausomas mikroservisus, galime priversti juos keistis informacija tarpusavyje?
Šis straipsnis yra pirmasis iš straipsnių serijos apie mikroservisų bendravimas Symfony sistema ir joje aptariamas populiariausias būdas - AMQP ryšys naudojant RabbitMQ.
Tikslas
Sukurti dvi nepriklausomas programas ir užtikrinti ryšį tarp jų naudojant tik pranešimų magistralę.
Koncepcija
Turime dvi įsivaizduojamas, nepriklausomas programas:
* app1: darbuotojams siunčiami el. pašto ir SMS pranešimai
* programa2: kuri leidžia valdyti darbuotojų darbą ir priskirti jiems užduotis.
Norime sukurti modernią ir paprastą sistemą, pagal kurią darbas darbuotojui būtų priskiriamas programa2 klientui bus išsiųstas pranešimas naudojant app1. Nepaisant to, kas atrodo, tai labai paprasta!
Paruošimas
Šiame straipsnyje naudosime naujausią "Symfony" versiją (6.1 versija rašymo metu) ir naujausią PHP versiją (8.1). Keliais labai paprastais veiksmais sukursime veikiančią vietinę "Docker" aplinka su dviem mikroservisais. Viskas, ko jums reikia:
* veikiantis kompiuteris,
* įdiegta "Docker" + "Docker Compose" aplinka
* ir vietoje sukonfigūruotą Symfony CLI ir šiek tiek laisvo laiko.
Vykdymo aplinka
Naudosimės "Docker" kaip programų virtualizavimo ir konteinerizavimo įrankio galimybėmis. Pradėkime nuo katalogų medžio sukūrimo, dviejų "Symfony" programosir aprašyti mūsų aplinkos infrastruktūrą naudojant docker-compose.yml failas.
cd ~
mkdir microservices-in-symfony
cd microservices-in-symfony
symfony new app1
symfony new app2
touch docker-compose.yml
Sukūrėme du katalogus dviem atskiroms "Symfony" programoms ir sukūrėme tuščią docker-compose.yml failą, kuriuo paleidžiama mūsų aplinka.
Pridėkime šiuos skyrius prie docker-compose.yml file:
versija: '3.8'
paslaugos:
Paslaugos: app1:
containername: app1
build: app1/.
restart: on-failure
envfile: app1/.env
aplinka:
APPNAME: app1
tty: true
stdinopen: true
app2:
containername: app2
build: app2/.
restart: on-failure
envfile: app2/.env
aplinka:
APPNAME: app2
tty: true
stdinopen: true
Rabbitmq:
containername: rabbitmq
vaizdas: rabbitmq:management
ports:
- 15672:15672
- 5672:5672
aplinka:
- RABBITMQDEFAULTUSER=user
- RABBITMQDEFAULT_PASS=password
Šaltinis kodas galima įsigyti tiesiogiai: thecodest-co/microservices-in-symfony/blob/main/docker-compose.yml
Bet palaukite, kas čia nutiko? Tiems, kurie nesusipažinę su "Docker", pirmiau pateiktas konfigūracijos failas gali atrodyti mįslingas, tačiau jo paskirtis labai paprasta. Naudodami "Docker Compose" kuriame tris "paslaugas":
- app1: tai pirmosios "Symfony" programos konteineris
- app2: antrosios "Symfony" programos konteineris
- rabbitmq: "RabbitMQ" programos atvaizdas kaip komunikacijos tarpinės programinės įrangos sluoksnis
Kad tinkamai veiktų, mums vis dar reikia "Docker" failas failai, iš kurių kuriami atvaizdai. Taigi sukurkime juos:
touch app1/Dockerfile
touch app2/Dockerfile
Abiejų failų struktūra yra lygiai tokia pati ir atrodo taip:
IŠ 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/
Paleisti chmod +x /usr/local/bin/install-php-extensions && sync &&
install-php-extensions amqp
Paleiskite 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/*
Paleiskite docker-php-ext-install zip;
CMD bash -c "cd /app && composer install && php -a"
Šaltinio kodas prieinamas tiesiogiai: /thecodest-co/microservices-in-symfony/blob/main/app1/Dockerfile
Pirmiau pateiktą failą "Docker Compose" naudoja konteineriui sukurti iš PHP 8.1 atvaizdo su įdiegtu "Composer" ir AMQP plėtiniu. Be to, jis paleidžia PHP intepreterį priedėlio režimu, kad konteineris veiktų fone.
Dabar jūsų katalogų ir failų medis turėtų atrodyti taip:
.
├──── app1
│ └── └── Dockerfile
| (...) # Symfony programėlės struktūra
├── app2
│ └── └── Dockerfile
| (...) # Symfony programėlės struktūra
└── docker-compose.yml
Pirmoji "Symfony" mikroserviso paslauga
Pradėkime nuo app1 katalogą ir pirmąją programą.
Mūsų pavyzdyje tai yra programa, kuri klausosi ir vartoja pranešimus iš eilės, siunčiamus programa2 kaip aprašyta reikalavimuose:
darbo priskyrimas darbuotojui
programa2klientui bus išsiųstas pranešimas
Pradėkime nuo reikiamų bibliotekų pridėjimo. AMQP yra natūraliai palaikoma symfony/messenger pratęsimas. Papildomai įdiegsime monologas/monologas sekti sistemos žurnalus, kad būtų lengviau analizuoti programų elgseną.
cd app1/
symfony composer req amqp ampq-messenger monolog
Įdiegus buvo pridėtas papildomas failas config/packages/messenger.yaml. Tai "Symfony Messenger" komponento konfigūracijos failas ir mums nereikia jo pilna konfigūracija.
Pakeiskite jį toliau pateiktu YAML failu:
sistema:
messenger:
# Atmeskite šį (ir toliau nurodytą nepavykusį transportą), kad nepavykę pranešimai būtų siunčiami į šį transportą ir vėliau tvarkomi.
# failure_transport: failed
transportas:
# https://symfony.com/doc/current/messenger.html#transport-configuration
external_messages:
dsn: "%env(MESSENGER_TRANSPORT_DSN)%
options:
auto_setup: false
mainai:
pavadinimas: pranešimai
tipas: direct
default_publish_routing_key: from_external
eilės:
pranešimai: pranešimai: išoriniai: išoriniai: pranešimai: išoriniai:
privalomieji raktai: pranešimai: pranešimai: privalomieji_raktai: [from_external]
Šaltinio kodas prieinamas tiesiogiai: thecodest-co/microservices-in-symfony/blob/main/app1/config/packages/messenger.yaml
"Symfony Messenger" naudojamas sinchroniniam ir asinchroniniam bendravimui "Symfony" programose. Jis palaiko įvairias transportuoja, arba transporto sluoksnio tiesos šaltiniai. Savo pavyzdyje naudojame AMQP plėtinį, kuris palaiko įvykių eilių sistemą RabbitMQ.
Pirmiau pateiktoje konfigūracijoje apibrėžiamas naujas transportas pavadinimu external_messages, kuriame pateikiamos nuorodos į MESSENGER_TRANSPORT_DSN aplinkos kintamasis ir apibrėžia tiesioginį klausymąsi pranešimai kanalas pranešimų magistralėje. Šiuo metu taip pat redaguokite app1/.env failą ir pridėkite atitinkamą transporto adresą.
"`env
(...)
MESSENGERTRANSPORTDSN=amqp://user:password@rabbitmq:5672//messages
(...)
"
Parengus taikomosios programos struktūrą ir sukonfigūravus bibliotekas, metas įgyvendinti verslo logiką. Žinome, kad mūsų programa turi reaguoti į darbo priskyrimą darbuotojui. Taip pat žinome, kad darbo priskyrimasapp2system pakeičia darbo būseną. Taigi sukurkime modelį, imituojantį būsenos pasikeitimą, ir išsaugokime jįapp1/Message/StatusUpdate.php` kelyje:

{
viešoji funkcija __construct(protected string $status){}
public function getStatus(): string
{
return $this->status;
}
}
Šaltinio kodas prieinamas tiesiogiai: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Message/StatusUpdate.php
Mums vis dar reikia klasės, kuri įgyvendintų verslo logiką, kai mūsų mikroservisas gauna minėtą įvykį iš eilės. Taigi sukurkime Pranešimų tvarkyklė į app1/Handler/StatusUpdateHandler.php kelias:

naudoti PsrLogLoggerInterface;
use SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
klasė StatusUpdateHandler
{
viešoji funkcija __construct(
protected LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->warning('APP1: {STATUS_UPDATE} - '.$statusDescription);
// likusi verslo logikos dalis, t. y. el. laiško siuntimas naudotojui
// $this->emailService->email()
}
}
Šaltinio kodas prieinamas tiesiogiai: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Handler/StatusUpdateHandler.php
PHP atributai gerokai palengvina darbą ir reiškia, kad šiuo konkrečiu atveju mums nereikia rūpintis dėl automatinio įjungimo ar paslaugų deklaracijos. Mūsų mikroservisas, skirtas domeno įvykiams tvarkyti, paruoštas, laikas pereiti prie antrosios programos.
Antroji "Symfony" mikroserviso paslauga
Apžvelgsime programa2 katalogas ir antrasis "Symfony" programa. Mūsų idėja - siųsti pranešimą į eilę, kai darbuotojui sistemoje priskiriama užduotis. Taigi atlikime greitą AMQP konfigūravimą ir priverskime mūsų antrąją mikroservisą pradėti skelbti StatusUpdate įvykius į pranešimų magistralę.
Bibliotekos diegiamos lygiai taip pat, kaip ir pirmosios programos atveju.
cd ..
cd app2/
symfony composer req amqp ampq-messenger monolog
Įsitikinkime, kad app2/.env faile yra galiojantis "RabbitMQ" DSN įrašas:
(...)
MESSENGER_TRANSPORT_DSN=amqp://user:password@rabbitmq:5672//messages
(...)
Belieka tik sukonfigūruoti "Symfony Messenger app2/config/packages/messenger.yaml file:
sistema:
messenger:
# Atmeskite šį (ir toliau nurodytą nepavykusį transportą), kad nepavykę pranešimai būtų siunčiami į šį transportą ir vėliau tvarkomi.
# failure_transport: failed
transportas:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: "%env(MESSENGER_TRANSPORT_DSN)%
maršrutizavimas:
# Nukreipti pranešimus į transportą
"AppMessageStatusUpdate": async
Kaip matote, šį kartą transporto apibrėžtis nukreipia tiesiai į async ir apibrėžia maršruto parinkimą siunčiant mūsų StatusUpdate pranešimą į sukonfigūruotą DSN. Tai vienintelė konfigūravimo sritis, belieka sukurti AMQP eilės loginį ir įgyvendinimo sluoksnį. Tam sukursime dvynį StatusUpdateHandler ir StatusUpdate klasės programa2.

naudoti PsrLogLoggerInterface;
use SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
klasė StatusUpdateHandler
{
viešoji funkcija __construct(
private readonly LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->warning('APP2: {STATUS_UPDATE} - '.$statusDescription);
## verslo logika, t. y. vidinių pranešimų siuntimas arba kai kurių kitų sistemų įrašymas į eilę
}
}

{
viešoji funkcija __construct(protected string $status){}
public function getStatus(): string
{
return $this->status;
}
}
Šaltinio kodas prieinamas tiesiogiai: /thecodest-co/microservices-in-symfony/blob/main/app2/src/Message/StatusUpdate.php
Galiausiai belieka sukurti būdą, kaip siųsti pranešimą į pranešimų magistralę. Sukursime paprastą Symfony komanda už tai:

naudoti SymfonyComponentConsoleAttributeAsCommand;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
use SymfonyComponentMessengerMessageBusInterface;
[AsCommand(
name: "app:send"
)]
klasė SendStatusCommand plečia 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(
pranešimas: new StatusUpdate($status)
);
return Command::SUCCESS;
}
}
Ačiū Priklausomybės įterpimas galime naudoti MessageBusInterface komandoje ir išsiųsti StatusUpdate pranešimą per išsiuntimas() metodą į mūsų eilę. Be to, čia taip pat naudojame PHP atributus.
Štai ir viskas - belieka paleisti "Docker Compose" aplinką ir pažiūrėti, kaip elgiasi mūsų programos.
Aplinkos paleidimas ir testavimas
Naudojant "Docker Compose" konteineriai su dviem mūsų programomis bus sukurti ir paleisti kaip atskiri egzemplioriai, o vienintelis tarpinės programinės įrangos sluoksnis bus Rabbitmq konteinerį ir mūsų pranešimų magistralės realizaciją.
Iš projekto šakninio katalogo paleiskime šias komandas:
cd ../ # įsitikinkite, kad esate pagrindiniame kataloge
docker-compose up --build -d
Ši komanda gali šiek tiek užtrukti, nes sukuriami du atskiri konteineriai su PHP 8.1 + AMQP ir ištraukiamas RabbitMQ atvaizdas. Būkite kantrūs. Sukūrę atvaizdus galite paleisti mūsų komandą iš programa2 ir išsiųsti keletą pranešimų į eilę.
docker exec -it app2 php bin/console app:send
Galite tai daryti tiek kartų, kiek tik galite. Kol nėra vartotojas jūsų pranešimai nebus apdorojami. Kai tik paleidžiate app1 ir suvalgyti visus pranešimus, kuriuos jie rodys jūsų ekrane.
docker exec -it app1 php bin/console messenger:consume -vvv external_messages

Visas šaltinio kodas kartu su README rasite mūsų viešoje saugykloje The Codest Github
Santrauka
"Symfony" su savo bibliotekomis ir įrankiais leidžia greitai ir efektyviai kurti modernius žiniatinklio svetainė programos. Naudodamiesi keliomis komandomis ir keliomis kodo eilutėmis galime sukurti modernią ryšių tarp programų sistemą. Symfony, kaip ir PHP, idealiai tinka žiniatinklio programų kūrimas o dėl savo ekosistemos ir lengvo įdiegimo ši ekosistema pasiekia vienus iš geriausių pateikimo rinkai laiko rodiklių.
Tačiau greitas ne visada reiškia geras - pirmiau pateiktame pavyzdyje pateikėme paprasčiausią ir greičiausią bendravimo būdą. Labiau smalsūs tikrai pastebės, kad trūksta domeno įvykių atskyrimo už taikomojo sluoksnio ribų - dabartinėje versijoje jie dubliuojami, be to, nėra visiško palaikymo Vokas, be kita ko, nėra Antspaudai. Kviečiu skaityti II dalį, kurioje aptarsime "Symfony" programų domenų struktūros suvienodinimo mikroservisų aplinkoje temą ir aptarsime antrąjį populiarų mikroservisų komunikacijos metodą - šį kartą sinchroninį, paremtą REST API.
