PHP 8.2: Was ist neu?
Die neue Version von PHP steht vor der Tür. Was sind die neuen Implementierungen, über die Sie Bescheid wissen sollten? Lesen Sie diesen Artikel, um es herauszufinden!
Lesen Sie den ersten Teil unserer PHP-Serie, die sich mit der Kommunikation von Microservices im Symfony-Framework und dem beliebtesten Weg - AMQP-Kommunikation mit RabbitMQ - beschäftigt.
Die moderne Anwendungsarchitektur hat die Entwickler gezwungen, die Kommunikation zwischen den verschiedenen Komponenten von IT-Systemen anders zu betrachten. Früher war die Sache einfacher - die meisten Systeme wurden als monolithische Strukturen erstellt, die durch ein Netz von Geschäftslogikverbindungen miteinander verbunden waren. Die Aufrechterhaltung solcher Abhängigkeiten in einer PHP Projekt war eine große Herausforderung für PHP Entwicklerund die wachsende Beliebtheit von SaaS-Lösungen und die enorme Zunahme der Popularität von Wolke Services haben dazu geführt, dass wir heute immer mehr über Microservices und Anwendungsmodularität hören.
Wie können wir durch die Schaffung unabhängiger Microservices dafür sorgen, dass diese untereinander Informationen austauschen?
Dieser Artikel ist der erste in einer Reihe von Beiträgen über Microservices-Kommunikation in Symfony Framework und deckt den beliebtesten Weg ab - AMQP-Kommunikation mit RabbitMQ.
Zwei unabhängige Anwendungen erstellen und die Kommunikation zwischen ihnen nur über den Nachrichtenbus erreichen.
Wir haben zwei imaginäre, unabhängige Anwendungen:
* app1
E-Mail- und SMS-Benachrichtigungen an die Mitarbeiter senden
* app2
: Damit können Sie die Arbeit Ihrer Mitarbeiter verwalten und ihnen Aufgaben zuweisen.
Wir wollen ein modernes und einfaches System schaffen, bei dem die Zuweisung von Arbeit an einen Mitarbeiter in app2
sendet eine Benachrichtigung an den Kunden mit app1
. Trotz des Anscheins ist dies sehr einfach!
Für die Zwecke dieses Artikels werden wir die neueste Symfony-Version (Version 6.1 zum Zeitpunkt der Erstellung dieses Artikels) und die neueste Version von PHP (8.1) verwenden. In wenigen, sehr einfachen Schritten werden wir eine funktionierende lokale Docker-Umgebung mit zwei Microservices erstellen. Alles, was Sie brauchen, ist:
* einen funktionierenden Computer,
* installiertes Docker + Docker Compose-Umgebung
* und eine lokal konfigurierte Symfony CLI und etwas freie Zeit.
Wir werden die Fähigkeiten von Docker als Werkzeug zur Anwendungsvirtualisierung und Containerisierung nutzen. Beginnen wir mit der Erstellung eines Verzeichnisbaums, eines Rahmens für zwei Symfony-Anwendungenund beschreiben die Infrastruktur unserer Umgebungen mit Hilfe des docker-compose.yml
Datei.
cd ~
mkdir microservices-in-symfony
cd microservices-in-symfony
symfony neu app1
symfony neu app2
touch docker-compose.yml
Wir haben zwei Verzeichnisse für zwei separate Symfony-Anwendungen erstellt und ein leeres docker-compose.yml
Datei, um unsere Umgebung zu starten.
Fügen wir die folgenden Abschnitte zur docker-compose.yml
file:
Version: '3.8'
Dienste:
app1:
containername: app1
bauen: app1/.
restart: bei Ausfall
Umgebungsdatei: app1/.env
Umgebung:
APPNAME: app1
tty: wahr
stdinopen: wahr
app2:
containername: app2
bauen: app2/.
restart: bei Fehlschlag
Umgebungsdatei: app2/.env
Umgebung:
APPNAME: app2
tty: wahr
stdinopen: wahr
rabbitmq:
containername: rabbitmq
Bild: rabbitmq:management
Ports:
- 15672:15672
- 5672:5672
Umgebung:
- RABBITMQDEFAULTUSER=Benutzer
- RABBITMQDEFAULT_PASS=Kennwort
Quelle Code direkt verfügbar: thecodest-co/microservices-in-symfony/blob/main/docker-compose.yml
Aber Moment, was ist hier passiert? Für diejenigen, die mit Docker nicht vertraut sind, mag die obige Konfigurationsdatei rätselhaft erscheinen, aber ihr Zweck ist sehr einfach. Mit Docker Compose bauen wir drei "Dienste" auf:
Für einen ordnungsgemäßen Betrieb benötigen wir noch Dockerdatei
Dateien, die die Quelle für die Erstellung der Bilder sind. Lassen Sie uns diese also erstellen:
touch app1/Dockerfile
app2/Dockerfile berühren
Beide Dateien haben genau die gleiche Struktur und sehen wie folgt aus:
VON 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-erweiterungen amqp
RUN 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/*
Führen Sie docker-php-ext-install zip aus;
CMD bash -c "cd /app && composer install && php -a"
Quellcode direkt verfügbar: /thecodest-co/microservices-in-symfony/blob/main/app1/Dockerfile
Die obige Datei wird von Docker Compose verwendet, um einen Container aus einem PHP 8.1-Image mit Composer und der installierten AMQP-Erweiterung zu erstellen. Außerdem wird der PHP-Intepreter im Append-Modus ausgeführt, damit der Container im Hintergrund weiterläuft.
Ihr Verzeichnis- und Dateibaum sollte nun wie folgt aussehen:
.
├── app1
│ └── Dockerfile
| (...) # Symfony App Struktur
├── app2
│ └── Dockerfile
| (...) # Symfony App Struktur
└─── docker-compose.yml
Beginnen wir mit der app1
Verzeichnis und die erste Anwendung.
In unserem Beispiel handelt es sich um eine Anwendung, die Nachrichten aus der Warteschlange abhört und konsumiert, die von app2
wie in den Anforderungen beschrieben:
Zuweisung eines Jobs an einen Arbeiter in
app2
sendet eine Benachrichtigung an den Kunden
Beginnen wir mit dem Hinzufügen der erforderlichen Bibliotheken. AMQP wird nativ unterstützt für die symfony/messenger
Erweiterung. Wir werden zusätzlich installieren Monolog/Monolog
um Systemprotokolle für eine einfachere Analyse des Anwendungsverhaltens aufzubewahren.
cd app1/
symfony composer req amqp ampq-messenger monolog
Nach der Installation wurde eine zusätzliche Datei unter config/packages/messenger.yaml
. Es handelt sich um eine Konfigurationsdatei für die Symfony Messenger-Komponente und wir brauchen sie nicht. vollständige Konfiguration.
Ersetzen Sie sie durch die unten stehende YAML-Datei:
Rahmen:
messenger:
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
# failure_transport: failed
Transporte:
# https://symfony.com/doc/current/messenger.html#transport-configuration
externe_nachrichten:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
Optionen:
auto_setup: falsch
Austausch:
name: nachrichten
type: direkt
default_publish_routing_key: from_external
Warteschlangen:
nachrichten:
binding_keys: [from_external]
Quellcode direkt verfügbar: thecodest-co/microservices-in-symfony/blob/main/app1/config/packages/messenger.yaml
Symfony Messenger wird für synchrone und asynchrone Kommunikation in Symfony-Anwendungen verwendet. Er unterstützt eine Vielzahl von transportiertoder Quellen der Wahrheit der Transportschicht. In unserem Beispiel verwenden wir die AMQP-Erweiterung, die das RabbitMQ-Ereignis-Warteschlangensystem unterstützt.
Die obige Konfiguration definiert einen neuen Transport namens externe_Nachrichten
die sich auf den BOTEN_TRANSPORT_DSN
Umgebungsvariable und definiert das direkte Abhören auf dem Nachrichten
Kanal im Nachrichtenbus. Bearbeiten Sie zu diesem Zeitpunkt auch die app1/.env
und fügen Sie die entsprechende Transportadresse hinzu.
“`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;
}
}
Quellcode direkt verfügbar: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Message/StatusUpdate.php
Wir brauchen noch eine Klasse, die die Geschäftslogik implementiert, wenn unser Microservice das obige Ereignis von der Warteschlange erhält. Erstellen wir also eine Nachrichten-Handler im app1/Handler/StatusUpdateHandler.php
Pfad:
use PsrLogLoggerSchnittstelle;
use SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
class StatusUpdateHandler
{
public function __construct(
protected LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->warning('APP1: {STATUS_UPDATE} - '.$statusDescription);
// der Rest der Geschäftslogik, d.h. das Senden einer E-Mail an den Benutzer
// $this->emailService->email()
}
}
Quellcode direkt verfügbar: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Handler/StatusUpdateHandler.php
PHP Attribute machen die Dinge viel einfacher und bedeuten, dass wir uns in diesem speziellen Fall keine Gedanken über Autowiring oder Dienstdeklaration machen müssen. Unser Microservice für die Verarbeitung von Domänenereignissen ist fertig, es ist Zeit, sich mit der zweiten Anwendung zu befassen.
Wir werden einen Blick auf die app2
Verzeichnis und das zweite Symfony-Anwendung. Unsere Idee ist es, eine Nachricht an die Warteschlange zu senden, wenn einem Worker eine Aufgabe im System zugewiesen wird. Lassen Sie uns also eine schnelle Konfiguration von AMQP durchführen und unseren zweiten Microservice mit der Veröffentlichung beginnen StatusUpdate
Ereignisse an den Nachrichtenbus.
Die Installation der Bibliotheken erfolgt genauso wie bei der ersten Anwendung.
cd ..
cd app2/
symfony composer req amqp ampq-messenger monolog
Wir müssen sicherstellen, dass die app2/.env
Datei einen gültigen DSN-Eintrag für RabbitMQ enthält:
(...)
MESSENGER_TRANSPORT_DSN=amqp://user:password@rabbitmq:5672/%2f/messages
(...)
Alles, was bleibt, ist die Konfiguration von Symfony Messenger in der app2/config/packages/messenger.yaml
file:
Rahmen:
messenger:
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
# failure_transport: failed
Transporte:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
Weiterleitung:
# Leiten Sie Ihre Nachrichten an die Transporte weiter
AppMessageStatusUpdate': async
Wie Sie sehen können, verweist die Transportdefinition diesmal direkt auf asynchron
und definiert das Routing in Form des Sendens unserer StatusUpdate
Nachricht an den konfigurierten DSN. Dies ist der einzige Bereich, der konfiguriert werden muss. Nun müssen nur noch die Logik und die Implementierungsschicht der AMQP-Warteschlange erstellt werden. Hierfür erstellen wir den Zwilling StatusUpdateHandler
und StatusUpdate
Klassen in app2
.
use PsrLogLoggerSchnittstelle;
use SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
class StatusUpdateHandler
{
public function __construct(
private readonly LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->warning('APP2: {STATUS_UPDATE} - '.$statusDescription);
## Geschäftslogik, d.h. Senden einer internen Benachrichtigung oder Einreihen in eine Warteschlange anderer Systeme
}
}
{
public function __construct(protected string $status){}
public function getStatus(): string
{
return $this->status;
}
}
Quellcode direkt verfügbar: /thecodest-co/microservices-in-symfony/blob/main/app2/src/Message/StatusUpdate.php
Schließlich muss nur noch eine Möglichkeit geschaffen werden, eine Nachricht an den Nachrichtenbus zu senden. Wir erstellen eine einfache Symfony-Befehl für diese:
use SymfonyComponentConsoleAttributeAsCommand;
use SymfonyComponentConsoleCommandBefehl;;
use SymfonyComponentConsoleEingabeEingabeSchnittstelle;
use SymfonyComponentConsoleOutputOutputInterface;
use SymfonyComponentMessengerMessageBusInterface;
[AsCommand(
Name: "app:send"
)]
class 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 = "Arbeiter X zugewiesen an Y";
$this->messageBus->dispatch(
Nachricht: new StatusUpdate($status)
);
return Befehl::SUCCESS;
}
}
Dank an Injektion von Abhängigkeiten können wir eine Instanz von MessageBusInterface
in unserem Befehl und senden eine StatusUpdate
Nachricht über die dispatch()
Methode zu unserer Warteschlange. Zusätzlich verwenden wir hier auch PHP Attribute.
Das war's - jetzt müssen wir nur noch unsere Docker Compose-Umgebung ausführen und sehen, wie sich unsere Anwendungen verhalten.
Mit Docker Compose werden die Container mit unseren beiden Anwendungen als separate Instanzen erstellt und ausgeführt, die einzige Middleware-Schicht ist die rabbitmq
Container und unsere Nachrichtenbus-Implementierung.
Führen Sie im Stammverzeichnis des Projekts die folgenden Befehle aus:
cd ../ # stellen Sie sicher, dass Sie sich im Hauptverzeichnis befinden
docker-compose up --build -d
Dieser Befehl kann einige Zeit in Anspruch nehmen, da er zwei separate Container mit PHP 8.1 + AMQP erstellt und das RabbitMQ-Image zieht. Haben Sie Geduld. Nachdem die Images erstellt wurden, können Sie unseren Befehl von app2
und einige Nachrichten an eine Warteschlange senden.
docker exec -it app2 php bin/console app:send
Du kannst es so oft machen, wie du kannst. Solange es keine Verbraucher werden Ihre Nachrichten nicht bearbeitet. Sobald Sie die app1
und konsumieren Sie alle Nachrichten, die auf Ihrem Bildschirm angezeigt werden.
docker exec -it app1 php bin/console messenger:consume -vv external_messages
Die vollständige Quellcode sowie die README finden Sie in unserem öffentlichen Repository The Codest Github
Symfony mit seinen Bibliotheken und Tools ermöglicht einen schnellen und effizienten Ansatz zur Entwicklung moderner Webanwendungen. Mit ein paar Befehlen und ein paar Zeilen Code sind wir in der Lage, ein modernes Kommunikationssystem zwischen Anwendungen zu schaffen. Symfony, wie PHPist ideal für Entwicklung von Webanwendungen und dank seines Ökosystems und der einfachen Implementierung erreicht dieses Ökosystem einige der besten Markteinführungsindikatoren.
Schnell bedeutet jedoch nicht immer gut - im obigen Beispiel haben wir den einfachsten und schnellsten Weg der Kommunikation vorgestellt. Wissbegierigen wird sicherlich auffallen, dass es an der Trennung von Domänenereignissen außerhalb der Anwendungsschicht mangelt - in der aktuellen Version werden sie dupliziert, und es gibt keine vollständige Unterstützung für Umschlag
unter anderem gibt es keine Briefmarken
. Für diese und andere Fragen lade ich Sie ein, Teil II zu lesen, in dem wir das Thema der Vereinheitlichung der Domänenstruktur von Symfony-Anwendungen in einer Microservices-Umgebung behandeln und die zweite beliebte Microservices-Kommunikationsmethode diskutieren - diesmal synchron, basierend auf der REST-API.