Web

Web-Projekte schnell entwickeln

Node.js im Projektumfeld

19.08.2013
Von 
Sebastian Springer arbeitet seit mehreren Jahren beim PHP-Dienstleister Mayflower in München und ist dort derzeit als Projekt-und Teamleiter tätig. Dabei liegt sein Fokus auf der Entwicklung und Qualitätssicherung von dynamischen Webapplikationen mit JavaScript und PHP.
Für schnelle Web-Projekte und Echtzeitanwendungen lohnt es sich, einen Blick auf Node.js zu werfen. Die Entwicklungs-Plattform macht JavaScript auch auf dem Server lauffähig und lässt sich dank der offenen Architektur und einer engagierten Community fast endlos erweitern.

Eine wichtige Bemerkung vorweg: Node.js ist kein Web-Framework wie beispielsweise Ruby on Rails oder Symfony2 - Node.js ist eine Plattform. Warum jedoch ist eine Unterscheidung so wichtig und an welchen Eigenschaften lässt sich diese festmachen? Bei den beiden erstgenannten Frameworks handelt es sich um vollwertige Web-Application-Frameworks. Als solche sind sie auf die Entwicklung von Webapplikationen spezialisiert und bieten eine große Fülle an Funktionalitäten, Helfern und Werkzeugen. Sie nehmen einem Entwickler zahlreiche Aufgaben ab und sorgen so dafür, dass er eine Web-Applikation schnell, stabil und sicher aus der Taufe heben kann.

Node.js macht JavaScript auf dem Server lauffähig.
Node.js macht JavaScript auf dem Server lauffähig.
Foto: Joyent Inc., Node.js

Node.js ist hingegen eine Plattform, mit der die ursprünglich clientseitige Sprache JavaScript auch serverseitig verwendet werden kann. Node.js wurde entwickelt, weil viele Features, die in anderen Sprachen bereits implementiert waren, nach Meinung von Ryan Dahl, dem Entwickler von Node.js, zu viele Limitierungen bieten. Ein Beispiel ist der Zugriff auf das Dateisystem - hier macht das serverseitig nicht verbreitete JavaScript keine Vorgaben und eröffnet die Möglichkeit zur eigenen Weiterentwicklung. Dahl verband die offene V8-Engine von Google Chrome mit verschiedenen Bibliotheken wie dem Eventloop libev, um JavaScript auch serverseitig einsetzen zu können.

Aufbau von Node.js

Node.js ist modular aufgebaut. Den Kern der Plattform bildet die als Open Source verfügbare JavaScript-Engine V8. Diese interpretiert JavaScript-Code und führt ihn auf eine äußerst effiziente Weise aus. Ursprünglich sollte die V8-Engine lediglich im Browser ihren Dienst verrichten, bietet also aufgrund des Sandbox-Konzepts der Browser kaum Schnittstellen zum Betriebssystem - vor allem nicht, wenn es um die Ein- und Ausgabe geht. Deshalb ergänzen verschiedene Bibliotheken wie libev und libeio die V8-Engine und erweitern ihre Möglichkeiten um asynchrone Ein- und Ausgabeoperationen. Einen wichtigen Wendepunkt in der Entwicklung von Node.js markierte die Integration von libuv, einer Abstraktionsbibliothek für die asynchrone Ein- und Ausgabe. libuv ist seit der Version 0.6 in Node.js enthalten und ermöglicht es, dass unter Windows IOCP statt libev und libeio für die asynchrone Ein- und Ausgabe zum Einsatz kommt. Seit dieser Version ist Node.js damit auch auf Windows-Plattformen verfügbar.

