Ist Node.js ein Superheld?

Keine Kommentare

In Superhelden-Comics taucht immer wieder das selbe Motiv auf: ein Held oder eine Heldin mit Superkräften und einer oder mehreren Schwächen, wie z.B. Kryptonit. Die Schwäche ist wichtig, vermutlich wichtiger als die Kräfte, weil erst die Schwäche den Charakter interessant und zugänglich für uns macht. In unserer Realität kommt Macht so gut wie immer mit einem Preisschild. Wenn man manchmal über Node.js liest oder hört, kann man schon den Eindruck eines Superhelden bekommen. Doch all diese hoch gepriesenen „Kräfte“ haben ebenfalls ihren Preis, wir sind hier in unserer Realität, nicht wahr? In Comics sollten sowohl der Held bzw. die Heldin wie auch deren Unterstützer über die Schwächen Bescheid wissen, denn dieses Wissen kann über Erfolg oder den Tod entscheiden. In der Software Entwicklung ist es nicht anders, wir, die Entwickler sollten nicht nur die Fähigkeiten, sondern auch den Preis wissen. Lasst uns Node.js aus diesem Blickwinkel betrachten.

Zu Anfang, das offizielle Statement von der Node.js Webseite:

„Node.js is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.“

Es ist zweifellos auch Marketing, und doch ist es genau das, worum es sich bei Node.js dreht: Die Handhabe von vielen Netzwerkverbindungen und großen Datenmengen ohne dafür allzu viele Ressourcen zu brauchen oder unnötige Latenzen zu verursachen. Das sind die Fähigkeiten und Stärken. Ich möchte ab hier Node.js aus drei Blickwinkeln betrachten: als Technologie, als Plattform und als Ökosystem und Community.

Node.js als Technologie

Node.js entstand als eine neue Art um Webserver schnell und einfach zu entwickeln. Und „Server“ bzw. Netzwerkapplikation ist immer noch der Hauptfokus. Auch wenn wir mit Node.js inzwischen Werkzeuge für die Konsole oder Desktop Applikationen entwickeln und sogar Roboter kontrollieren können, nahezu jede Entwicklung in Node.js wurde gemacht um es besser im Erstellen und Betreiben von Servern zu machen. Und Node.js ist inzwischen sehr gut darin. Ein typischer Node.js Server hat einen geringen Speicherverbrauch, startet sehr schnell, handhabt Unmengen an simultanen Verbindungen in einem einzigen Prozess, während es die Ereignisschleife weiter und weiter dreht.

Was ist also der Preis dafür? Zuallererst, es ist ein einziger Prozess. Immer. Es nutzt immer nur einen CPU-Kern zur Zeit. Und wegen der V8, der JavaScript Umgebung, der maximal verfügbare Speicher pro Prozess ist begrenzt auf ein Maximum von 1,7 GB. Somit können wir nicht unbegrenzt vertikal skalieren, wie wir es z.B. mit der JVM könnten. Statt dessen müssen wir mehrere Prozesse, mehrere Instanzen der Anwendung starten. Wir müssen horizontal skalieren um eine Maschine auszulasten. Praktischerweise gibt es das „cluster“ Modul in Node.js genau zu diesem Zweck. Es erlaubt mehreren Node.js Prozessen Ressourcen, wie z.B. Sockets, gemeinsam zu nutzen und stellt einen IPC Kanal zwischen Master- und Worker-Prozessen her. Somit ist vertikale Skalierbarkeit zwar eine Schwäche, kann jedoch relativ problemlos mit horizontaler Skalierung aufgewogen werden.

