Symfony: Mikroservisu komunikācija I daļa
Izlasiet mūsu PHP sērijas pirmo daļu, kas veltīta mikropakalpojumu komunikācijai Symfony ietvarstruktūrā un populārākajam veidam - AMQP komunikācijai, izmantojot RabbitMQ.
Mūsdienu lietojumprogrammu arhitektūra ir piespiedusi izstrādātājus mainīt domāšanas veidu par saziņu starp dažādām IT sistēmu sastāvdaļām. Kādreiz viss bija vienkāršāk - lielākā daļa sistēmu tika veidotas kā monolītas struktūras, kas savā starpā bija savienotas ar biznesa loģikas savienojumu tīklu. Šādu atkarību uzturēšana PHP projekts bija liels izaicinājums PHP izstrādātāji, kā arī pieaugošā popularitāte SaaS risinājumus un milzīgo popularitātes pieaugumu, ko rada mākonis pakalpojumi izraisīja, ka šodien mēs arvien vairāk dzirdam par mikroservisi un lietojumprogrammu modularitāte.
Kā, izveidojot neatkarīgus mikropakalpojumus, mēs varam panākt, lai tie savstarpēji apmainās ar informāciju?
Šis raksts ir pirmais publikāciju sērijā par mikropakalpojumu komunikācija Symfony ietvarstruktūra, un tā aptver populārāko veidu - AMQP saziņu, izmantojot RabbitMQ.
Mērķis
Izveidot divas neatkarīgas lietojumprogrammas un nodrošināt saziņu starp tām, izmantojot tikai ziņojumu kopni.
Koncepcija
Mums ir divas iedomātas, neatkarīgas lietojumprogrammas:
* app1: kas nosūta e-pasta un SMS paziņojumus darbiniekiem
* lietotne2: kas ļauj pārvaldīt darbinieku darbu un piešķirt viņiem uzdevumus.
Mēs vēlamies izveidot mūsdienīgu un vienkāršu sistēmu, ar kuras palīdzību darba piešķiršana darbiniekam, kas lietotne2 nosūtīs paziņojumu klientam, izmantojot app1. Neskatoties uz šķietamību, tas ir ļoti vienkārši!
Sagatavošana
Šajā rakstā mēs izmantosim jaunāko Symfony versiju (6.1 versija rakstīšanas laikā) un jaunāko PHP versiju (8.1). Dažos ļoti vienkāršos soļos mēs izveidosim darbojošos vietējo Docker vide ar divām mikropakalpojumiem. Viss, kas jums nepieciešams, ir:
* darbojošos datoru,
* instalēta Docker + Docker Compose vide
* un lokāli konfigurētu Symfony CLI un nedaudz brīvā laika.
Runtime vide
Mēs izmantosim Docker kā lietojumprogrammu virtualizācijas un konteinerizācijas rīka iespējas. Sāksim, izveidojot direktoriju koku, sistēmu diviem Symfony lietojumprogrammas, un aprakstīt mūsu vides infrastruktūru, izmantojot docker-compose.yml failu.
cd ~
mkdir microservices-in-symfony
cd microservices-in-symfony
symfony new app1
symfony new app2
touch docker-compose.yml
Mēs esam izveidojuši divus direktorijus divām atsevišķām Symfony lietojumprogrammām un izveidojuši tukšu docker-compose.yml failu, lai palaistu mūsu vidi.
Pievienosim šādas sadaļas docker-compose.yml file:
versija: '3.8'
pakalpojumi:
app1:
containername: app1
build: app1/.
restart: on-failure
envfile: app1/.env
vide:
APPNAME: app1
tty: true
stdinopen: true
app2:
containername: app2
build: app2/.
restart: on-failure
envfile: app2/.env
vide:
APPNAME: app2
tty: true
stdinopen: true
rabbitmq:
containername: rabbitmq
attēls: rabbitmq:management
ports:
- 15672:15672
- 5672:5672
vide:
- RABBITMQDEFAULTUSER=user
- RABBITMQDEFAULT_PASS=password
Avots: kods pieejams tieši: thecodest-co/microservices-in-symfony/blob/main/docker-compose.yml
Bet pagaidiet, kas šeit notika? Tiem, kas nav pazīstami ar Docker, iepriekš minētais konfigurācijas fails var šķist mīklains, tomēr tā mērķis ir ļoti vienkāršs. Izmantojot Docker Compose, mēs veidojam trīs "pakalpojumus":
- app1: kas ir konteiners pirmajai Symfony lietojumprogrammai.
- app2: kas ir konteiners otrajai Symfony lietojumprogrammai.
- rabbitmq: RabbitMQ lietojumprogrammas attēls kā sakaru starpprogrammatūras slānis.
Lai nodrošinātu pareizu darbību, mums joprojām ir nepieciešams Dockerfile faili, kas ir avots attēlu veidošanai. Tāpēc izveidosim tos:
touch app1/Dockerfile
touch app2/Dockerfile
Abiem failiem ir tieši tāda pati struktūra, un tie izskatās šādi:
NO php:8.1
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
KOPĒT . /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-extensions amqp
RUN apt-get update
&& apt-get install -y libzip-dev wget --no-no-install-recommends
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
Palaidiet docker-php-ext-install zip;
CMD bash -c "cd /app && composer install && php -a"
Avota kods ir pieejams tieši: /thecodest-co/microservices-in-symfony/blob/main/app1/Dockerfile
Iepriekš minēto failu izmanto Docker Compose, lai izveidotu konteineru no PHP 8.1 attēla ar Kompozitoru un instalētu AMQP paplašinājumu. Turklāt tas palaiž PHP intepreter pievienošanas režīmā, lai konteiners darbotos fonā.
Tagad jūsu direktoriju un failu kokam vajadzētu izskatīties šādi:
.
├──── app1
│ └── └── Dockerfile
| (...) # Symfony lietotnes struktūra
├──── app2
│ └── └── Dockerfile
| (...) # Symfony lietotnes struktūra
└── docker-compose.yml
Pirmā Symfony mikroservisa izveide
Sāksim ar app1 direktoriju un pirmo lietojumprogrammu.
Mūsu piemērā tā ir lietojumprogramma, kas klausās un patērē ziņojumus no rindas, ko sūta lietotne2 kā aprakstīts prasībās:
piešķirot darbu darbiniekam
lietotne2nosūtīs paziņojumu klientam
Sāksim ar nepieciešamo bibliotēku pievienošanu. AMQP ir dabiski atbalstīta symfony/messenger pagarinājums. Mēs papildus instalēsim monologs/monologs lai varētu sekot līdzi sistēmas žurnāliem, kas atvieglo lietojumprogrammu uzvedības analīzi.
cd app1/
symfony composer req amqp ampq-messenger monolog
Pēc instalēšanas tika pievienots papildu fails zem config/packages/messenger.yaml. Tas ir Symfony Messenger komponenta konfigurācijas fails, un mums tas nav nepieciešams. pilnīga konfigurācija.
Aizstājiet to ar tālāk norādīto YAML failu:
ietvars:
messenger:
# Atceliet šo (un zemāk norādīto neveiksmīgo transportu), lai uz šo transportu nosūtītu neveiksmīgus ziņojumus vēlākai apstrādei.
# failure_transport: failed
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
external_messages:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
opcijas:
auto_setup: false
maiņa:
nosaukums: ziņojumi
tips: direct
default_publish_routing_key: from_external
rindas:
: ziņojumi: ārējās: ārējās: ārējās: ārējās: ārējās: ārējās: ārējās
Saistošie_atslēgas: ziņojumi: ziņojumi: ziņojumi: saistošās_ atslēgas: [from_external]
Avota kods ir pieejams tieši: thecodest-co/microservices-in-symfony/blob/main/app1/config/packages/messenger.yaml
Symfony Messenger tiek izmantots sinhronajai un asinhronajai saziņai Symfony lietojumprogrammās. Tas atbalsta dažādas transportsvai transporta slāņa patiesības avoti. Mūsu piemērā mēs izmantojam AMQP paplašinājumu, kas atbalsta notikumu rindu sistēmu RabbitMQ.
Iepriekš minētā konfigurācija definē jaunu transportu ar nosaukumu external_messages, kas atsaucas uz MESSENGER_TRANSPORT_DSN vides mainīgo un definē tiešo klausīšanos uz ziņojumi ziņu kopnes kanāls. Šajā brīdī rediģējiet arī app1/.env failu un pievienojiet attiecīgo transporta adresi.
"`env
(...)
MESSENGERTRANSPORTDSN=amqp://user:password@rabbitmq:5672//messages
(...)
"
Pēc lietojumprogrammas ietvara sagatavošanas un bibliotēku konfigurēšanas ir pienācis laiks īstenot biznesa loģiku. Mēs zinām, ka mūsu lietojumprogrammai ir jāreaģē uz uzdevuma piešķiršanu darbiniekam. Mēs arī zinām, ka, piešķirot darbuapp2system, mainās darba statuss. Tāpēc izveidosim modeli, kas imitē statusa maiņu, un saglabāsim toapp1/Message/StatusUpdate.php` ceļā:

{
publiskā funkcija __construct(protected string $status){}
public function getStatus(): string
{
return $this->status;
}
}
Avota kods ir pieejams tieši: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Message/StatusUpdate.php
Mums joprojām ir nepieciešama klase, kas īstenos biznesa loģiku, kad mūsu mikroserviss saņem iepriekš minēto notikumu no rindas. Tātad izveidosim Ziņojuma apstrādātājs in the app1/Handler/StatusUpdateHandler.php ceļš:

izmantot PsrLogLoggerInterface;
use SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
klase StatusUpdateHandler
{
publiskā funkcija __construct(
protected LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->brīdinājums('APP1: {STATUS_UPDATE} - '.$statusDescription);
// pārējā biznesa loģika, t. i., e-pasta nosūtīšana lietotājam
// $this->emailService->email()
}
}
Avota kods ir pieejams tieši: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Handler/StatusUpdateHandler.php
PHP atribūti ievērojami atvieglo darbu un nozīmē, ka šajā konkrētajā gadījumā mums nav jāuztraucas par automātisko vadu vai servisa deklarāciju. Mūsu mikroserviss domēna notikumu apstrādei ir gatavs, ir pienācis laiks ķerties pie otrās lietojumprogrammas.
Otra Symfony mikroservisa pakalpojums
Mēs aplūkosim lietotne2 direktoriju un otro Symfony lietojumprogramma. Mūsu ideja ir nosūtīt ziņojumu uz rindu, kad sistēmā darbiniekam tiek piešķirts uzdevums. Veiksim ātru AMQP konfigurāciju un sāksim publicēt mūsu otro mikroservisu. StatusUpdate notikumi uz ziņojumu kopni.
Bibliotēku instalēšana ir tieši tāda pati kā pirmajā lietojumprogrammā.
cd ..
cd app2/
symfony composer req amqp ampq-messenger monolog
Pārliecināsimies, ka app2/.env failā ir derīgs DSN ieraksts RabbitMQ:
(...)
MESSENGER_TRANSPORT_DSN=amqp://user:password@rabbitmq:5672//messages
(...)
Atliek tikai konfigurēt Symfony Messenger programmā app2/config/packages/messenger.yaml file:
ietvars:
messenger:
# Atceliet šo (un zemāk norādīto neveiksmīgo transportu), lai uz šo transportu nosūtītu neveiksmīgus ziņojumus vēlākai apstrādei.
# failure_transport: failed
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
maršrutēšana:
# Novirzīt ziņojumus uz transportu
'AppMessageStatusUpdate': async
Kā redzat, šoreiz transporta definīcija norāda tieši uz async un definē maršrutēšanu, nosūtot mūsu StatusUpdate ziņojumu uz konfigurēto DSN. Šī ir vienīgā konfigurācijas joma, atliek tikai izveidot AMQP rindas loģiku un īstenošanas slāni. Šim nolūkam mēs izveidosim dvīņu StatusUpdateHandler un StatusUpdate klases lietotne2.

izmantot PsrLogLoggerInterface;
use SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
klase StatusUpdateHandler
{
publiskā funkcija __construct(
private readonly LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->brīdinājums('APP2: {STATUS_UPDATE} - '.$statusDescription);
## darbības loģika, t. i., iekšējā paziņojuma nosūtīšana vai rindas veidošana citās sistēmās
}
}

{
publiskā funkcija __construct(protected string $status){}
public function getStatus(): string
{
return $this->status;
}
}
Avota kods ir pieejams tieši: /thecodest-co/microservices-in-symfony/blob/main/app2/src/Message/StatusUpdate.php
Visbeidzot, atliek tikai izveidot veidu, kā nosūtīt ziņojumu uz ziņojumu kopni. Mēs izveidosim vienkāršu Symfony komanda šim nolūkam:

izmantot SymfonyComponentConsoleAttributeAsCommand;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
use SymfonyComponentMessengerMessageBusInterface;
[AsCommand(
name: "app:send"
)]
klase SendStatusCommand extends 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(
ziņojums: new StatusUpdate($status)
);
return Command::SUCCESS;
}
}
Paldies Atkarību ieiegriešana mēs varam izmantot MessageBusInterface mūsu komandā un nosūtiet StatusUpdate ziņojumu, izmantojot nosūtīšana() metode mūsu rindā. Turklāt mēs šeit izmantojam arī PHP atribūtus.
Tas ir viss - atliek tikai palaist mūsu Docker Compose vidi un apskatīt, kā uzvedīsies mūsu lietojumprogrammas.
Vides palaišana un testēšana
Izmantojot Docker Compose, konteineri ar mūsu divām lietojumprogrammām tiks izveidoti un palaisti kā atsevišķi gadījumi, un vienīgais starpprogrammatūras slānis būs rabbitmq konteiners un mūsu ziņojumu kopnes implementācija.
No projekta saknes direktorija palaidīsim šādas komandas:
cd ../ # pārliecinieties, ka atrodaties galvenajā direktorijā
docker-compose up --build -d
Šī komanda var aizņemt kādu laiku, jo tā izveido divus atsevišķus konteinerus ar PHP 8.1 + AMQP un velk RabbitMQ attēlu. Esiet pacietīgi. Pēc tam, kad attēli ir izveidoti, varat palaist mūsu komandu no lietotne2 un nosūtīt dažus ziņojumus uz rindu.
docker exec -it app2 php bin/console app:send
Jūs varat to darīt tik reižu, cik vien varat. Kamēr nav patērētājs jūsu ziņojumi netiks apstrādāti. Tiklīdz iedarbināsiet app1 un izmantot visus ziņojumus, kas tiek rādīti ekrānā.
docker exec -it app1 php bin/console messenger:consume -vvv external_messages

Pilnīga avota kods kopā ar README var atrast mūsu publiskajā repozitorijā The Codest Github
Kopsavilkums
Symfony ar tās bibliotēkām un rīkiem ļauj ātri un efektīvi izstrādāt modernu tīmekļa vietne lietojumprogrammas. Ar dažām komandām un dažām koda rindiņām mēs varam izveidot mūsdienīgu saziņas sistēmu starp lietojumprogrammām. Symfony, tāpat kā PHP, ir ideāli piemērots tīmekļa lietojumprogrammu izstrāde un, pateicoties tās ekosistēmai un vieglai ieviešanai, šī ekosistēma sasniedz vienus no labākajiem tirgus ieviešanas laika rādītājiem.
Tomēr ātrs ne vienmēr nozīmē labs - iepriekš minētajā piemērā mēs piedāvājām vienkāršāko un ātrāko saziņas veidu. Zinātkārākie noteikti pamanīs, ka trūkst domēna notikumu atslēgšanas ārpus lietojumprogrammu slāņa - pašreizējā versijā tie tiek dublēti, un nav pilnīga atbalsta Aploksne, cita starpā nav Pastmarkas. Šiem un citiem interesentiem aicinu izlasīt II daļu, kurā aplūkosim tēmu par Symfony lietojumprogrammu domēna struktūras unifikāciju mikropakalpojumu vidē un apspriedīsim otru populāro mikropakalpojumu saziņas metodi - šoreiz sinhrono, kas balstīta uz REST API.
