Planning Poker mit Node.js und Socket.IO

Bei unserer täglichen Arbeit als Remote-Teil des Entwicklungsteams unseres Kunden benötigen wir häufig innerhalb des Scrum-Prozesses ein Remote-Tool zum Schätzen und Besprechen (Backlog Grooming bzw. Refinement Meeting) neuer Tasks.

Die folgenden Anforderungen einer solchen Pokeranwendung haben förmlich nach einer Umsetzung mit Node.js geschrien.

Unsere Anforderungen sind dabei:

  • Mehrere User können an einem Tisch für ein Ticket Schätzungen abgeben
  • Die Schätzungen sind solange unsichtbar, bis alle eine Schätzung abgegeben haben. Dabei sollen Änderungen aber in Echtzeit sichtbar sein
  • Das Schätzergebnis wird für alle sichtbar in Echtzeit aufgedeckt, sobald die letzte Schätzung abgegeben wurde
  • Pro Ticket sollen mehrere Schätzrunden möglich sein

Als Event-basierte, mit geringem Aufwand und dadurch schnell aufzusetzende Entwicklungsplattform eignet sich Node.js hervorragend zur Umsetzung von Anwendungsprototypen.
Zudem lassen sich mit Node.js dank non-blocking Eigenschaft auch relativ unkompliziert Webanwendungen mit Comet– bzw. Push-Anforderungen umsetzen.

Daher kam ich auf die Idee, am Mayday (unserem Slackday) eine node-basierte Anwendung mit Hilfe von Socket.IO für genau diesen Zweck umzusetzen.

Comet und Socket.IO

Mit Comet oder Web-Push ist hier die Fähigkeit des Webservers gemeint, nicht nur auf Request (Pull) eines Clients bzw. Browsers zu reagieren und Daten auszuliefern, sondern innerhalb einer definierten Verbindung bzw. als Reaktion auf ein bestimmtes Event ohne vorherige Anfrage selbstständig Daten an den Client zu senden.

Im node-Umfeld existieren mehrere Ansätze um Push-Funktionalität für Webanwendungen zu implementieren. Zum einen bietet sich Faye an, eine Implementierung des Bayeux-Protokolls. Nachdem ich bereits etwas Erfahrung mit Faye gesammelt hatte, wollte ich etwas Neues ausprobieren.

Macht man sich auf die Suche, findet man noch die ein oder andere Implementierung. Darunter ziemlich schnell Socket.IO. Auch Socket.IO vereinigt mehrere Ansätze, Echtzeit- bzw- Push-Anwendungen über die Node.js-Plattform zu implementieren. Neben Long Polling liegt der Schwerpunkt dabei auf dem Einsatz von Websockets.

Die Anwendung

Einmal installiert, ist eine Node.js-Anwendung innerhalb kürzester Zeit lauffähig.
Da im Internet schon jede Menge Installationsanleitungen sowohl für Linux, Mac als auch Windows existieren, spare ich mir an dieser Stelle die Node- und NPM-Installationshinweise. Außerdem findet sich die vollständige Anwendung im derzeitigen Entwicklungsstand auf github. Die folgenden Codeausschnitte entsprechen daher nicht der vollständigen Anwendung.

Wichtig zur Installation der Abhängigkeiten (benötigte externe Module) über NPM und zur Beschreibung der Anwendung ist die Datei „package.json“:

{
    "name": "planningpoker",
    "version": "0.1.1",
    "description": "node.js realtime planning poker",
    "keywords": [
        "realtime",
        "planning",
        "poker",
        "scrum"
    ],
    "dependencies": {
        "socket.io": "0.9.x",
        "express": "3.3.x"
    }
}

Wie im Code-Beispiel zu sehen, werden die Abhängigkeiten über „dependencies“ definiert.

In diesem Fall benötigen wir express, das vermutlich mächtigste Node.js Web-Framework sowie das zuvor beschriebene Socket.IO zur Umsetzung der Push-Funktionalität.

Ein npm install installiert anhand dieser Information dann die angegebenen Module ins lokale Projekt-Verzeichnis.

Damit steht der Real-Time Anwendung nichts mehr im Weg. In der Datei app.js findet sich die serverseitige Anwendungslogik. Dort wird die Nutzung von Modulen initiiert und die Webapplikation gestartet:

var port, app, server, io, storage, helpers;

port    = process.env.PORT || 3000;
app     = require('express')();
server  = require('http').createServer(app);
io      = require('socket.io').listen(server);
helpers = require('./helpers');
storage = require('./storage');

server.listen(port);

Neben den lokalen Modulen für helper-Funktionen und die Umsetzung eines Session-Storage werden zu Beginn neben dem Anwendungsport die Module „express“ und „socket.io“ geladen sowie die Anwendung als Webapplication gestartet. Der Standardport 3000 wird verwendet, sofern in der Environmentumgebung des Application-Users kein eigener Port (process.env.PORT ) definiert wurde.

Die beiden lokalen Module sind derzeit noch eine eher lose Sammlung von Funktionen bzw. Eigenschaften für Routinen, die jeweils in einem Objekt gekapselt werden. Hier kann in Zukunft noch eine weitere Aufteilung in eine Model- und Controller-Logik erfolgen.

Das folgende Codebeispiel zeigt das Verbindungshandling von Socket.IO:

// client socket.io connection handling
socket.on('connect', function() {
    username = (isSet(usernameSet)) ? usernameSet : username;
    var userObject = {
        'userId': userId,
        'username': username
    };

    socket.emit('username', userObject);
});

// server socket.io connection handling
io.sockets.on('connection', function(socket) {
    var userId = socket.id;

    // send initial userlist
    socket.on('username', function(data) {
        var username = data,
            oldUserId = null,
            cardValue = null; 

        // some more code here...
        storage.updateUsername(username, userId);
    }
}

Verbindet sich ein Browser mit der Anwendung, wird initial eine Socket.IO-Verbindung („connection“) aufgebaut. Innerhalb der Socket.IO-Verbindung regelt nun der im callback übergebene socket ein anwendungsbezogen definierbares Event-Handling.

Der Client reagiert („on“) auf das „connect“ Event des Servers, löst in diesem Fall das „username“ Event aus („emit“) und übergibt ein generiertes User-Objekt als payload. Bei der serverseitigen Behandlung des initial ausgelösten „username“ Events werden die vom Client übergebenen Daten verarbeitet und das User-Objekt dem lokalen Session-Storage hinzugefügt. Während sich „emit“ lediglich auf die aktuelle Verbindung zwischen einem Client und dem Server bezieht, lässt sich über „broadcast.emit“ ein Event auf allen (außer dem aktuellen Client selbst) potentiell verbundenen Clients auslösen.

Auf Frontendseite habe ich Socket.IO mit der jQuery-Bibliothek und (Twitter) Bootstrap kombiniert, die durch eine einfache Handhabung und umfangreiche Gestaltungsmöglichkeiten bestechen.
Um die Anwendung auch in freier Wildbahn testen zu können, bietet sich heroku an.
Der heroku-cedar-Stack unterstützt jedoch bisher keine websockets, weshalb io.set(„transports“, [„xhr-polling“]); in der deployten Anwendung bei Socket.IO für ein Umschalten auf xhr bzw. long polling sorgt.

Node Socket.io planning poker Screenshot
Screenshot der laufenden Anwendung mit zwei Teilnehmern

Fazit

Eine funktionsfähige Real-Time Anwendung lässt sich mit Hilfe von Node.js und Socket.IO innerhalb weniger Stunden erstellen. So lassen sich erste Ergebnisse innerhalb kürzester Zeit testen und präsentieren. Nutzt man auf Frontendseite die Express-Funktionalität oder gar Bootstrap, lässt sich dabei auch eine für das Auge ansprechende Anwendung erstellen. Socket.IO ermöglicht eine sehr einfache Handhabung von Websockets und reduziert den Aufwand für das Verbindungs- und Event-Management einer Real-Time fähigen Push-Anwendung. Den Code gibt es für alle Interessierten auch auf github: https://github.com/bgrande/node-socket-io-planning-poker.

Auch im Java-Umfeld hat sich einiges getan und mit dem playFramework existiert ein einfach zu nutzendes Framework, das einen ähnlichen Umfang an Möglichkeiten wie Node.js bietet. Daher wäre es mit Sicherheit interessant, die hier beschriebene Anwendung mit Hilfe des playFrameworks umzusetzen.

4 Kommentare

Schreibe einen Kommentar

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