Eine weitere Stärke von Node.js ist, dass es ermöglicht C und C++ Bibliotheken einzubinden und als JavaScript Modul zugänglich zu machen. Auf diese Weise können wir die Funktionalität aus diesem, viel älterem, Ökosystem nutzbar machen, um z.B. CPU-lastige Berechnungen ins C-Land auszulagern oder Threads nutzen, wenn wir es müssen. Auf der anderen Seite bekommen wir hier dieselben Probleme mit, die allen C oder C++ Projekten eigen ist: Manche Bibliotheken sind nicht auf jeder von Node.js unterstützen Plattform verfügbar. Doch es zeigt sich, dass dies ein eher geringes Problem ist, vor allem weil Server in der Regel in sehr stark spezifizierten und kontrollierten Umgebungen laufen und Plattformunabhängigkeit eher selten eine Anforderung ist.

Node.js als Plattform

Ich glaube die interessanteste Frage hier ist: Was gibt uns Node.js als Ausführungsumgebung? Hier sind, wie ich denke, die wichtigsten Punkte.

Als erstes, Node.js bringt ein eigenes, integriertes Modulsystem, das sich grob an dem CommonJS Standard orientiert. Damit begegnet Node.js einem der größten Nachteile von JavaScript, dem fehlen eines eigenen nativen Modulsystems. Node.js Module sind im Grunde Dateien, eine Datei ist ein Modul. Der Module-Loader funktioniert so, dass jedes Modul seine Abhängigkeiten exklusiv zur Verfügung hat, ohne dass dabei Konflikte mit anderen Modulen auftreten. Selbst wenn mehrere Module mehrere verschiedene Versionen des selben Moduls als Abhängigkeit haben, ist das kein Problem. Außerdem ist jedes Modul ein Singleton.

Dieses System ist sehr mächtig. Der Preis dafür ist zum einen größerer Speicherverbrauch auf der Festplatte, weil jede Abhängigkeit in jeder benötigten Version installiert wird. npm, der Paketmanager, kann mit dem Befehl dedupe dem etwas abhelfen. Ein anderer Preis der Mächtigkeit wird durch die Cache-Strategie des Loaders verursacht. Da der Cache die absoluten Pfade der Dateien als Cache-Schlüssel nutzt, werden zwei unterschiedliche Versionen eines Moduls aus den Abhängigkeiten getrennt von einander geladen. Normalerweise ist das gut und gewünscht, außer wenn das geladene Modul eine Ressource kapselt, die systemweit einmalig vorhanden sein soll. Um das zu erreichen müssen wir einen extra Schritt gehen und die Module mit Dependency Injection im Sinn gestalten. Außerdem ist dieses Szenario relativ selten, persönlich ist es mir so noch nicht begegnet.

Eine weitere Stärke von Node.js, ist die Abstraktion der Nebenläufigkeit. In Node.js ist jedes Stück Applikationscode praktisch nebenläufig. Doch wir müssen darüber nicht nachdenken. Die meiste Zeit spielt es kaum eine Rolle, weil jeder Teil des Applikationscodes exklusiv ausgeführt und nicht durch andere Teile unterbrochen wird. Die eigentliche Nebenläufigkeit wird durch die Ereignisschleife (engl. event loop) in einem einzigen Thread verwaltet. Das bedeutet nicht, wie oft fälschlich angenommen, dass Node.js keine Threads benutzt. Threads werden durch Node.js benutzt, aber es ist komplett abgekapselt von Nutzercode. Node.js befreit uns davon über Threads und Locking nachdenken zu müssen, dafür müssen wir in einem asynchronem, ereignisbasiertem Programmierparadigma denken. Es hat sich gezeigt, dass dieses Paradigma vielen von uns nicht behagt und wird oft als Nachteil angesehen. Es ist sicher eine Umstellung, obwohl wir diese Art der Programmierung schon länger in Form des Observer Pattern kennen und vor allem in der GUI Entwicklung einsetzen. Und doch scheint eine Funktion bzw. eine Closure als eine Variante des Observers zu obskur. Also wurden einige hilfreiche Bibliotheken entwickelt um dem abzuhelfen. Bekannteste Lösungen sind die „async“ Bibliothek, zahlreiche Promise (bzw. Future) Implementierungen, der streamline.js Präprozessor, und auch das „stream“ Modul, das der vermutlich am meisten unterschätzte Teil der Node.js API, kann dazu eingesetzt werden.

