Nodyn: Ein Node.js-kompatibles Framework für die JVM

Niko Köbler

© Shutterstock/a-image

Neben Avatar aus dem Hause Oracle gibt es eine weitere Node.js-Alternative für die JVM: Im Red-Hat-Team von Project Odd wird unter dem Namen „Nodyn“ ebenfalls an einer Node.js-kompatiblen Lösung gearbeitet. Wir wollen hier das Nodyn-Projekt einmal genauer unter die Lupe nehmen.

Nodyn ist nicht einfach nur ein Abklatsch von Node.js, sondern vereint mit dem Node-API zusätzlich auch noch Netty und Vert.x miteinander, um den größtmöglichen Vorteil für die asynchrone Netzwerk- und Request-Verarbeitung zu bieten. Nodyn läuft zwar, wie Avatar, auf der JVM, nutzt im Gegensatz zu Avatar aber (noch) nicht die Nashorn JavaScript Engine der JVM, sondern setzt auf die JS-Engine DynJS. Diese wird, wie Nodyn, vom Projekt Odd entwickelt. Project Odd ist eine Forschungsabteilung bei Red Hat, die sich vornehmlich mit dynamischen Sprachen auf der JVM beschäftigt. So stammt u. a. JRuby von diesem Team, außerdem TorqueBox (Ruby Gems für Wildfly) – und eben auch DynJS.

Ähnlich wie Nashorn bringt DynJS ein Kommandozeileninterface mit, das es ermöglicht, JavaScript-Operationen direkt oder auch in JS-Dateien enthaltenen Code auszuführen. Für Java-Anwendungen kann die Script Engine über bekannte Dependency-Mechanismen oder auch direkt in den Klassenpfad geladen und somit im Java-Code verwendet werden. Auch hier können einzelne JS-Befehle oder ganze Dateien geladen und ausgeführt werden. Die Syntax unterscheidet sich naturgemäß von der von Nashorn. Aber auch mit DynJS ist es möglich, Java- und JavaScript-Code zu vermischen. Mehr Informationen zu DynJS und warum Douglas Campos es entwickelt hat, liefert ein Interview [1] mit dem Gründer von DynJS.

Etwas verwirrend und umständlich ist noch die Tatsache, dass hier zwar mit DynJS entwickelt, aber Vert.x einbezogen wird, das (noch) Rhino verwendet – und das, obwohl auf der JVM Nashorn zur Verfügung steht, zu dem auch die Vert.x-Gemeinde mittlerweile tendiert. Tim Fox hat dies in einem GitHub-Issue bereits angesprochen, und es gibt Bestrebungen, Nodyn auch den Betrieb mit Nashorn zu ermöglichen. Diese Entwicklung ist sehr zu begrüßen, ist Nashorn doch die Script Engine, die am stärksten weiterentwickelt wird: Für sie werden nicht nur Ecma-Script-6-(ES6-)Features implementiert – voraussichtlich für Java 9 –, sondern es werden mit dem nächsten Java-Update (Release 8u40) auch große Performanceverbesserungen kommen.

API-Geflüster

Ursprünglich wollte man mit Nodyn das gesamte öffentliche API von Node.js nachprogrammieren. Da dieses API aber schlecht bis gar nicht dokumentiert ist, entschloss man sich nach einiger Recherchearbeit, die JavaScript-Teile von Node.js wiederzuverwenden und nur den C++-Part mit Java- und JavaScript-Code zu ersetzen. Laut Nodyn-Blog ist somit mehr JavaScript-Code in Nodyn enthalten als in Node.js selbst.

Die unausweichliche Frage nach dem Warum eines Node.js-Klons auf der Java Virtual Machine beantwortet das Nodyn-Team auch gleich im selben Blogeintrag. Ähnlich der Argumentation im Avatar-Artikel, dass bestehende JVM-Infrastrukturen weitergenutzt, aber dennoch Node-basierte Applikationen eingesetzt werden sollen, ebnet Nodyn hier den Weg. Noch ist es zwar nicht möglich, mehrere Nodyn-Prozesse innerhalb eines JVM-Prozesses laufen zu lassen, aber es ist ein erklärtes Ziel des Teams, dies in Zukunft ebenfalls möglich zu machen.

