Ja, eine Schnupperstunde, so würde ich den Talk am letzten Montag zu Node.js auf dem Microsoft Web Summit 2013 bezeichnen. In der einen Stunde habe ich versucht die wichtigsten Aspekte der Entwicklung mit Node.js aufzuzeigen

  • Prozess der die Google V8 JavaScript Engine hostet
  • Single Threaded Event Loop
  • Asynchrone Verarbeitung
  • Modularisierung
  • Event Emitter
  • Streaming

wobei ich mich primär auf die Flexibilität durch Modularisierung fokussiert habe.

In meinem Vortrag habe ich eine Sache komplett außen vor gelassen: Testing. Ich kann nur nochmals betonen: Testen ist Lebenswichtig. Ich hebe meine Hand fürs Testing. Ich bin Pro Testing und beglückwünsche jeden Entwickler der das Tagtäglich lebt. Gerade im Umfeld mit dynamischen Sprachen ist Testing essentiell. Hier gibt es ebenfalls eine große Auswahl, die bekanntesten dürften Jasmine, Mocha und Chai sein, diese lassen sich noch mit Mocking Frameworks wie Sinon.js ergänzen.

Slides

Das Slidedeck gibt es hier, voila.

Wer Slides anderer Vorträge von mir sucht wird in Zukunft mehr Slidedecks auf meinem Speakerdeck finden.

Code

Während des Vortrages habe ich die Anwendung Anfangs live gecodet, später über Schnipsel erweitert und zum Schluß ein fertiges Projekt genommen weil ich einen Fehler in einem meiner Code Files hatte welcher mir nicht gleich offensichtlich ins Auge stieß.

Auf jeden Fall vielen Dank an André, Christian und den 3. der mir beim Audience Pair Programming während der Session geholfen hat.

Am Anfang war der Server

Was ich an Node wirklich cool finde ist die Möglichkeit sich sein System so zusammen zu stecken wie man es gerne möchte. Natürlich muss man hierfür schon wissen welche Module und Möglichkeiten es gibt und ich habe in der einen Stunde wirklich nur ganz rudimentäre Dinge gezeigt. Als Ausgangsbasis für eine kleine Musikplaylist habe ich den folgenden Code genommen

var http = require('http');

var port = process.env.PORT || 5000;

var server = http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('Hello from Node.js on Windows');
});

server.listen(port);
  

Es ist schon beeindruckend wie man mit 6 Zeilen Code einen einfachen HTTP Listener erstellen kann. Danach habe ich die Funktion in ein eigenes Modul ausgelagert.

(function() {
"use strict";

function sayHello(req, res) {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>Hello from Node.js on Windows</h1>');
}

exports.myFunction = sayHello;
})();

Somit lassen sich einzelne Funktionspakete leicht in modulare Blöcke packen und entsprechend wiederverwenden. Die Verwendung des exports Statements sorgt dafür das die Funktion über den Modulnamen import erreicht werden kann.

Der Server Code den ich in die app.js gepackt habe lädt nun das eigene Modul mittels require rein.

var http = require('http'),
controller = require('./controller');

var port = process.env.PORT || 5000;

var server = http.createServer(function (req, res) {
controller.myFunction(req, res);
});

server.listen(port);
  

Einsatz von Routen

Bis jetzt wird jeder HTTP Request an diesen Listener übergeben und es kommt immer das gleiche Resultat zurück. Es wird an der Zeit ein paar HTTP Routen zu definieren. Hierfür entscheide ich mich für das Modul director aus dem Flatiron.jsFramework. Das schöne an diesem Modul ist das es sowohl Serverseitig in Node.js funktioniert wie auch Client-seitig im Browser.

Module unter Node installiert man überlicherweise mit dem Node Package Manager kurz Npm. Npm greift auf npmjs.org zu, eine Registry in welche Entwickler eigene Module hinzufügen können. Um Director zu installieren gibt man einfach npm install director an und das Paket wird im Untervzeichnis node_modulesabgelegt.

Director ersetzt nun den Listener und macht ein Dispatch. Damit Director weiß wohin er weiterleiten kann müssen Routen definiert werden. Die Routen können direkt als Literal in der Instanzierung angegeben werden

var router = new director.http.Router({
'/about': {
get: controller.myFunction
}
});

oder ad hoc

router.get('/', Controller.showRoot);

Abhängigkeiten definieren

Nun hat man eine Abhängigkeit zu einem Modul geschaffen. Diese Abhängigkeit sollte man für seine Anwendung definieren, dazu gibt es eine weitere Datei die npmanalysiert: package.json.

Diese Datei ist sozusagen das Manifest und enthält neben den Informationen zur Anwendung auch einen Bereich für Abhängigkeiten. Die externen Module kann man per npm install installieren. Für diese App sieht das package.json folgendermaßen aus:

{
"name": "DemoApp",
"version": "0.1.0",
"author": "writeline",
"description": "This is just for a demo",
"contributors": [
{
"name": "Dariusz Parys",
"email": "dparys@microsoft.com"
}
],
"main": "./app.js",
"keywords": [
"azure",
"demo"
],
"dependencies" : {
"director": ">=1.1.0",
"jade": ">=0.28.1",
"union": ">=0.3.6",
"formidable": ">=1.0.11",
"azure": ">=0.6.10"
},
"noAnalyze": "true",
"license": "Apache",
"engines": {
"node": ">=0.6.14"
}
}