Auf den C-Bibliotheken bauen die sogenannten Node Bindings auf. Diese stellen das Verbindungsstück zwischen dem C-Code und JavaScript dar und bilden somit die API für die Module von Node.js. Diese Module liegen im Quellcode von Node.js als reguläre JavaScript-Dateien vor. Das bedeutet, dass sich der gesamte Quellcode ohne Einschränkung mit einem normalen Texteditor lesen lässt. Nach der Kompilierung des Codes werden die Module zum festen Bestandteil der Plattform. Die Node.js-Module enthalten lediglich einen Grundstock an Funktionalität, mit dessen Hilfe sich Webapplikationen entwickeln lassen. Dazu gehören der HTTP-Server oder die Schnittstellen, mit denen ein Zugriff auf das Dateisystem möglich wird. Dieser Kern wird bewusst klein gehalten, damit Node.js leicht und flexibel bleibt. Daher eignet sich die Plattform besonders gut für schnelle Web-Projekte wie Webservices und Echtzeitanwendungen, die über Websockets mit einem Browser kommunizieren.

Node Packaged Modules

Die Node.js-Plattform selbst stellt nur die grundlegenden Werkzeuge für die Entwicklung von Webapplikationen zur Verfügung - wer mehr braucht, nutzt die Node Packaged Modules. Das sind Module, die von Drittanbietern gepflegt werden und mit dem Node Package Manager, kurz NPM, verwaltet werden. Die Pakete werden in einem zentralen Repository vorgehalten, auf das der NPM über HTTP zugreift. Aktuell sind mehr als 35.000 Pakete verfügbar - wer selbst etwas veröffentlichen möchte, benötigt lediglich eine E-Mail-Adresse und ein Passwort, das sich per Kommandozeile erstellen lässt. Im Repository findet sich beispielsweise express, ein Framework zur Entwicklung von Webapplikationen. Außerdem existieren zahlreiche Parser, die Formate wie XML oder YAML in JavaScript-Datenstrukturen umwandeln und sie so für eine Node.js-Applikation verfügbar machen. Eine weitere bedeutende Gruppe von Modulen im Repository sind die Datenbanktreiber, da Node.js den Zugriff auf Datenbanken nicht von Haus aus unterstützt. Über den NPM sind beispielsweise Treiber für MySQL, SQLite, Redis oder MongoDB verfügbar.

Wie die Einbindung von NPM-Modulen aussehen kann, zeigt dieses Quellcode-Beispiel:

var express = require('express');
var router = require('./app/router.js'):
var app = express();
app.set('views', __dirname + '/app/views');
app.set('view engine', 'jade');
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.cookieSession({secret: 'mySecret'}));
app.use(express.logger());
app.use('/js', express.static(__dirname + '/public/js'));
app.get('/', router.login);
app.listen(8080);

Eventbasierte Architektur

Bei der Entwicklung von Applikationen verfolgt Node.js einen Single-Threaded-Ansatz. Das bedeutet, dass Node.js lediglich einen Thread zur Ausführung einer Applikation zur Verfügung stellt. Heißt aber auch, dass die Ausführung einer Applikation von einer bereits laufenden Operation komplett blockiert wird. Um dieses Problem zu entschärfen, lagert Node.js sämtliche Operationen bis auf den eigentlichen Applikationscode in einen eigenen Thread aus. Hier kommen weitere Kernkomponenten der Node.js-Plattform zum Einsatz. Greifen Anwender beispielsweise aus einer Applikation heraus auf das Dateisystem des Servers zu, sendet Node.js diese Anfrage an das Betriebssystem. Oder es lässt sich eine Callback-Funktion definieren, die ausgeführt wird, sobald die Zugriffsoperation beendet ist. Intern wird der Eventloop von Node.js verwendet, um die Callback-Funktion auszuführen, sobald das Ergebnis vorliegt.

Der Vorteil der Entkopplung liegt darin, dass die Applikation auf die Erledigung einer Operation wartet. Dieses Warten verbraucht keinerlei Ressourcen und das System kann in der Zwischenzeit weitere Anfragen beantworten. Diese Art der Entwicklung kann auch innerhalb einer Applikation eingesetzt werden, um rechenintensive Aufgaben in separate Childprozesse auslagern.