Der GitHub-Wiki Eintrag zur Roadmap zeigt, was Red Hat mit Nodyn noch alles vorhat. Eine 1.0-Release-Verfügbarkeit bis Ende 2014 ist zum Zeitpunkt des Redaktionsschlusses zwar noch nicht in Sicht, aber die API-Kompatibilitätsübersicht lässt mittlerweile keine größeren Löcher mehr erkennen. Eine Übersicht, welche NPM-Module bereits lauffähig sind, gibt es noch nicht, aber viele Module funktionieren bereits. Möchte man Nodyn mit einem Modul einsetzen, das noch nicht funktioniert, dann schreibt man einfach eine kurze Nachricht auf die Mailingliste, um das Team darüber zu informieren. Meist bekommt man nach kurzer Zeit (teilweise sogar am Wochenende) eine Antwort, was zu tun ist oder dass ein Fix vorgenommen wird. Ein toller Service des Projektteams – davon kann sich manch anderer großer Hersteller eine Scheibe abschneiden. Ein Test mit einem (zugegebenermaßen sehr) einfachen Expressprogramm (Listing 1), hat auf Anhieb funktioniert, ohne dass Anpassungen notwendig waren.

var express = require('express');
var app = express();

// respond with "Hello World!" on the homepage
app.get('/', function (req, res) {
  res.send('

Hello JavaScript!

‚); }); // accept POST request on the homepage app.post(‚/‘, function (req, res) { res.send(‚

Got a POST request.

‚); }); // accept GET request at /user app.get(‚/user‘, function (req, res) { res.send(‚

Hello John Doe!

‚); }); // start the server var server = app.listen(3000, function () { var host = server.address().address; var port = server.address().port; console.log(‚Example app listening at http://%s:%s‘, host, port); });

Für die Integration mit Enterprise-Technologien plant man NPM-Module für den Einsatz mit JMS und der JBoss-Cache-Lösung Infinispan. Anders als Avatar, das den Weg wieder weg vom Application Server gegangen ist, denkt man bei Nodyn darüber nach, Nodyn-Applikationen auch innerhalb eines WildFly oder EAP laufen lassen zu können, um auf die Cluster-Fähigkeiten des Containers zurückgreifen zu können.

Nodyn und Vert.x

Mancher Leser fragt sich bei der Vereinigung von Node.js und Vert.x vielleicht, warum man das überhaupt macht. Schließlich sind sich Node.js und Vert.x insoweit ähnlich, als sie beide ein asynchrones und nichtblockierendes Programmiermodell unterstützen, Vert.x zudem polyglott ist und u. a. auch JavaScript ausführen kann. Vert.x hat aber noch eine Komponente, die im Node-API nicht enthalten ist, mit Nodyn und einem weiteren NPM-Modul aber auch der Node-Gemeinde zugänglich gemacht werden kann: den Event Bus. Letzterer erlaubt es einem Modul, sich mit Handlern auf beliebigen Adressen zu registrieren und damit auf eingehende Nachrichten (von anderen Modulen) zu reagieren. Optional können diese Handler auf Nachrichten zum ursprünglichen Absender antworten. Das alles natürlich asynchron.

Das NPM-Modul vertx2-core übernimmt die Aufgabe, das in Nodyn intern enthaltene Vert.x für die Verwendung in Applikationen zur Verfügung zu stellen:

var vertx = require('vertx2-core')

Von hier aus kann der Event Bus angesprochen werden, sowohl als Sender als auch als Empfänger:

var registration = vertx.eventbus.register('myAddress', function(msg) {
    // handle incoming message...
});

vertx.eventbus.send('myAddress', ...);