Die Node.js API ist aufs Wesentliche reduziert und gilt für viele deshalb als eine weitere Stärke. Node.js soll uns helfen und sonst aus dem Weg sein. Und das tut es meistens. Damit das so bleibt, wurden viele Anfragen für neue Funktionalität von den Kernentwicklern von Node.js abgelehnt. Sie empfehlen statt dessen diese Funktionalität als Drittmodule zu veröffentlichen. Es ist sehr einfach für die Community solches zu tun, wie man auch an dem Wachstum des Ökosystems sehen kann. Und deshalb gibt es dort große Vielfalt. Das ist gut und gleichzeitig eine Belastung. In Java sind die JSRs und API Spezifikationen der normative Faktor, der das Zusammenspiel einzelner Implementierungen definiert. In Node.js gibt es eine solche ordnende Instanz nicht. Die Community wählt mit ihren Füßen, so dass meist erst mit der Zeit sich einige wenige Lösungen und Vorgehen herauskristallisieren.

Nun zum letzten Punkt bezüglich Node.js als Plattform und dieser dürfte kontrovers sein. JavaScript ist eine Stärke dieser Plattform. Es ist eine der am weitesten genutzten, geliebten und gehassten Programmiersprachen heute. Trotz ihrer Macken ist sie eine kompakte und mächtige Sprache. Sie macht es für Frontend-Entwickler einfacher in die Serverentwicklung zu kommen. Tatsächlich sind viele produktive und respektierte Mitglieder der Community entweder Full-Stack-Webentwickler oder gar reine Frontend-Spezialisten. Auch können viele Bibliotheken, die ursprünglich für Webbrowser entwickelt wurden auch direkt in Node.js benutzt werden. JavaScript selbst erlaubt es, viele Konzepte sehr elegant und ausdrucksvoll zu implementieren. Und doch hat die Sprache ihre Macken, die nicht jeder kennt und zu umgehen weiß, auch kann statische Typisierung in großen Teams helfen. Und manche von uns können sich OO ohne Klassen einfach nicht vorstellen. Für solche Fälle gibt es TypeScript, CoffeeScript, Googles Closure Tools oder auch JSHint.

Node.js als Ökosystem und Community

Es ist die Community, die vermutlich die allergrößte Stärke von Node.js ist. Es gibt andere Technologien für asynchrone Ein/Ausgabe-Verarbeitung, andere Wege JavaScript auf dem Server einzusetzen. Doch keine diese Alternativen hat eine so große, rapide wachsende und engagierte Community. Das ist so, weil die API und das Ökosystem von Node.js es leicht machen schon mit wenig Aufwand etwas zu erschaffen.

Node.js ohne npm macht wenig Sinn, deshalb werden sie in Installationspaketen zusammengepackt. Es ist npm, das die Infrastruktur für das Ökosystem schuf, wie wir es heute kennen. Jede/r kann mit npm ihr/sein Modul im Nu öffentlich machen. Der npm Client bringt einige mächtige Funktionen mit und kann sogar in kleineren Projekten ein dediziertes Build-Tool ersetzen. Dieser Paketmanager ist einfach und praktisch. Der andere Teil von nmp ist das Repository, das ursprünglich als ein dünner Wrapper um ein CouchDB Cluster gebaut wurde. Mit diesem Aufbau war npm lange Zeit ein für die Community sehr nützliches Werkzeug. Leider wurden Anforderungen, die für Unternehmen interessant sind, in dieser Zeit weitgehend vernachlässigt. Auch schaffte das Repository es nicht mit dem Rest des Ökosystems zu skalieren. Doch das wird sich nun ändern. Das vor kurzem gegründete npm, Inc. soll nun die Weiterentwicklung von npm sichern und vor allem das Repository stabil und skalierbar machen. Zusätzlich sind einige Funktionen und Dienste geplant, die speziell für Unternehmen interessant sind, wie zum Beispiel private Module oder private Proxy-Repositories.