Ein weiterer Vorteil des Single-Threaded-Ansatzes liegt darin, dass sich Entwickler nicht um die Probleme der parallelen Programmierung wie den gleichzeitigen Zugriff auf Ressourcen kümmern müssen. So wird die Erstellung von Applikationen erheblich einfacher.

Skalierbarkeit

Auch Business-Entwickler können Node.js durchaus gebrauchen.
Auch Business-Entwickler können Node.js durchaus gebrauchen.
Foto: Fotolia.de/Privilege

Mit Node.js lassen sich nicht nur kleine Webapplikationen oder Werkzeuge umsetzen, sondern auch komplette Business-Anwendungen, die Anfragen von mehreren tausend Nutzern gleichzeitig bearbeiten müssen. Neben der großen Anzahl an unterstützenden Entwicklungspaketen kommt hier die Tatsache zum Tragen, dass sich eine Node.js-Anwendung - wie eine Applikation, die auf anderen Plattformen entwickelt wurde - auch skalieren lässt. Skalierung bedeutet in diesem Fall, dass sich der Infrastruktur, auf der die Applikation betrieben wird, eine weitere Rechnerinstanz, also einen Server, hinzufügen lässt.

Damit eine solche Skalierung funktionieren kann, muss eine Applikation bestimmte Voraussetzungen erfüllen. Es dürfen beispielsweise keine limitierenden Komponenten existieren. Ein konkretes Beispiel sind Session-Daten, die im Arbeitsspeicher eines Servers vorgehalten werden. Eine Session bezeichnet dabei eine aktive Sitzung zwischen einem Client und dem Server, in der beispielsweise Anmeldeinformationen vorgehalten werden, um den Benutzer der Session zu identifizieren. Liegen die Daten einer Session im Arbeitsspeicher eines bestimmten Servers, ist es nicht möglich, eine Anfrage, die einer bestimmten Session zugeordnet ist, an einen anderen Server im Verbund weiterzuleiten. Derartige Probleme lassen sich lösen, indem die Session-Daten auf einem zentralen Server vorgehalten werden. Ähnliches gilt für Datenbanken. Auch hier benötigen Entwickler einen oder mehrere zentrale Datenbankserver, auf die die einzelnen Server zugreifen, und nicht verteilte lokale Datenbanken auf den unterschiedlichen Servern, die erst aufwändig synchronisiert werden müssen.

Zwischen den Benutzern und den Servern liegt bei einer solchen Architektur meist ein Load-Balancer. Aufgabe dieses Rechners ist es, die Anfragen der Benutzer mithilfe eines bestimmten Algorithmus‘ an die verfügbaren Server zu verteilen. Mögliche Ziele einer solchen Struktur sind die Ausfallsicherheit und damit eine hohe Verfügbarkeit des Gesamtsystems oder eine gleichmäßige Verteilung der Last durch die eingehenden Anfragen.

Sicherheit und Qualität

Wenn es um die Umsetzung von geschäftskritischen Anwendungen geht, kommen die Aspekte Sicherheit und Qualität ins Spiel. Auch in diesem Bereich ist die Node.js-Plattform gut aufgestellt - selbst ohne NPM-Pakete.

So verfügt Node.js beispielsweise mit dem Assert-Modul über ein eigenes Testframework zum Testen von Applikationscode. Wer mehr als die Grundfunktionen benötigt, findet umfangreichere Lösungen wie beispielsweise nodeunit als NPM-Pakete. Auch wenn es um die Sicherheit von Anwendungen geht, nimmt Node.js im Kern nur die grundlegenden Arbeiten ab. Ähnliches gilt allerdings auch für andere Plattformen im Web wie beispielsweise PHP. Typische Angriffsvektoren wie Injections, CSS oder XSRF lassen sich auch bei Node.js erst durch die Verwendung bestehender NPM-Pakete absichern. (sh)