Da, wie oben bereits erwähnt, Vert.x polyglott ist, also mehrere Programmiersprachen von Hause aus unterstützt und zudem noch über leistungsfähige Cluster-Fähigkeiten verfügt, um viele Knoten und Event Bus Listener über mehrere physische Maschinen zu spannen, kann so eine große anfallende Last sehr einfach verteilt werden. Weiter wird es damit auch möglich, dass Node-Programme über den Event Bus mit anderen Vert.x-Instanzen und -Applikationen kommunizieren können, ganz egal, ob diese in Java, JavaScript oder sogar Groovy, Clojure oder Scala geschrieben sind.

In Listing 2 findet sich eine Beispielapplikation, mit der wir in der Lage sind, über den Webbrowser eine bestimmte Menge an Bier zu bestellen.

var http = require("http");
var vertx = require("vertx2-core");

var registration = vertx.eventbus.register("bar", function(message) {
  var amount = message.body.amount;
  console.log("BAR: Someone ordered " + amount + " beer.");
  message.reply({wait_time: amount * 1.75});
});

var server = http.createServer(function(request, response) {
  var url = request.url;
  var parts = url.split("/");
  var amount = parts[1];

  if (!isNaN(amount)) {
    vertx.eventbus.send("bar", {amount: amount}, function(message) {
      response.write(amount + " beer will be ready in " + message.body.wait_time + " minutes");
      response.end();
    });
  }
});

server.listen(9000, function() {
  console.log( "Beer-Server is listening on port 9000" );
});

Als Antwort bekommen wir die Zeit zurückgeliefert, die es dauert, bis alle Biere fertiggezapft sind. Hierfür implementieren wir folgende Schritte:

Die Bierbar registriert einen Vert.x Event Bus Listener auf der Adresse bar. Dieser Listener wartet auf eingehende Nachrichten, berechnet aus dem amount-Feld (Menge), wie lange es dauert, bis die Biere gezapft sind, und schickt diesen Wert als Antwort an den Absender zurück. Zusätzlich wird die bestellte Menge als Information für die Bar auf der Konsole ausgegeben.

Einen Standard-Node-HTTP-Server erzeugen wir auf Port 9000. Der Node HTTP Request Handler sendet bei einem eingehenden Request eine Nachricht mit der bestellten Menge auf dem Vert.x Event Bus mit der Adresse bar. Gleichzeitig wird eine Callback-Funktion mitgegeben, die die (asynchrone) Antwort der Bar entgegennimmt. Wenn die Antwort der Bar angekommen ist, wird diese Information als Response an den ursprünglich aufrufenden Webclient zurückgegeben. Die Anwendung starten wir auf der Konsole mit dem Befehl

$ nodyn beer_bar.js

Unmittelbar danach sehen wir dort die Ausgabe

Beer-Server listening on port 9000

Mit einem Browser rufen wir nun folgenden URL auf, um fünf Biere (für uns und unsere Freunde) zu bestellen:

http://localhost:9000/5

Als Antwort erhalten wir im Browser

5 beer will be ready in 8.75 minutes

Zusätzlich erhält die Bar auf der Serverkonsole die Ausgabe:

BAR: Someone ordered 5 beer.

Vert.x und Nodyn

Das war ein einfaches Beispiel, den Vert.x Event Bus in unsere Node-Anwendung zu integrieren. Das Ganze funktioniert aber auch andersherum, indem wir unsere Node-Anwendung innerhalb einer Vert.x-Instanz ausführen und hier mit einer reinen, eventuell bereits bestehenden, Vert.x-Komponente über den Event Bus kommunizieren.

Hierfür kommt das Vert.x-Modul mod-nodyn zum Einsatz. Vert.x-Module sind wiederverwendbare Komponenten, ähnlich wie NPM-Module. Das mod-nodyn-Modul macht Nodyn innerhalb von Vert.x verfügbar, nutzt für den Ablauf den in Vert.x bereits vorhandenen Event Loop und sorgt so dafür, dass alle Komponenten reibungslos zusammenarbeiten können.