>= sagt einfach aus, nimm alles von da an, auch wenn es neuer ist. Übrigens, das ist keine Best Practice, sondern lediglich während der Entwicklung interessant, speziell wenn man ein Modul als Abhängigkeit hat dass des Öfteren gefixed wird.

Um die Routen zu verteilen muss noch der Router dispatchen

var server = http.createServer(function (req, res) {
router.dispatch(req, res, function (err) {
if (err) {
res.writeHead(404);
res.end();
}
});
});
  

Node hat sich nach der Version 0.1 von Promises verabschiedet und nimmt Callbacks her. Wer weiterhin mit Promises arbeiten möchte dem empfehle ich mal einen Blick auf das Github Repository von Dominic Denicola zu werfen.

Templating

In der Funktion weiter oben wurden die HTML Tags direkt im Response rausgeschrieben. Das will man nicht. Man will eine Viewengine. Und auch hier gibt es viele. Ich kann jedem empfehlen einen Blick auf Jade zu werfen.

Das HTML wird hier lediglich mit den Tag Namen beschrieben, Attribute in Klammern mit Kommas verpackt, Inhalte einfach dahintergeschrieben, Hierarchien durch Einrückungen (Whitespace) sichergestellt.

Die About Seite in Jade sieht so aus.

!!!
html
h1 Hello from Node.js on Windows

Fertig.

Analog die Einstiegsseite der Anwendung

!!!
html
h1 Microsoft Web Summit 2013 Demo
h2 Simple Node.js coding
div#main
a(href='/about') About
|
a(href='/music') Music
|
a(href='/upload') Upload Music

Kurz und knackig.

Um das Template auch anzuzeigen könnte die Implementierung der showRootFunktion nun so aussehen:

exports.showRoot = function () {
var res = this.res,
req = this.req;

var path = __dirname + '/root.jade';
var content = fs.readFileSync(path, 'utf8');
var renderFunction = jade.compile(content, { filename: path, pretty: true });
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(renderFunction());


}

Man bekommt eine Funktion zum Rendern des Templates zurück. In diese Funktion können auch Objekte übergeben werden die in der View für Anzeigelogik Verwendung finden. Zum Beispiel die Anzeige der Musikstücke funktioniert auf diese Weise, übergeben werden die gefunden Musikblobs aus Windows Azure

res.end(renderFunction({blobs: blobs}));

und im Jade Template wird darüber iteriert

!!!
html
h1 Available music
ul
each blob in blobs
li #{blob.name}
audio(controls, src='#{blob.url}')

Upload von Daten

Eine der weiteren Stärken von Node.js ist das Streaming. Hier gibt es bereits Beispiele in der Praxis die Produktive Dienste aufgebaut haben.

Einen Hacken gibt es beim bestehenden Code: Buffering. Insofern wird noch eine Middleware benötigt über die ich das Buffering ausstelle und director integriere. Hier kommt union zum Einsatz.

Die Definition des Servers sieht nun ein wenig anders aus:

var server = union.createServer({
buffer: false,
before: [
function (req, res) {
var found = router.dispatch(req, res);
if (!found) {
res.emit('next');
}
}
]
});

Das ist der Grundstein damit die Datei gestreamed empfangen werden kann, für das Post Handling nehme ich das Modul formidable. Es benutzt Events auf welche ich lauschen kann um dann darauf zu reagieren:

exports.upload = function () {
var res = this.res,
req = this.req;

var form = formidable.IncomingForm();

form
.on('file', function (field, file) {
var options = {
contentType: file.type
};
console.log("file path: " + file.path);
})
.parse(req, function (err, fields, files) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
if (err) {
res.end('error: Upload failed');
} else {
res.end('sucess: Uploaded file(s) ' + util.inspect({ fields: fields, files: files }));
}
});
}

Die Route kann Streaming empfangen sobald man dies dem Router mitteilt:

router.post('/upload', { streaming: true }, controller.upload);

Azure Integration

Die Dateien werden in ein Temporäres Verzeichnis gespeichert. Von dort möchte ich diese in den Windows Azure Blob Storage packen. Die Schritte sind wirklich simpel an dieser Stelle. Zum einen erstelle ich einen Blob Storage Client und übergebe diesem den API Key zur Verbindung. Der folgende Code Schnipsel zeigt die Definition der Umgebungsvariablen und dem Anlegen eines Containers falls dieser noch nicht existiert:

process.env.AZURE_STORAGE_ACCOUNT = "xyz";
process.env.AZURE_STORAGE_ACCESS_KEY = "xyz";

var blobService = azure.createBlobService();
blobService.createContainerIfNotExists('uploads', { publicAccessLevel: 'blob' }, function (error) {
if (error) {
console.dir(error);
return;
}
});

Um einen Blob in den Container zu legen muss man lediglich die folgende Zeile im file Event von Formidable hinzufügen

blobService.createBlockBlobFromFile('uploads', file.name, file.path, options, function (error) {
if (error) {
console.dir(error);
}
})

Deployment

Das Deployment Model ist mit Windows Azure Websites simpel. Ich habe ein Github Repository mit der Windows Azure Website verbunden. Bei jedem git push origin master wird automatisch auf die Webseite verteilt und die Seite steht kurze Zeit später im Web live zur Verfügung.

Das komplette Beispiel aus dem Talk findet ihr hier auf Github.