PHP 8.2: Co nowego?
Nowa wersja PHP jest tuż za rogiem. Jakie są nowe implementacje, o których powinieneś wiedzieć? Sprawdź ten artykuł, aby się dowiedzieć!
Przeczytaj pierwszą część naszej serii PHP poświęconej komunikacji mikroserwisów we frameworku Symfony i najpopularniejszemu sposobowi - komunikacji AMQP z wykorzystaniem RabbitMQ.
Nowoczesna architektura aplikacji wymusiła na deweloperach zmianę sposobu myślenia o komunikacji pomiędzy poszczególnymi komponentami systemów informatycznych. Kiedyś sprawa była prostsza - większość systemów tworzona była jako monolityczne struktury połączone ze sobą siecią powiązań logiki biznesowej. Utrzymanie takich zależności w PHP projekt było ogromnym wyzwaniem dla Programiści PHPa także rosnąca popularność rozwiązań SaaS i ogromny wzrost popularności chmura Usługi spowodowały, że dziś coraz częściej słyszymy o mikrousługach i modułowości aplikacji.
W jaki sposób, tworząc niezależne mikrousługi, możemy sprawić, by wymieniały one między sobą informacje?
Ten artykuł jest pierwszym z serii postów na temat Komunikacja mikrousług w Symfony i obejmuje najpopularniejszy sposób - komunikację AMQP przy użyciu RabbitMQ.
Aby utworzyć dwie niezależne aplikacje i osiągnąć komunikację między nimi przy użyciu tylko magistrali komunikatów.
Mamy dwie wyimaginowane, niezależne aplikacje:
* app1
: który wysyła powiadomienia e-mail i SMS do pracowników
* app2
który pozwala zarządzać pracą pracowników i przydzielać im zadania.
Chcemy stworzyć nowoczesny i prosty system, dzięki któremu przydzielanie pracy pracownikowi w app2
wyśle powiadomienie do klienta przy użyciu app1
. Wbrew pozorom jest to bardzo proste!
Na potrzeby tego artykułu użyjemy najnowszej wersji Symfony (wersja 6.1 w momencie pisania) i najnowszej wersji PHP (8.1). W kilku bardzo prostych krokach stworzymy działające lokalne środowisko Docker z dwoma mikroserwisami. Wszystko czego potrzebujesz to:
* działający komputer,
* zainstalowany Docker + Środowisko Docker Compose
* i lokalnie skonfigurowany Symfony CLI i trochę wolnego czasu.
Wykorzystamy możliwości Dockera jako narzędzia do wirtualizacji i konteneryzacji aplikacji. Zacznijmy od utworzenia drzewa katalogów, szkieletu dla dwóch Aplikacje Symfonyi opisać infrastrukturę naszych środowisk przy użyciu docker-compose.yml
plik.
cd ~
mkdir microservices-in-symfony
cd microservices-in-symfony
symfony new app1
symfony new app2
touch docker-compose.yml
Stworzyliśmy dwa katalogi dla dwóch oddzielnych aplikacji Symfony i utworzyliśmy pusty katalog docker-compose.yml
aby uruchomić nasze środowisko.
Dodajmy następujące sekcje do docker-compose.yml
file:
version: '3.8'
usługi:
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=user
- RABBITMQDEFAULT_PASS=hasło
Źródło kod dostępne bezpośrednio: thecodest-co/microservices-in-symfony/blob/main/docker-compose.yml
Ale zaraz, co tu się stało? Dla osób niezaznajomionych z Dockerem powyższy plik konfiguracyjny może wydawać się enigmatyczny, jednak jego cel jest bardzo prosty. Korzystając z Docker Compose budujemy trzy "usługi":
Do prawidłowego działania nadal potrzebujemy Plik Docker
które są źródłem do tworzenia obrazów. Więc stwórzmy je:
touch app1/Dockerfile
touch app2/Dockerfile
Oba pliki mają dokładnie taką samą strukturę i wyglądają następująco:
Z 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-extensions 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/*
RUN docker-php-ext-install zip;
CMD bash -c "cd /app && composer install && php -a"
Kod źródłowy dostępny bezpośrednio: /thecodest-co/microservices-in-symfony/blob/main/app1/Dockerfile
Powyższy plik jest używany przez Docker Compose do zbudowania kontenera z obrazu PHP 8.1 z zainstalowanym Composerem i rozszerzeniem AMQP. Dodatkowo uruchamia intepreter PHP w trybie append, aby kontener działał w tle.
Drzewo katalogów i plików powinno teraz wyglądać następująco:
.
├── app1
│ └── Dockerfile
| (...) # Struktura aplikacji Symfony
├── app2
│ └── Dockerfile
| (...) # Struktura aplikacji Symfony
└── docker-compose.yml
Zacznijmy od app1
i pierwszej aplikacji.
W naszym przykładzie jest to aplikacja, która nasłuchuje i pobiera wiadomości z kolejki wysyłanej przez app2
zgodnie z opisem w wymaganiach:
przypisanie zadania do pracownika w
app2
wyśle powiadomienie do klienta
Zacznijmy od dodania wymaganych bibliotek. AMQP jest natywnie obsługiwany dla symfony/messenger
rozszerzenie. Dodatkowo zainstalujemy monolog/monolog
do śledzenia dzienników systemowych w celu łatwiejszej analizy zachowania aplikacji.
cd app1/
symfony composer req amqp ampq-messenger monolog
Po instalacji dodano dodatkowy plik w sekcji config/packages/messenger.yaml
. Jest to plik konfiguracyjny dla komponentu Symfony Messenger i nie potrzebujemy go pełna konfiguracja.
Zastąp go poniższym plikiem YAML:
ramy:
messenger:
# Usuń ten komentarz (i nieudany transport poniżej), aby wysyłać nieudane wiadomości do tego transportu w celu późniejszej obsługi.
# failure_transport: failed
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
external_messages:
dsn: "%env(MESSENGER_TRANSPORT_DSN)%
options:
auto_setup: false
exchange:
name: messages
typ: bezpośredni
default_publish_routing_key: from_external
queues:
messages:
binding_keys: [from_external]
Kod źródłowy dostępny bezpośrednio: thecodest-co/microservices-in-symfony/blob/main/app1/config/packages/messenger.yaml
Symfony Messenger służy do synchronicznej i asynchronicznej komunikacji w aplikacjach Symfony. Obsługuje wiele różnych transportyczyli źródła prawdy warstwy transportowej. W naszym przykładzie używamy rozszerzenia AMQP, które obsługuje system kolejek zdarzeń RabbitMQ.
Powyższa konfiguracja definiuje nowy transport o nazwie external_messages
który odwołuje się do MESSENGER_TRANSPORT_DSN
i definiuje bezpośrednie nasłuchiwanie na wiadomości
w Message Bus. W tym momencie należy również edytować app1/.env
i dodać odpowiedni adres transportowy.
“`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;
}
}
Kod źródłowy dostępny bezpośrednio: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Message/StatusUpdate.php
Nadal potrzebujemy klasy, która zaimplementuje logikę biznesową, gdy nasza mikrousługa otrzyma powyższe zdarzenie z kolejki. Stwórzmy więc klasę Obsługa komunikatów w app1/Handler/StatusUpdateHandler.php
ścieżka:
use PsrLogLoggerInterface;
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);
// reszta logiki biznesowej, tj. wysyłanie wiadomości e-mail do użytkownika
// $this->emailService->email()
}
}
Kod źródłowy dostępny bezpośrednio: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Handler/StatusUpdateHandler.php
PHP znacznie ułatwiają sprawę i oznaczają, że w tym konkretnym przypadku nie musimy martwić się o autowiring czy deklarację usługi. Nasza mikrousługa do obsługi zdarzeń domenowych jest gotowa, czas zabrać się za drugą aplikację.
Przyjrzymy się app2
i drugi katalog Aplikacja Symfony. Naszą ideą jest wysyłanie wiadomości do kolejki, gdy pracownik otrzyma zadanie w systemie. Wykonajmy więc szybką konfigurację AMQP i uruchommy naszą drugą mikrousługę, aby rozpocząć publikację StatusUpdate
zdarzeń do magistrali komunikatów.
Instalacja bibliotek przebiega dokładnie tak samo, jak w przypadku pierwszej aplikacji.
cd ..
cd app2/
symfony composer req amqp ampq-messenger monolog
Upewnijmy się, że app2/.env
zawiera prawidłowy wpis DSN dla RabbitMQ:
(...)
MESSENGER_TRANSPORT_DSN=amqp://user:password@rabbitmq:5672/%2f/messages
(...)
Pozostaje tylko skonfigurować Symfony Messenger w sekcji app2/config/packages/messenger.yaml
file:
ramy:
messenger:
# Usuń ten komentarz (i nieudany transport poniżej), aby wysyłać nieudane wiadomości do tego transportu w celu późniejszej obsługi.
# failure_transport: failed
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
# Przekieruj wiadomości do transportów
'AppMessageStatusUpdate': async
Jak widać, tym razem definicja transportu wskazuje bezpośrednio na asynchroniczny
i definiuje routing w postaci wysyłania naszych StatusUpdate
do skonfigurowanej sieci DSN. Jest to jedyny obszar konfiguracji, pozostaje tylko stworzyć logikę i warstwę implementacji kolejki AMQP. W tym celu utworzymy bliźniaczą StatusUpdateHandler
i StatusUpdate
zajęcia w app2
.
use PsrLogLoggerInterface;
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);
## logika biznesowa, tj. wysyłanie wewnętrznych powiadomień lub kolejkowanie innych systemów
}
}
{
public function __construct(protected string $status){}
public function getStatus(): string
{
return $this->status;
}
}
Kod źródłowy dostępny bezpośrednio: /thecodest-co/microservices-in-symfony/blob/main/app2/src/Message/StatusUpdate.php
Na koniec wszystko, co należy zrobić, to stworzyć sposób wysyłania wiadomości do magistrali komunikatów. Stworzymy prostą aplikację Polecenie Symfony za to:
use SymfonyComponentConsoleAttributeAsCommand;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
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 = "Pracownik X przypisany do Y";
$this->messageBus->dispatch(
message: new StatusUpdate($status)
);
return Command::SUCCESS;
}
}
Dzięki Wstrzykiwanie zależności możemy użyć instancji MessageBusInterface
w naszym poleceniu i wysłać StatusUpdate
wiadomość za pośrednictwem dispatch()
do naszej kolejki. Dodatkowo używamy tutaj również atrybutów PHP.
To wszystko - pozostaje tylko uruchomić nasze środowisko Docker Compose i sprawdzić, jak zachowują się nasze aplikacje.
Dzięki Docker Compose kontenery z naszymi dwiema aplikacjami zostaną zbudowane i uruchomione jako oddzielne instancje, a jedyną warstwą pośredniczącą będzie rabbitmq
i nasza implementacja magistrali komunikatów.
Z katalogu głównego projektu uruchommy następujące polecenia:
cd ../ # upewnij się, że jesteś w katalogu głównym
docker-compose up --build -d
To polecenie może zająć trochę czasu, ponieważ buduje dwa oddzielne kontenery z PHP 8.1 + AMQP i pobiera obraz RabbitMQ. Bądź cierpliwy. Po zbudowaniu obrazów możesz uruchomić naszą komendę z poziomu app2
i wysłać kilka wiadomości do kolejki.
docker exec -it app2 php bin/console app:send
Można to robić dowolną liczbę razy. Tak długo, jak nie ma konsument wiadomości nie będą przetwarzane. Jak tylko uruchomisz aplikację app1
i konsumować wszystkie wiadomości, które będą wyświetlane na ekranie.
docker exec -it app1 php bin/console messenger:consume -vv external_messages
Kompletny kod źródłowy wraz z plikiem README można znaleźć w naszym publicznym repozytorium The Codest Github
Symfony ze swoimi bibliotekami i narzędziami pozwala na szybkie i wydajne podejście do tworzenia nowoczesnych aplikacji. aplikacje internetowe. Za pomocą kilku komend i kilku linijek kodu jesteśmy w stanie stworzyć nowoczesny system komunikacji pomiędzy aplikacjami. Symfony, podobnie jak PHPjest idealny dla tworzenie aplikacji internetowych a dzięki swojemu ekosystemowi i łatwości wdrożenia osiąga jedne z najlepszych wskaźników czasu wprowadzenia na rynek.
Jednak szybko nie zawsze znaczy dobrze - w powyższym przykładzie przedstawiliśmy najprostszy i najszybszy sposób komunikacji. Co bardziej dociekliwi z pewnością zauważą, że brakuje odłączenia zdarzeń domenowych poza warstwę aplikacji - w obecnej wersji są one dublowane, nie ma też pełnego wsparcia dla Koperta
między innymi nie ma Znaczki
. Tych i innych zapraszam do lektury części II, w której poruszymy temat ujednolicania struktury domenowej aplikacji Symfony w środowisku mikrousług oraz omówimy drugą popularną metodę komunikacji mikrousług - tym razem synchroniczną, opartą o REST API.