Ausgehend von unserem ersten Beispiel in Listing 2 erweitern wir den Anwendungsfall und teilen ihn nun in zwei Vert.x-Prozesse auf, die in einem Cluster laufen. Dazu teilen wir auch den Code in zwei unabhängige Teile auf. Die Node-Webapplikation beer-web.js in Listing 3 sieht ähnlich wie das erste Beispiel aus, allerdings ist der Teil der Zeitberechnung, wie lange es dauert, die bestellten Biere zu zapfen, nicht mehr enthalten. Immer noch enthalten ist das vertx2-core-NPM-Modul, um aus der Nodyn-Anwendung weiterhin mit dem Vert.x Event Bus kommunizieren zu können. Auch wenn die  Node-Anwendung nun in Vert.x ausgeführt wird, benötigen wir dieses Modul weiterhin, um auf den Event Bus zugreifen zu können.

var http = require("http");
var vertx = require("vertx2-core");

var server = http.createServer(function(request, response) {
  var url = request.url;
  var parts = url.split("/");
  var amount = parts[1];
  
  if (!isNaN(amount)) {
    vertx.eventbus.send("bar", {amount: amount}, function(message) {
    response.write(amount + " beer will be ready in " + message.body.wait_time + " minutes");
      response.end();
    });
  }
});

server.listen(9000, function() {
  console.log( "Beer-Server is listening on port 9000" );
});

Das Vert.x Verticle beer-bar.js wird auch in JavaScript geschrieben, nutzt aber das Vert.x-JavaScript-API, das nicht mit dem Node-API kompatibel ist, siehe Listing 4. Das Verticle registriert, wie bereits im ersten Beispiel, einfach einen Listener auf der bar-Adresse und wartet auf eingehende Nachrichten, um daraufhin die Zeit zu berechnen und auf die Nachricht zu antworten.

var eventBus = require("vertx/event_bus");

eventBus.registerHandler("bar", function(message, replier) {
  java.lang.System.err.println("BAR: Someone ordered " + message.amount + " beer");
  replier({wait_time: message.amount * 1.75});
});

java.lang.System.err.println("The BAR is open!");

Beide Komponenten lassen wir nun in einem Vert.x-Cluster laufen. Vert.x-Cluster unterscheiden sich insofern von einem Node.js-Cluster, als jede Komponente nach wie vor völlig unabhängig von anderen Komponenten läuft und nur der Event Bus über alle Cluster-Knoten gespannt wird. So ist es möglich, über den Event Bus alle Vert.x-Komponenten miteinander kommunizieren zu lassen. Vert.x-Cluster lassen sich sehr einfach starten, indem auf der Kommandozeile einfach die –cluster-Option mit angegeben wird. Das Finden und die Kommunikation der Cluster-Mitglieder untereinander geschehen dank multicast automatisch und problemlos. Vert.x bedient sich hier Hazelcast [15], das das gesamte Cluster-Management übernimmt und für alle Umgebungen konfiguriert und angepasst werden kann.

Damit wir nun unsere beer-web.js Komponente innerhalb von Vert.x ausführen können, muss Vert.x mit dem mod-nodyn-Modul gestartet und diesem eine Konfiguration mitgegeben werden, wo die Node-Applikation zu finden ist. Das geschieht mit dem Konfigurationsobjekt:

{
  "main": "baas_web.js"
}

Da wir nun sozusagen ein Beer-as-a-Service haben, nennen wir die Datei auch baas.conf. Auf der Kommandozeile sieht das dann folgendermaßen aus:

$ vertx runmod io.nodyn.vertx~mod-nodyn~1.0.0-SNAPSHOT -conf baas.conf -cluster

