Beitrag zur W-JAX Challenge mit JRuby on Rails

W-JAX Challenge: TippTop.net

Anlässlich der W-JAX kribbelte es mir in den Fingern und eine Münze hat nach ihrem Wurf entschieden, dass mein Lieblingsframework Ruby on Rails mit Hilfe der JRuby-Runtime den Einzug ins Finale der W-JAX Challenge halten sollte. Soweit so gut, doch bis dahin sind noch ein paar Dinge zu erledigen. Wie letztlich der zweite Platz in der Challenge erreicht wurde, lesen Sie hier.

Welche Architektur- und Designentscheidungen standen an?

Die Anwendung basiert auf JRuby in Zusammenspiel mit dem MVC-Framework Ruby on Rails. JRuby ist 100 % kompatibel mit Ruby on Rails und kann somit Rails-Anwendungen ohne Änderungen auf einer JVM laufen lassen. Als bekennender Rails

-Switcher sind die folgenden Merkmale von Rails in Bezug auf die Webanwendung ausschlaggebend:

  • MVC basiert
  • Convention over Configuration
  • Don’t repeat yourself (Deutlich bei der View-Entwicklung zu sehen, Stichwort Partials)
  • Ruby als produktivitätssteigernde

    dynamische Programmiersprache

  • Mongrel als HTTP-Server (Clusterfähig)
  • JRuby als native threaded Ablaufumgebung mit der Möglichkeit bei Bedarf jede beliebige Java-Klasse zu integrieren und zur Laufzeit dynamisch zu erweitern.
  • Plug-in-Architektur (Benutzerverwaltung, Rollen, Statemachine)

Die folgende Tabelle kann mit jeder Railsanwendung generiert werden und verdeutlicht, dass der reine Anwendungscode (ohne Views) kaum mehr als 800 Zeilen umfasst. Damit ist klar, dass ein einzelner Entwickler in nur rund sechs Personentagen die vorliegende Anwendung programmieren, testen und designen kann:

Abbildung 1

Schnellanleitung für die Installation

Die Anwendung kommt komplett daher und kann unter www.tipptop.net heruntergeladen werden. Das beiliegende Ant-Skript startet unter einem Unix-System mit vorhandener JVM direkt mit folgendem Aufruf:

$ ant 

Die Datenbank (Derby) ist bereits vorhanden und kann bei Bedarf auch neu generiert werden. Hierzu wird folgender Ant-Aufruf benötigt:

$ ant setup 

Mehr ist nicht notwendig, um eine lauffähige Anwendung zu erhalten. Da die Datenbank praktisch embedded läuft, ist noch zu beachten, dass nur eine Connection zur Verfügung steht, so dass die Anwendung vor der Nutzung eines Datenbankadmintools beendet werden muss. Es sind keine weiteren Zusatzkomponenten zu installieren.

Wo liegen die Logfiles?

Wer sehen will, wie die Anwendung auf die Daten zugreift und welche Ausgaben geloggt werden, findet die Logfiles im Verzeichnis /tipptop/logs. Konkret startet die Anwendung im Development-Modus, was für eine Beurteilung völlig ok ist. Im Log ist nach der Registrierung von neuen Benutzern auch der Aktivierungscode für den Account zu sehen, der dann vom Benutzer kopiert und in die URL eingefügt werden muss, da die Anwendung nicht nach draußen „funkt“ (d.h. es werden keine echten Emails versandt).

[ header = Seite 2: Wie „arbeitet“ man mit Tipptop? ]

Wie „arbeitet“ man mit Tipptop?

Nach Start der Anwendung ist diese unter der URL http://localhost:3000 verfügbar. Die technologische Basis ist HTML, CSS und JavaScript mit teilweiser AJAX-Funktionalität, wie sie bei der Spielersuche in der Hall of Fame zu finden ist. Die folgende Abbildung zeigt den Startbildschirm:

Abbildung 2

Im Menü können die Punkte Home, Spielplan, Gruppen, Hall of Fame und Konto angewählt werden. Diese Punkte werden im Folgenden näher erklärt.

  • Home:
    Hiermit kann typischerweise auf die Startseite verzweigt werden. Eine besondere Funktionalität ist hier nicht vorhanden
  • Spielplan:
    Der Spielplan ist zweigeteilt in die Übersicht der Vorrundenspiele (Gruppenspiele) und in die Finalrunden. Laut Spezifikation wurden nur die Gruppenspiele gefordert. Die Finalrunden werden soweit bekannt in der Datei db/seeds.rb in der Datenbank definiert. Hier kann der Programmcode entsprechend um den Abgleich für die Finalrunden erweitert werden. Zentraler Ansgriffspunkt ist hier die Klasse Refresher (Datei app/models/refresher.rb). Im Refresher werden bisher auch die Abgleiche für die Hall of Fame sowie die Gruppenplatzierungen vorgenommen. Im Spielplan kann man sofort tippen, sobald man sich angemeldet und ein Spielerprofil angelegt hat. Entsprechende Icons für die unterschiedlichen Aktionen helfen bei der Aktionswahl. Ist ein Tipp für ein Spiel vorgenommen worden, wird dieser neben dem Spiel angezeigt. Eine Änderung des Tipps kann bis 30 Minuten vor Spielbeginn vorgenommen werden. Ist eine Änderung oder die Eingabe eines Tipps nicht mehr möglich, wird dies durch ein Stoppschild signalisiert. In der Administratorrolle kann ein Spiel vom Administrator gesteuert werden. Hierzu ist es möglich, ein Spiel zu starten und im Verlauf die Zwischenergebnisse einzupflegen. Auf Wunsch des Administrators ist ein Abgleich des Systems möglich, wobei Ergebnisse erst nach vorheriger Beendigung eines Spiels in die Wertung einfließen.
  • Gruppen:
    In der Gruppenansicht sind die Gruppen A bis H aufgelistet. Ist man als Administrator angemeldet, erscheint ein Stift als Hinweis auf die Bearbeitungsfunktion. Der Administrator kann das Land sowie die Flagge für eine Mannschaft festlegen, wie in der Spezifikation gefordert.
  • Hall of Fame:
    In dieser Ansicht sind die zehn besten Tippspieler aufgelistet. Diese Liste wird allerdings erst sichtbar, wenn sich ein Benutzer angemeldet hat. Hierzu ist es nicht nötig, ein Spielerprofil anzulegen. Die Links der bestplatzierten führen direkt zu den Tipps, die nur angezeigt werden, wenn ein Spiel angepfiffen oder beendet wurde. Die Suchfunktion ist mit Hilfe von jQuery (jRails-Plugin) und AJAX-Autocomplete implementiert worden. Gibt man also einen Buchstaben in das Suchfeld ein, wird im Hintergrund die Datenbank abgefragt und Vorschläge für die Spielernamen angezeigt. Der Benutzer kann dann einen Eintrag auswählen und dann auf die Seite des Benutzers verzweigen.
  • Konto:
    Hier kann ein Benutzer je nach Status ein Spielerprofil anlegen oder bearbeiten. Ein weiterer Link führt zur eigenen Liste der abgegebenen Tipps. Der Administrator findet unter diesem Menüpunkt zwei Links für die Anpassung von Systemparametern und den Start eines Abgleichs.

Rechts nebem dem Hauptbildschirm sind zwei weitere Sidebar-Bereiche vorhanden. Unter dem Twitter-Account @tipptopdotnet habe ich entsprechend einige Tweets erstellt, die während der Nutzung angezeigt werden. Darunter ist eine Auflistung der drei aktuellsten Nachrichten (http://localhost:3000/news) implementiert (Zusatzfunktionalität).

Im Bereich der Fußzeile sind einige Links zu den Spielbedingungen und das Impressum zu finden, wie ebenfalls aus der Liste der Anforderungen zu entnehmen ist.

TippTop.net in 10 Minuten

Im Folgenden werden zwei Use-Cases beschrieben, die die Arbeit der Anwendung verdeutlichen. Zum Einen ist es ein Use-Case für den Tippspieler und zum Anderen für den Administrator.

[ header = Seite 3: Tippspieler ]

Tippspieler
  • Registrierung:
    Auf der Startseite findet man einen Link zur Registrierung. Daraufhin wird das Registrierungsformular gezeigt. Hier müssen alle Felder ausgefüllt werden. Eine Validierung prüft Formate und vorhandene Nicknames.

Registrierung

  • Aktivierung:
    Nach dem Abschicken des Formulars wird im Logfile /log/development.log ein Aktivierungslink ausgegeben, der im Produktionsbetrieb per Email versandt wird. Dieser Link muss im Browser eingegeben werden, damit das Nutzerkonto aktiviert wird.
[exec] Sent mail to malagant1969@googlemail.com [exec] [exec] Date: Wed, 28 Oct 2009 21:10:52 +0100 [exec] From: =?utf-8?Q?Wettb=9Fro_TippTop?= <noreply@tipptop.net> [exec] To: malagant1969@googlemail.com [exec] Subject: Bitte aktivieren Sie Ihr Konto bei TippTop [exec] Mime-Version: 1.0 [exec] Content-Type: text/plain; charset=utf-8 [exec] [exec] Hi, [exec] [exec] please confirm your registration: [exec] http: //tipptop.net/confirmation/Pp1kcQYE4J8_tPkPUmb3 [exec] [exec] Thanks! 

Nach erfolgter Aktivierung kann sich der Benutzer anmelden. Alternativ existieren drei vorgefertige Konten:

mjohann@rails-experts.com (Passwort: topsecret) Rolle: Gamer

spacken@spacken.eu (Passwort: topsecret) Rolle: Gamer

bla@bla.com (Passwort: topsecret) Rolle: Guest

Die Accounts werden in der Datei db/seeds.rb angelegt und sind direkt nach dem Aufsetzen der Datenbank verfügbar.

Login

  • Spielerprofil anlegen:
    Vor dem Spielen muss noch ein Spielerprofil erstellt werden. Hierzu klickt der Benutzer auf den Menüpunkt „Konto“ und dann auf „Spielerdetails anlegen“.

Mein Account

Spielerdetails

Danach ist ein Spielerprofil für den angemeldeten Benutzer definiert worden und es können Tipps abgegeben werden.

[ header = Seite 4: Tipp abgeben ]

  • Tipp abgeben:
    Mit einem Klick auf den Menüpunkt „Spielplan“ kann der Spieler nun seine Tipps abgeben. Beispiel: Erstes Spiel.

Übersicht der Spiele

Mit einem Klick auf das Lampensymbol wird ein Formular zur Abgabe eines Tipps anzeigt.

Tipp für das Spiel

Nach Eingabe der Tore wird der Tipp im System hinterlegt.

Übersicht der Spiele

  • Tipp ändern:
    Ein Tipp kann bis 30 Minuten vor Spielbeginn geändert werden. Hierzu ist ein Klick auf den Stift notwendig. Soll ein Tipp zurückgezogen werden, muss das rote X angeklickt werden.

Jeder Benutzer interessiert sich für die Tipps der anderen Spieler. Hierzu dient die Hall of Fame, die im Use-Case des Administrators gezeigt wird.

Administrator
  • Anmeldung als Admin:
    Der einzige Administrator im System ist admin@tipptop.net (Passwort: topsecret). Wenn man sich mit diesen Angaben anmeldet erhält man Adminrechter für die Anwendung. Der Administrator kann selbst nicht tippen.

Übersicht der Spiele

Mit einem Klick auf den Stift wird folgendes Formular angezeigt.

Spiel bearbeiten

  • Spiel starten:
    Damit der Use-Case fortlaufen kann, muss der Administrator nun das Spiel starten. Danach wird automatisch in den Spielplan verzweigt und angezeigt, dass das Spiel gestartet wurde.

Übersicht der Spiele

[ header = Seite 5: Spiel beenden ]

  • Spiel beenden:
    Wenn das Spiel gelaufen ist, kann der Administrator das Spiel beenden. Hierzu muss er wieder in die Bearbeitung des Spiels wechseln und den folgenden Link anklicken.

Spiel bearbeiten

  • Ergebnis erfassen:
    Das Ergebniss eines Spiels wird im Dialog eingegeben. Mit einem Klick auf „Speichern“ wird das Ergebnis festgelegt.
  • Abgleich starten:
    Auf der Seite „Konto“ des Administrators kann mit einem Klick auf „Abgleich starten“ eine Neuberechnung der Gruppenstatistik und der „Hall of Fame“ ausgelöst werden.

Mein Account

  • Hall of Fame anzeigen:
    Nach einem Abgleich kann mit einem Klick auf den Menüpunkt „Hall of Fame“ in die Spielerstatistik gewechselt werden.

Hall of Fame

  • Gruppenübersicht anzeigen:
    Mit einem Klick auf den Menüpunkt „Gruppen“ kann die Gruppensicht aufgerufen werden.

Gruppenübersicht

Testbarkeit und Erweiterbarkeit

Es sind rund 700 Zeilen Testcode nach Behavior Driven Development mit rSpec generiert worden. Diese finden sich in den Unterverzeichnissen zu /spec. Aus Zeitgründen sind keine spezialisierten Tests erstellt worden.

Die Anwendung ist wie jede Ruby on Rails Anwendung strukturiert und kann entsprechend leicht erweitert werden.

In der Datei /config/database.yml kann mit ein paar Zeilen eine komplett andere Datenbank genutzt werden (z.B. MySQL). Durch Erweiterung der Controller-Responses ist leicht eine WebService-API zu realisieren (jeweils mit ein paar Zeilen Code).

[ header = Seite 6: Einige Highlights von TippTop.net ]

Einige Highlights von TippTop.net
  • Layout:
    Das Layout ist in /app/views/layouts/application.html.erb definiert und stellt das Grundlayout für alle Views bereit. Unterstützt wird das Layout von CSS und jQuery.
  • Partials:
    Partials sind wie Portlets und können zur redundanzfreien Entwicklung der Oberflächen genutzt werden. Partials, wie die Login-Info, der Twitter-Bereich und das Menü finden sich in /app/views/shared und verdeutlichen den Gebrauch.
  • Statemachine:
    Ein Plugin (AASM = Acts_as_state_machine) sorgt für dynamische Methoden, die nur zur Laufzeit existieren und Transitionen bereitsstellen, um den Status von Objekten wie den Usern oder der Spiele nachzuführen. So kann ein User beispielsweise vom initialen Status „pending“ zu „confirmed“ wechseln. Der passende Code kann in /app/models/user.rb untersucht werden.
  • Seeding:
    Die Initialbefüllung der Datenbank erfolgt mit Hilfe des Ruby-Skripts in der Datei /db/seeds.rb. Das komplette Schema ist in /db/schema.rb zu finden. Damit sind SQL-spezifische Statements ausgeschlossen und die Datenbank kann beliebig ausgetauscht werden.
  • Autocomplete bei Suchfunktion:
    Im Dialog für die Suche in der „Hall of Fame“ wurde ein jQuery-Autocomplete eingebaut, um den Gebrauch von AJAX zu demonstrieren. Sicher hätte die Anwendung bei mehr Zeit an vielen Stellen „ajaxifiziert“ werden können. Der Code hierzu findet sich in den Dateien:

    /public/javascripts/application.js

    /app/controller/hall_of_fame.rb (Dort findet sich im respond_to-Block eine einzige Zeile, die auf das JavaScript-Format abzielt.) Diese sorgt dafür, dass anstelle einer HTML-View ein JavaScript-Template an den Client geschickt wird.

    /app/views/hall_of_fame/index.js.erb

    Ein paar CSS-Styles sorgen für die Dropdown-Liste im Suchfeld.

  • Refresher für den Systemabgleich:
    Der Refresher wird über einen Link ausgelöst. Die Klasse Refresher findet sich in der Datei /app/models/refresher.rb Der Refresher führt die Auswertung der Tipps und der Gruppenlisten durch.

class Refresher class << self def refresh_all refresh_groups refresh_gamers true end def refresh_groups refresh_games refresh_goals refresh_wins_and_losses end def refresh_gamers @games = Game.finished @gamers = Gamer.all(:include => :tipps) @gamers.each do |gamer| gamer.points = 0 @games.each do |game| @tipps = game.tipps @tipps.each do |tipp| if gamer == tipp.gamer gamer.points += calculate_points(game.goals_team_one, game.goals_team_two, tipp.goals_one, tipp.goals_two) end end end gamer.save! end end # Calculate the points for this tipp def calculate_points(game_goals_one, game_goals_two, goals_one, goals_two) # Nichts richtig points = 0 #Tendenz und Tore richtig return points += 5 if game_goals_one == goals_one && game_goals_two == goals_two # Tendenz und Torverhältnis richtig return points += 4 if ((game_goals_one > game_goals_two && goals_one > goals_two) || (game_goals_one < game_goals_two && goals_one < goals_two)) && (game_goals_one - game_goals_two == goals_one - goals_two) # Nur Tendenz richtig return points += 3 if ((game_goals_one > game_goals_two && goals_one > goals_two) || (game_goals_one < game_goals_two && goals_one <= goals_two) ) points end # Amount of games per team is updated based on finished games def refresh_games @games = Game.finished @teams = Team.all @teams.each do |team| team.games = @games.all(:conditions => ["team_one = ? or team_two = ?", team.id, team.id]).size team.save! end end # Total number of goals per team is updated based on finished games def refresh_goals @games = Game.finished @teams = Team.all @teams.each do |team| goals_won = 0 goals_lost = 0 @games.all(:conditions => ["team_one = ? or team_two = ?", team.id, team.id]).each do |game| goals_won += game.goals_team_one if game.team_one == team goals_lost += game.goals_team_two if game.team_one == team goals_won += game.goals_team_two if game.team_two == team goals_lost += game.goals_team_one if game.team_two == team end team.goals_won = goals_won team.goals_lost = goals_lost team.save! end end # Amount of games won per team is updated based on finished games def refresh_wins_and_losses @games = Game.finished @teams = Team.all @teams.each do |team| wins = 0 losses = 0 draws = 0 @games.all(:conditions => ["team_one = ? or team_two = ?", team.id, team.id]).each do |game| #puts "++++ game.winner.country = #{game.winner.country} ; team.country = #{team.country}" wins += 1 if game.winner == team losses += 1 if game.loser == team draws += 1 if game.is_draw? end team.wins = wins team.losses = losses team.draw = draws refresh_points(team) team.save! end end def refresh_points(team) team.points = team.wins * 3 team.points += team.draw end end end 
Fazit

Die Challenge sollte sicher auch den Teilnehmern der Konferenz zeigen, dass die genutzte Technik für alltagstaugliche Projekte eingesetzt werden kann. Und da zählen Produktivität, Lines of Code, Strukturierung, Interoperabilität.

Folgende Fakten sind aus meiner Sicht überzeugend für den Einsatz von JRuby on Rails:

  • Eine Person hat rund 6 Personentage benötigt, um die Anwendung zu erstellen. Dabei ist jeder Rails-Kenner in der Lage, die Anwendung zu warten und zu erweitern, ohne die sonst übliche Packagestruktur typischer Java-Anwendungen auskundschaften zu müssen. Das erhöht die Produktivität enorm.
  • Ruby Code ist sehr gut lesbar im Vergleich zu anderen Sprachen.
  • Die Plugin-Architektur erlaubt redundanzfreien Code und eine schnelle Realisierung von Querschnittsfunktionen (Benutzer-Rollen-Konzept, Paginierung).
  • JRuby als Ablaufumgebung bietet die Interoperabilität typischer Java-Anwendungen und ist darüberhinaus auch noch schneller als andere Ruby-Runtimes. Mit dem Maven-Gem stehen Ruby-Projekten tausende Komponenten aus der Java-Welt zur Verfügung.
  • Der Anwendungscode umfasst lediglich rund 800 Zeilen Code.
Michael Johann ist Chefredakteur des Magazins RailsWay (www.railsway.de) und Autor des Buches „Ruby on Rails für JEE-Experten“ (Hanser Verlag). Er ist Berater und Trainer für JRuby on Rails und regulärer Sprecher auf Konferenzen rund um den Globus. Vor dem Switch zu Ruby on Rails wurde er als JEE-Experte und als Chefredakteur von Java-Spektrum bekannt.
Geschrieben von
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.