Bislang gibt es weder in npm noch im Node.js Modulsystem explizite Namensräume, es gab keinen technischen Bedarf dafür. Durch Präfixe simulierte Namensräume, wie zum Beispiel „connect-json“ oder „grunt-cli“, waren für die meisten Entwickler ausreichend. Nun, mit kommenden Funktionen und Diensten wie privaten Repositories und privaten Modulen in öffentlichen Repositories, wird dieses Thema erst richtig aktuell. Und weil es npm ist, das Repositories und Module darin verwaltet, wird dort dieses Thema behandelt. Dann wird es notwendig sein bestimmte Repositories angeben zu können, um ganz bestimmte Module als Abhängigkeiten zu referenzieren. Auch dies wird kommen.

Mit Hilfe von npm haben viele Entwickler noch viel mehr Module veröffentlicht, zur diesem Zeitpunkt über 78k. Wenn man irgendeine Funktionalität braucht, dann ist die Wahrscheinlichkeit hoch, dass ein passendes Modul in npm zu finden ist. Nur muss man es erst einmal finden. Der Preis der schieren Menge und der Vielfalt der Modullandschaft ist schlechte Auffindbarkeit. Auch wenn npm, Inc. daran arbeitet und auch wenn es weitere alternative Möglichkeiten zur Suche gibt, liegt es an der Community selbst hier zu helfen. Auch die Modulentwickler selbst können einiges zur Verbesserung beitragen, angefangen mit wohl gewählten Schlüsselworten und guter Readme-Datei bis hin zum Aufbau einer eigenen, kleinen Teil-Community, die bei der Verbreitung hilft.

Der weitere Preis des extremen Wachstums und hoher Vielfalt sowohl der Modullandschaft, als auch der Community selbst, ist die Tatsache, dass kaum echte Qualitätsstandards oder sinnvolle Auswahlkriterien existieren, die einem die Bewertung von Modulen erleichtern. Ein paar wenige Best Practices und der gesunde Menschenverstand ist oft alles, was uns übrigbleibt. Das mag für Freizeitprojekte oder Start-Ups reichen, doch nicht für Unternehmen. Die Community muss, und wie ich glaube, wird Qualitätsstandards für auf npm veröffentlichte Module definieren müssen, um diese zumindest etwas Vergleichbar zu machen. Dabei würden schon elementare Kriterien wie Lizenz, das Vorhandensein von Tests, Dokumentation und Release Notes helfen.

Fazit

Node.js erfuhr viel Förderung und Akzeptanz, sowohl unter Freizeitentwicklern und Start-Ups, als auch bei einigen großen Unternehmen. Auch positive Berichterstattung gab es reichlich. Obwohl all diese Liebe sicher nicht unverdient ist, sollten wir nicht vergessen, dass Node.js trotzdem kein Superheld ist, wie es manchmal scheint. Es hat seine Schwächen und Tücken, sei es das Programmierparadigma oder das vergleichsweise noch recht geringes Alter der Community. Doch wenn wir um die Schwächen wissen, können wir diese Technologie objektiver betrachten und besser darüber Urteilen, wann und wie wir sie einsetzen können und sollten. Dann ist Node.js ein weiteres Werkzeug in unserer Kiste, eines, das in seiner Domäne sehr gut ist.

Gregor Elke spezialisiert sich auf Themen rund um JavaScript-Entwicklung. Ob verteilte Services mit Node.js oder Web-Frontends mit Angular, React oder ganz ohne, JavaScript hält überall Einzug.
Er teilt seine Begeisterung für JavaScript gerne mit anderen, daher gibt er gerne Schulungen und Coachings in diesen Themenbereichen.
Gregor Elke organisiert auch die Hamburger Node.js User Group.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.