Die Konsolenausgabe (mit leicht erweitertem Hazelcast-Logging gegenüber der Default-Einstellung) ist in Abbildung 1 zu sehen: Ein einzelner Vert.x-Prozess wird in einem Cluster-Knoten ausgeführt.

Abb. 1: Ein einzelner Vert.x-Prozess wird in einem Cluster ausgeführt

Abb. 1: Ein einzelner Vert.x-Prozess wird in einem Cluster ausgeführt

Spannend wird es nun, wenn wir beer-bar.js als natives Vert.x Verticle, ebenfalls im Cluster, in einem zweiten Kommandozeilenfenster starten:

$ vertx run beer-bar.js -cluster

Die Konsolenausgabe für diesen Prozess ist in Abbildung 2 zu sehen: Der zweite Vert.x-Prozess wurde gestartet, hat automatisch den ersten gefunden und sich mit diesem zu einem Cluster verbunden. Genau das, was wir haben wollten.

Abb. 2: Der zweite Vert.x-Prozess wurde gestartet

Abb. 2: Der zweite Vert.x-Prozess wurde gestartet

Nun rufen wir im Browser wieder die bekannte Adresse auf, um einige Biere (es sind noch ein paar Freunde hinzugekommen) zu bestellen:

http://localhost:9000/13

Wir bekommen als Antwort:

13 beer will be ready in 22.75 minutes

Rechnerisch richtig, auch wenn die Wartezeit vielleicht etwas lang ist. Hier sollte die Bar einmal an den internen Prozessen arbeiten und mehr Zapfstellen parallel anbieten. Für die Bar wird auf der (zweiten) Konsole die Bestellung ausgegeben:

BAR: Someone ordered 13 beer

Alle hier gezeigten Beispiele finden sich auch hier.

Debugging

Im Node.js-Ökosystem gibt es mit dem NPM-Modul node-inspector eine Remote-Debugging-Konsole, die es dem Entwickler ermöglicht, auch den serverseitigen Code mit dem gleichen Komfort zu debuggen wie auf der Clientseite. Node-Inspector baut dazu im Browser ein Frontend auf, das die gleichen Werkzeuge bietet wie die Dev-Tools des Google Chrome Browsers. Breakpoints setzen, Ausdrücke überwachen, Callstack einsehen, all das und vieles mehr ist möglich.

Auch mit Nodyn lässt sich der Node-Inspector nutzen. Hierzu muss eine Nodyn-App lediglich im Debug-Modus gestartet werden:

$ nodyn debug app.js

Auf der Kommandozeile ist dann zu sehen, dass die Anwendung den (Standard-)Debug-Port 5858 gestartet hat und dort Verbindungen von einem Debugger erwartet. Node-Inspector nutzt ebenfalls diesen Standard-Port und verbindet sich beim Start dorthin. Ab jetzt kann die Anwendung auch auf der Serverseite debuggt werden.

Fazit und Ausblick

Nodyn bietet zum Einen eine reine Node-API-Implementierung auf der JVM, die es sich als Ziel gesetzt hat, es uns zu ermöglichen, Node.js-Anwendungen direkt in Nodyn auf der JVM ablaufen zu lassen. Zusammen mit dem NPM-Modul vertx2-core kann der Event Bus von Vert.x innerhalb einer solchen Node-Anwendung angesprochen und verwendet werden. So wird ein Nodyn-Programm alleine schon Cluster-kommunikationsfähig.

Mithilfe des Vert.x-Moduls mod-nodyn wird es dann sogar möglich, Node.js-Programme direkt in Vert.x auszuführen, ohne dass hierfür Änderungen am Programmcode notwendig werden. Zusammen mit der Integration des Vert.x Event Bus kann dann eine Nodyn-Anwendung im Cluster auch mit anderen Vert.x Verticals kommunizieren und Daten austauschen. Dabei spielt es keine Rolle, ob diese Verticals auch in JavaScript, Java, Groovy, Scala oder einer sonstigen Sprache geschrieben sind, die Vert.x unterstützt und die auf der JVM ausgeführt werden kann.

