Node.js für PHP-Entwickler: 5 wichtige pragmatische Aspekte mit Codebeispielen

Während die Popularität von Node.js steigt, sinkt die Popularität von PHP. Vor diesem Hintergrund werden in diesem Beitrag 5 praktische Aspekte erläutert, die PHP-Entwickler unbedingt wissen müssen, um Node.js zu nutzen. Das sind Dinge, über die niemand spricht oder schreibt, Zeit, loszulegen.

  1. Node.js für PHP-Entwickler (nicht Node.js vs. PHP)
  2. Node.js für PHP-Entwickler die praktische Seite
    1. Node.js Code-Ausführung ist asynchron und nicht sequentiell
      1. Node.js verspricht Möglichkeiten
    2. Node.js Prozess ist langlaufend, im Gegensatz zu PHP
      1. Memoization Beispiel
      2. Connection Pool Beispiel mit MySQL
    3. Debugging ist einfacher in Node.js einfacher als in PHP
    4. Große Versions-Upgrades in Node.js sind im Vergleich zu PHP nahtlos
    5. Docking einer Node.js-Anwendung ist im Vergleich zu PHP ein Kinderspiel
  3. Abschluss

Node.js für PHP-Entwickler (nicht Node.js gegen PHP) #

Dieser Beitrag ist eine Liste von Dingen, die Sie als PHP-Entwickler wissen und lernen müssen, um Node.js effektiv zu nutzen. Im Gegenteil, dieser Beitrag ist kein Node.js vs. PHP-Artikel, in dem PHP niedergemacht wird. Ich habe beide Sprachen benutzt. Im Jahr 2016 habe ich begonnen, mehr Node.js zu schreiben. Als ich anfing, hatte ich einige Schwierigkeiten, da ich zuvor mehr als 7 Jahre lang mit PHP gearbeitet hatte. Ende 2012 wurde ein Buch veröffentlicht, das Node.js für PHP-Entwickler behandelt.

In diesem Blogbeitrag werde ich nicht darüber sprechen, was PHP oder Node.js ist, darüber können Sie in anderen Beiträgen lesen. Ich werde auch nicht viel über Non-Blocking I/O oder die Ereignisschleife sprechen. Dennoch wird einiges davon durchgestrichen, wenn es um die praktischen Aspekte des Schreibens von gutem Node.js-Code geht.

Node.js für PHP-Entwickler – die praktische Seite #

PHP gibt es seit 1995 und angeblich wird es immer noch von 79,% der von W3tech überwachten Websites verwendet (ich kann nicht wirklich sagen, ob es das gesamte Internet ist). Die Wahrscheinlichkeit ist also sehr hoch, dass Sie PHP verwenden oder etwas in PHP geschriebenes einsetzen. Zum Beispiel mit steigender Tendenz:

WordPress wird von 63,7 % aller Websites verwendet, deren Content-Management-System wir kennen. Das sind 39,0 % aller von W3Tech überwachten Websites.

Auf der anderen Seite wurde Node.js im Jahr 2009 veröffentlicht. Große Tech-Unternehmen wie Linked In und Paypal begannen zwischen 2011 und 2013, es aus verschiedenen Gründen wie Microservices zu übernehmen. Laut der Stack Overflow-Entwicklerumfrage von 2020:

Im zweiten Jahr in Folge nimmt Node.js den ersten Platz ein, da es von der Hälfte der Befragten verwendet wird.

Es ist kein Geheimnis, dass Node.js in den letzten 5 Jahren sehr beliebt geworden ist.

Als PHP-Entwickler sind dies also 5 praktische Dinge, die man wissen muss, um ein großartiger Node.js-Softwareentwickler zu sein. Node.js für PHP-Entwickler ist in mancher Hinsicht ähnlich, unterscheidet sich aber auch in einigen anderen Aspekten, die im Folgenden beschrieben werden:

Node.js Code-Ausführung ist asynchron und nicht sequentiell #

Dies ist ein Verhalten, das viele PHP-Entwickler täuscht. In PHP wird der Code der Reihe nach ausgeführt, zuerst Zeile 1, dann 2 und so weiter. In Javascript und insbesondere in Node.js ist das nicht unbedingt der Fall. Man kann potenziell Dinge in den Hintergrund stellen, wenn man Versprechen und Rückrufe gut einsetzt.

Nachfolgend ein modifiziertes Codebeispiel mit einer Erklärung aus meinem Open-Source-Währungs-Api-Repo:

async function getExternal(fromCurrency, toCurrency, onDate) {
const rate = await getRate(fromCurrency, toCurrency, onDate);
db.query(
`INSERT INTO exchange_rates (from_currency, to_currency, rate, on_date) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE rate = ?`,

).then(result => {
if (result.affectedRows === 0) {
console.error(`Exchange rate of ${rate} for ${fromCurrency} to ${toCurrency} on ${onDate} could not be saved`);
}
}).catch(err => {
console.log(`Error while writing to db: `, err);
}); //this is done async for the API to respond faster
console.log(`Fetched exchange rate of ${rate} for ${fromCurrency} to ${toCurrency} of ${onDate} from the API`);
return { fromCurrency, toCurrency, onDate, rate };
}

Wenn man genauer hinsieht, ist das unschuldig aussehende db.query in Zeile 3 in den Hintergrund geschoben worden. Es wird also wie folgt ausgeführt:

  1. Get rate
  2. Run insert query in the background
  3. Während der Insert läuft, gibt die Funktion bereits die Rate zurück
  4. Wenn es ein Problem in der Insert-Abfrage gibt, wird es in der catch protokolliert

Es gibt keine einfache Möglichkeit, so etwas in PHP zu tun. Das ist das erste, was PHP-Entwickler stutzig macht. Es macht es für PHP-Entwickler schwieriger, Node.js zu verstehen. Dieses asynchrone Codeausführungsverhalten macht es auch schwieriger, bei Fehlern in Node.js den richtigen Stacktrace zu finden.

Um ehrlich zu sein, kann man in 2020 problemlos async await verwenden. Auch wenn es syntaktischer Zucker auf Promises ist, macht es die asynchrone Programmierung verdammt viel einfacher. Als ich in der Node 4/6-Ära um 2016 herum mit Callbacks und Promises anfing, war das noch ein ganz anderes Spiel. Dennoch sollte man aufpassen, wann man async-await nicht verwendet (wie oben) und einfach mit Promises, then und catch arbeitet. Verheddern Sie sich dabei aber nicht in der Promise-Hölle. Die Promise-Hölle ist so etwas wie die nächste Iteration der Callback-Hölle.

Pro-Tipp: Um zu wissen, welche ES6-Features Sie mit welcher Version von Node.js nutzen können, schauen Sie bei node.green nach.

Ein weiterer Pro-Tipp:

Gerade Node.js Versionen sind LTS, ungerade nicht. Verwenden Sie also Node 14 oder 16 und nicht 13 oder 15 in der Produktion.

Bei der nicht-sequenziellen Ausführung spielen Versprechen und die damit verbundene Macht eine wichtige Rolle. Die Fähigkeit, gleichzeitige Dinge zu tun, ist großartig in Node.js und Javascript im Allgemeinen.

Node.js promises possibilities #

Promises being asynchronous, you can run them concurrently. There are ways to do it. Du könntest 3 Promises gleichzeitig laufen lassen und die Ergebnisse des schnellsten abrufen. Sie können sogar promise.all, wo, wenn ein Versprechen abgelehnt wird, stoppt es die ganze Operation. Lesen Sie mehr über Promise.race, promise.all und promise.any in diesem großartigen Vergleich.

Mit diesen Überlegungen können Sie andere NPM-Bibliotheken ausprobieren, um die Gleichzeitigkeit von Versprechen zu begrenzen oder sogar gleichzeitig durch Versprechen zu filtern. Einiges davon kann man mit ReactPHP machen. Aber es ist nicht im nativen PHP enthalten, nicht einmal in PHP 8. Das ist etwas Neues für PHP-Entwickler in Node.js.

Lassen Sie uns zum nächsten Punkt übergehen, der Prozess muss in Node.js nicht sterben wie in PHP.

Node.js-Prozesse sind langlaufend, im Gegensatz zu PHP #

PHP soll nicht in dem Sinne sterben, dass es nicht benutzt wird. Sondern in dem Sinne, dass alle PHP-Prozesse sterben müssen. PHP ist nicht wirklich für langlaufende Aufgaben/Prozesse konzipiert. Wenn in PHP eine neue HTTP-Anfrage eintrifft, beginnt die Verarbeitung, und nachdem die Antwort zurückgeschickt wurde, wird der Prozess beendet. Das ist die Funktionsweise von PHP. Das schafft die Notwendigkeit für FPM und andere Server. Man kann argumentieren, dass PHP schon vor 20+ Jahren von vornherein serverlos war. Das überlasse ich Ihnen.

Auf der anderen Seite ist Node.js ein lang laufender Prozess. Dies ermöglicht es Ihnen, Informationen zwischen Anfragen zu teilen, da derselbe Server/Prozess mehrere Anfragen bearbeitet. Mit einem langlaufenden Prozess kann man leicht Dinge wie Memoisierung des Speichers und Verbindungspooling für eine Datenbank ausnutzen. Es eröffnet andere Möglichkeiten, wie z.B. das Zählen der Anzahl der gleichzeitigen Anfragen an diesen Prozess.

Memoization example #

Wenn Sie Memoization nicht kennen.

Memoization ist eine Funktion höherer Ordnung, die eine andere Funktion in den Cache stellt. Sie kann einige langsame Funktionen in schnelle Funktionen verwandeln. Es speichert das Ergebnis eines Funktionsaufrufs nach dem ersten Mal im Cache, so dass es bei einem erneuten Aufruf der Funktion mit den gleichen Argumenten im Cache gefunden wird.

Es kann in Node.js verwendet werden, aber nicht nativ in PHP. Einige Workarounds sind in PHP möglich, wie z.B. das Speichern des Funktionsrückgabewertes in Redis.

Nachfolgend ein Codebeispiel der Memoisierung auf einer Express-Route mit p-memoize:

const ONE_MINUTE_IN_MS = 60000;
const options = {
maxAge: ONE_MINUTE_IN_MS,
cacheKey: (arguments_) => arguments_.join(','),
};
app.get('/api/products', async (req, res, next) => {
try {
const memGetProducts = pMemoize(products.getMultiple, options);
res.json(await memGetProducts(req.query.page || 1, req.query.search));
} catch (err) {
next(err);
}
});

Der klare Vorteil ist die geringere Belastung des Datenspeichers. Für 1 Minute antwortet er mit der gleichen Antwort für die gleichen Parameter zurück. Die Ausgabe der Funktion products.getMultiple wird eine Minute lang im Speicher zwischengespeichert. Das macht die Antworten sehr schnell.

Beispiel für einen Verbindungspool mit MySQL #

Eine andere Sache, die wegen eines sterbenden Prozesses in PHP nicht möglich ist, ist Verbindungspooling. Laut Wikipedia:

In der Softwaretechnik ist ein Verbindungspool ein Zwischenspeicher für Datenbankverbindungen, der so gepflegt wird, dass die Verbindungen wiederverwendet werden können, wenn zukünftige Anfragen an die Datenbank erforderlich sind. Verbindungspools werden verwendet, um die Leistung bei der Ausführung von Befehlen auf einer Datenbank zu verbessern.

So haben Sie 5 Verbindungen in einem Pool, und wenn Sie 5 Abfragen an die Datenbank durchführen wollen, können Sie dies gleichzeitig tun. Das spart Zeit sowohl für die Verbindung zur Datenbank als auch für die Ausführung der Abfrage. Das ist in Node.js leicht zu bewerkstelligen, aber in PHP nicht ohne weiteres möglich.

Achten Sie auf die Anzahl der verfügbaren Verbindungen und halten Sie die Größe Ihres Verbindungspools optimal.

Wenn Sie zum Beispiel Kubernetes verwenden und Ihre Anwendung 5 Pods mit einer Verbindungspoolgröße von 2 hat. Das bedeutet, dass Ihre Datenbank immer 10 offene Verbindungen hat, auch wenn keine Abfragen ausgeführt werden.

Zeit für ein Connection-Pool-Beispiel mit MySQL-Datenbank mit MySQL npm-Modul:

var pool = mysql.createPool({
connectionLimit : 5,
host : 'example.org',
user : 'app',
password : 'pass',
database : 'schema'
});
for(var i=0;i<5;i++){
pool.query('SELECT 1 + 1 AS solution', function(err, rows, fields) {
if (err) {
throw err;
}
console.log(rows.solution); //Shows 2
});
}

Der obige Code führt dieselbe Abfrage 5 Mal parallel mit 5 MySQL-Verbindungen aus, die aus dem Connection-Pool genommen werden. Ich wünschte, ich könnte das in PHP sofort machen.

Node.js funktioniert meiner Erfahrung nach sehr gut mit Mysql. Wenn Sie das Connection Pooling mit Mongo DB ausprobieren möchten, finden Sie hier ein Mongo-Beispiel.

Bei einem lang laufenden Prozess müssen Sie als Entwickler mehr auf Speicherlecks achten und das Housekeeping-Zeug gut machen.

Das ist der Punkt, an dem Node.js für PHP-Entwickler ein bisschen umdenken müssen, wie der Code ausgeführt wird. Andererseits ist dies ein großer Vorteil von Node.js für PHP-Entwickler.

Debugging ist in Node.js einfacher als in PHP #

Zeilenweises Debuggen von Code ist ein wichtiger Teil der Entwicklererfahrung für jede Programmiersprache. Um PHP-Code zu debuggen, können Sie Add-ons wie X-Debug mit einigen IDE-Einstellungen verwenden. Die Einrichtung von X-Debug ist, gelinde gesagt, eine Herausforderung. Sie müssen es installieren und die Erweiterung aktivieren. Danach müssen Sie es mit einer IDE wie PHPStorm richtig konfigurieren.

Grundsätzlich ist „einfach“ das Letzte, was Sie sagen werden, wenn Sie X-Debug zum Laufen bringen wollen. Es sei denn, es ist alles gut mit einem Docker-Container konfiguriert und die IDE-Einstellungen sind auch leicht zu laden.

Auf der anderen Seite ist das Ausführen von Node Native Debugger oder sogar ndb viel einfacher im Vergleich zu PHP und X-debug. Mit VS Code ist das Debuggen von Node.js-Anwendungen so einfach, dass sogar ein Höhlenmensch es tun kann.

Öffnen Sie die Einstellungen > Settings und geben Sie im Suchfeld „node debug“ ein. Unter der Registerkarte „Erweiterungen“ sollte eine Erweiterung mit dem Titel „Node debug“ zu finden sein. Klicken Sie hier auf das erste Kästchen: Debug > Node: Auto Attach und setzen Sie das Dropdown-Menü auf „on“. Jetzt können Sie fast sofort loslegen. Ja, es ist wirklich so einfach.

Setzen Sie dann einige Haltepunkte im VS-Code mit index.js und geben Sie im Terminal node --inspect index.js ein.

BOOM! Ihr schrittweiser Node.js-Debugger läuft im VS-Code-Editor ohne großen Aufwand. Im Gegensatz zu PHP ist es nicht notwendig, eine andere Erweiterung zu installieren, zu aktivieren und zu konfigurieren, um ein Programm debuggen zu können. Keine Notwendigkeit, eine zusätzliche Erweiterung zu installieren, ist ein Vorteil von Node.js für PHP-Entwickler.

Der nächste Punkt betrifft auch die bessere Erfahrung von Entwicklern beim Upgrade selbst mehrerer Hauptversionen der Sprache.

Hauptversions-Upgrades in Node.js sind nahtlos im Vergleich zu PHP #

Der Wechsel zwischen mehreren Hauptversionen in Node.js ist eine nahtlose Erfahrung. Ein Upgrade von PHP 5.x auf PHP 7.x ist ein wochen- bis monatelanger Prozess, je nach Größe und Komplexität des Projekts.

In meiner persönlichen Erfahrung habe ich in der Vergangenheit Node.js-Microservices von Version 0.12 auf 4 aktualisiert. Kürzlich habe ich eine Anwendung von Node.js 10 auf 14 aktualisiert. Alle meine Node.js-Hauptversions-Upgrades waren einfach.

Einige kleinere package.json-Änderungen waren die einzigen kleinen Probleme, die ich hatte. Nach der Bereitstellung gab es kaum Probleme mit der Codekompatibilität. Als zusätzlicher Bonus war die Leistung beim Upgrade der Hauptversionen in der Regel besser.

Andererseits war das Upgrade von PHP nicht einfach. Ein Minor-Versions-Upgrade für eine Anwendung von PHP 5.4 auf 5.6 war nicht sehr mühsam. Aber der Wechsel von PHP 5.6 auf 7.2 für eine relativ große Anwendung war eine Qual. Es dauerte sehr lange und erforderte mehrere composer.json Änderungen. Es war auch eine schwierige Aufgabe, es zu testen. Die gute Seite eines großen Versionswechsels in PHP war sicherlich der Leistungsschub.

Nur eine Anmerkung: Die PHP-Anwendungen, mit denen ich gearbeitet habe, waren älter als die Node.js-Anwendungen. Ihre Erfahrung kann sicherlich anders sein als meine.

Docking einer Node.js-Anwendung ist im Vergleich zu PHP ein Kinderspiel #

Dockers Popularität ist in den letzten 5 Jahren stetig gestiegen. Es hat die Arbeitsweise von Softwareentwicklern seit seiner Veröffentlichung verändert. Sie sollten Docker auch für die lokale Entwicklung verwenden. In diesem Sinne kann die Dockerisierung einer PHP-Anwendung eine schwierige Aufgabe sein, je nachdem, wie die Komponenten angeordnet sind und wie komplex die Anwendung ist. Umgekehrt ist der Aufwand für das Dockerisieren einer Node.js-Anwendung geringer und der Prozess ist ein Kinderspiel.

Unten sehen Sie ein Beispiel für ein Dockerfile für eine PHP-Laravel-Anwendung mit Apache.

FROM composer:1.9.0 as build
WORKDIR /app
COPY . /app
RUN composer global require hirak/prestissimo && composer install
FROM php:7.3-apache-stretch
RUN docker-php-ext-install pdo pdo_mysql
EXPOSE 8080
COPY --from=build /app /var/www/
COPY docker/000-default.conf /etc/apache2/sites-available/000-default.conf
COPY .env.example /var/www/.env
RUN chmod 777 -R /var/www/storage/ && \
echo "Listen 8080" >> /etc/apache2/ports.conf && \
chown -R www-data:www-data /var/www/ && \
a2enmod rewrite

Das Gute an diesem Docker-Image für Laravel ist, dass PHP mit Apache im selben Image gebündelt ist. Man kann darüber streiten, ob dies ein besserer Weg ist, als PHP und Apache in zwei Docker-Images aufzuteilen.

Beachten Sie auch den mehrstufigen Docker-Build im obigen Docker-Image. Die Composer-Installation wird in einem anderen Image durchgeführt und die Ausgabe wird in das Hauptimage kopiert. Hätten wir PHP-FPM und Nginx in verschiedenen Docker-Images verwendet, wäre das Ganze noch komplexer gewesen. Es müssten zwei verschiedene Docker-Images verwaltet werden.

Nun ist es an der Zeit, einen Blick auf ein Node.js-Dockerfile zu werfen.

FROM node:14-alpine as base
WORKDIR /src
COPY package.json package-lock.json /src/
COPY . /src
EXPOSE 8080
FROM base as production
ENV NODE_ENV=production
RUN npm install
CMD
FROM base as dev
ENV NODE_ENV=development
RUN npm config set unsafe-perm true && npm install -g nodemon
RUN npm install
CMD

Da Node.js einen eingebauten Webserver hat, ist das Dockerfile viel sauberer.

Wenn man Node installiert, wird npm gleich mitgeliefert. Dadurch entfällt die Notwendigkeit, Pakete in einer anderen Phase des Docker-Builds zu installieren.

In der obigen Dockerdatei wird ein mehrstufiger Docker-Build verwendet, um Produktions- und Entwicklungs-Docker-Images zu trennen. Den Paketmanager (npm) gebündelt zu haben und den Webserver als Teil der Sprache/Laufzeit zu haben, ist etwas anderes in Node.js für PHP-Entwickler. Wenn Sie daran interessiert sind, eine Node.js-Anwendung Schritt für Schritt zu docken, folgen Sie diesem Tutorial.

Fazit #

Bei der Verwendung von Node.js für PHP-Entwickler ist ein leichtes Umdenken erforderlich, um die Möglichkeiten von Node.js gut zu nutzen. Node.js ist kein Allheilmittel. Es hat Nachteile und muss an verschiedene Arten der Codeausführung angepasst werden.

Gegenwärtig gibt es einige Vorteile bei der Verwendung von Node.js für PHP-Entwickler, wie asynchrone Programmierung und Gleichzeitigkeit. Andere Vorteile ergeben sich aus der Node.

Ich hoffe, dieser Beitrag hilft Ihnen als erfahrener PHP-Entwickler, mehr aus Node.js herauszuholen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.