Die Nutzung eines Clusters wird dank des Einsatzes von Hazelcast in Vert.x zum Kinderspiel. Kann Multicast verwendet werden, finden sich die beteiligten Knoten selbst und verbinden sich zu einen gemeinsamen Cluster. Für alle anderen Umgebungen kann Hazelcast entsprechend konfiguriert werden.

Noch werden unterschiedliche JavaScript Engines für die einzelnen Produkte umgesetzt: Nodyn nutzt DynJS, Vert.x offiziell noch Rhino, arbeitet aber an einer Nashorn-Unterstützung, die schon sehr weit gediehen ist. Die JVM selbst bringt seit Java 8 Nashorn als direkte JavaScript Engine mit. Auch in Nodyn sind schon Implementierungen für Nashorn zu finden. Da Rhino nicht mehr weiterentwickelt wird und Oracle stark an der Performance und Weiterentwicklung von Nashorn arbeitet, wäre es begrüßenswert, wenn sich in Zukunft alle Produkte auf der aktuellsten Script Engine wiederfinden würden.

Zum jetzigen Zeitpunkt kann man sicherlich noch nicht über fertigentwickelte Produkte sprechen, wenngleich die Zielrichtung dorthin geht und das grundsätzlich zu begrüßen ist. Es ist ein spannendes Thema und Forschungsfeld, wie man zwei unterschiedliche Plattformen möglichst effizient miteinander integrieren und die Stärken aus beiden Welten effektiv gemeinsam nutzen kann.

Serverseitiges JavaScript wird sich in den kommenden Monaten und Jahren weiterentwickeln und mehr und mehr Verbreitung in den Unternehmen finden. Die JVM als Plattform ist jedoch bereits etabliert und hat sich mehr als bewährt. Es liegt also nahe, beide zueinander zu bringen. Wir stehen noch ganz am Anfang, und es wird in Zukunft vielleicht vieles möglich sein, was wir uns derzeit noch nicht vorstellen können (oder vielleicht auch wollen?).

Egal, wo der Trend hingeht, ob hin zum Application Server (wie die Roadmap von Nodyn beschreibt) oder weg davon (wie bei Oracles Avatar 2.0), die Bemühungen in beiden Lagern zeigen, dass der Bedarf und das Potenzial vorhanden sind. Auch das Publikumsinteresse auf Konferenzen beweist eine sehr große Neugier daran, was bereits jetzt möglich ist und wo die Reise hingeht. Event-getriebene Architekturen und Anwendungen sind gefragter denn je, und damit auch der Umgang mit ihnen sowie die Ablaufumgebungen und das Deployment. Wie können Unternehmen mit diesem Ansatz zukunftsfähig werden und bleiben? Node.js und Vert.x sind gute und leistungsfähige Lösungen, die zusammen noch schlagkräftiger werden können, da man sich nicht mehr nur auf eine Umgebung, Technologie oder Sprache festlegen muss. Jeder Anwendungsfall kann in einem eigenen kleinen, leichtgewichtigen Modul umgesetzt und deployt werden, unabhängig von anderen Modulen, Komponenten oder wie auch immer man es nennen möchte (Micro-Service?). Vielleicht werden mit diesem Ansatz die guten alten Application Server, wie sie bislang genutzt wurden, tatsächlich aussterben?

Aufmacherbild: Internet of Things concept von Shutterstock / Urheberrecht: a-image

Verwandte Themen:

Geschrieben von
Niko Köbler
Niko Köbler
Niko Köbler ist freiberuflicher Software-Architekt, Developer & Trainer für Java & JavaScript-(Enterprise-)Lösungen, Integrationen und Webdevelopment. Er ist Co-Lead der JUG Darmstadt, schreibt Artikel für Fachmagazine und ist regelmäßig als Sprecher auf internationalen Fachkonferenzen anzutreffen. Niko twittert @dasniko.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: