Möchten mit der Java Virtual Machine vertraute Programmierer bei der Webentwicklung in bekannten Territorien bleiben, landen sie bei der Framework-Suche schnell bei Rails mit JRuby und Grails mit Groovy. Dieser Artikel durchleuchtet hauptsächlich funktionale Aspekte der populären Kandidaten und blickt auf ihre Historie zurück. Außerdem wird betrachtet, was positiv heraussticht oder weniger gut gelungen ist, da nicht Features allein den Ausschlag bei der Wahl geben können. So gesehen, ist eine Gegenüberstellung von Webframeworks gar nicht so weit von den Vergleichstests entfernt, die in Autozeitungen zu finden sind.
Im Autobau teilen sich oft unterschiedliche Modelle und Hersteller gemeinsame Plattformen, um Entwicklungs- und Herstellungskosten zu sparen. In vielen IT-Projekten ist die JVM-Plattform gesetzt, die Auswahl der darauf laufenden Systeme ist wiederum relativ frei, sodass JRuby schon viele Jahre eine ernstzunehmende Option ist. JRuby wurde 2001 veröffentlicht und ist für eigentlich alle populären Ruby-Anwendungen einsetzbar (Rails seit 2006). Im Funktionsumfang ist es der Referenzimplementierung MRI (auch als CRuby bekannt) immer einen Schritt hinterher (das kommende JRuby 9000 wird kompatibel zu MRI 2.2 sein). Zudem erfordert es mehr Geduld beim Anwendungsstart, sodass JRuby beim lokalen Entwickeln eher gemieden wird.
Außerdem fügt es sich nicht nahtlos in den Java-Kosmos ein, was beispielsweise
die Unterscheidung von Ruby- und Java-Objekten oder das stets einzufügende
require 'java'
verdeutlichen. Viele Ruby-Plug-ins (Gems) funktionieren
oder es gibt spezielle, mitunter veraltete, Wrapper Gems. Concurrency und
Parallelisierung sind lange vernachlässigte Stiefkinder des MRI. Obwohl es
inzwischen auf native OS-Threads setzt, sorgt der eingebaute Global
Interpreter Lock (GIL) dafür, dass jeweils nur ein Thread läuft. Für echte
Parallelisierung gibt man daher JRuby oder Alternativen wie
Rubinius oder IronRuby den Vorzug.
Groovy wurde 2003 veröffentlicht und ist, nach einer längeren Dämmerphase, seit 2008 stabil. Nicht Grails allein ist das Zugpferd der Sprache, auch Gradle, Spock und Geb sind in dem Kontext bekannte Projekte. Groovy gilt zudem als geschliffene Fassung von Java und ist als reine JVM-Sprache nur im zugehörigen Ökosystem überlebensfähig, wo sie allerdings bestens integriert ist.
Java-Code ist, bis auf wenige Ausnahmen, syntaktisch valider Groovy-Code (JDK
7). Daher können Groovy- und Java-Klassen voneinander erben, ohne dass der
Entwickler über Typ-Konvertierung oder Class-Loading stolpert. Was das
Kompilieren und Typisieren angeht, erlaubt Groovy mit def
und @CompileStatic
beides – sogar im wilden Mix. Groovy-Neulingen erleichtert die Sprache so den
Einstieg – auch in die Welt der dynamischen Sprachen.
Zum Vergleich der Sprachen folgen ein paar einfache Code-Beispiele:
JRuby | Groovy | |
---|---|---|
Einführung | 2001 (MRI Ruby 1995) | 2003; stabil seit 2008 |
JVM-Plattform | Portiert für die JVM, extra Layer für Java-Integration | Designed für die JVM, nahtlose Java-Integration |
Editoren und IDE’s | Vim, Emacs, TextMate, Sublime, Eclipse, NetBeans, IntelliJ IDEA | |
Besonderheiten | sehr schnelle Ruby-Implementierung, dynamische Typisierung | Superset von Java, Typisierung optional, statisch Kompilieren optional |
Projekte und Libs | Rails, Sinatra, Rspec, Rake, Bundler | Grails, Spock, Gradle, Geb, GPars, Griffon |
Gefragte Youngtimer
Webframeworks sind seit vielen Jahren De-facto-Standard für die moderne Webentwicklung, vor allem um den Boilerplate-Anteil zu reduzieren. Die herangezogenen Full-Stack-MVC-Webframeworks haben naturgemäß viele konzeptionelle Gemeinsamkeiten wie “Convention over Configuration” (CoC), “Don’t Repeat Yourself” (DRY), Support für Rapid Prototyping von CRUD-Anwendungen (Create, Read, Update, Delete), Hot-Reloading sowie Unterstützung für Mehrsprachigkeit, Tests und Deployment. Integrierte Webserver wie Webrick in Rails oder Tomcat für Grails vereinfachen und beschleunigen das lokale Entwickeln.
Der Vorläufer von Ruby on Rails entstand bei der Entwicklung einer Projektmanagement-Anwendung (Basecamp), die im Wesentlichen durch David Heinemeier Hansson getrieben und ab 2005 stabil genug für den Projekteinsatz außerhalb seiner Firma wurde. Ruby verdankt Rails einen Großteil seines Erfolgs und Rails wiederum, abgesehen von seinen pragmatischen Ansätzen (REST statt SOAP etc.), die frühen Einsätze bei Diensten wie GitHub und Twitter. Gerade die Mikroblogging-Anwendung kratzte später allerdings erheblich am Ruf, als häufige Performance-Probleme auf Rails zurückgeführt wurden. Bedeutende architektonische Verbesserungen (unter anderem Modularisierung) folgten 2008, nach der Framework-Hochzeit mit Merb.
Groovy on Grails entsprang tatsächlich der Idee, eine mit Groovy arbeitende JVM -Alternative zu Rails zu entwickeln. Um Verwechselungen zu vermeiden und auf Drängen von Hansson wurde der Name später auf Grails reduziert. Die Open-Source -Entwicklung war immer schon kommerziell gesponsert (bis 05/2015 Pivotal). Das Interessante an Grails ist sicherlich, dass es im Grunde die gleichen modernen Konzepte wie Rails verfolgt, sie aber mit erprobten und umfangreichen Evergreens wie Java EE, Spring, Hibernate, Quartz und Sitemesh zusammenbringt.
Ruby on Rails | Grails | |
---|---|---|
Einführung (v1.0) | 2005 | 2008 |
Lizenz | MIT License | Apache License 2.0 |
Reifegrad und Community | Rails ist länger im Umlauf und verfügt über die größere Community | Grails ist jünger als Rails und gefühlt weniger imEinsatz (diverse JVM-Konkurrenten) |
Sprache und Plattform | Ruby, plattformunabhängig (JVM: JRuby) | Groovy, plattformunabhängig mit JVM |
App-Server (Beispiele) | Passenger (mod_rails) + nginx/Apache, Mongrel, Thin, Unicorn oder JRuby + Warbler auf Java Application Server | Java Application Server (Tomcat, Glassfish, Jetty …) |
“Unter der Haube” | Core-Extensions (Gems) u.a. ActiveRecord, ActiveSupport oder ActionPack u.a. | Java EE, Spring, Hibernate, Sitemesh, Spock |
Produktivität | RAD, COC, DRY, Scaffolding … | |
Testen | Rspec, Cucumber, Minitest | JUnit, Spock |
Blick in den Motorraum
Architektonisch besteht Rails aus mehreren Ruby-Gems, sogenannten Core-Extensions wie ActiveRecord für objektrelationale Abbildungen (Object Relational Mapping, ORM), ActiveSupport für Ruby-Spracherweiterungen und ActionPack für Routing, Controller und View-Rendering. Grails hingegen vereint zum Großteil bewährte Java-Projekte wie Spring-Komponenten für Request Routing oder Hibernate für ORM und vereinfacht sie in Set-up und Konfiguration.
Fat Models
ORM ist das geheimnisvolle Bindeglied zwischen den Datenbanktabellen und Objekten der Anwendung. Die das Active-Record Pattern (nach Fowler) zugrunde legenden ORMs beider Frameworks bieten ein ähnliches Feature Set, vor allem bei dynamischen Find*-Methoden, Query-APIs, Validierung und Callbacks.
Rails zieht bewusst keine Trennung von Domain-Logik und Persistenz. Vor allem,
wenn der Controller einfach bleiben soll, landet alles in den jeweiligen
Domain-Klassen (in Rails Models genannt). Derartige Ableitungen von
ActiveRecord::Base
enthalten die Datenstruktur, die Constraints und eben
Fachlogik, die oft mit anderen Models – schlimmstenfalls mit anderen
Datenquellen oder Webservices – kommuniziert. Der Ausweg daraus ist steinig
(Separierung) und erhält kaum Unterstützung durch das Framework. Positiv
dagegen ist, dass in Rails der ORM-Austausch bereits eingeplant ist und
DataMapper beziehungsweise sein
Nachfolger RubyObjectMapper gerade bei
Adaptionen aus der NoSQL-Ecke eine Alternative sein kann.
Grails Object Relational Mapping (GORM) ist ein mächtiges Groovy-Pendant, das konsequent auf Hibernate, ein weiteres, schlachterprobtes Java-ORM, setzt. Dabei sind zunächst auch Mapper-Klassen zu definieren (in Grails Domains genannt), die den Datenzugriff, die Validierung (Constraints) aber auch grundlegende Domain-Logik enthalten. Für nicht relationale Datenbanken gibt es GORM-Adapter für Neo4j, Redis, standardisierte REST-Clients und so weiter.
Wenn man mit Rails' ActiveRecord
und ähnlichem gearbeitet hat, fühlt sich das Hibernate-Konzept zunächst fremd an,
denn ein save()
oder delete()
hält erst einmal alle Änderungen in seiner
Session (Datenbank, nicht HTTP) und gibt sie erst an die Datenbank weiter, wenn
der darüber liegende Thread (beispielsweise eine Controller Action) beendet oder
ein manuelles Session-Flush ausgerufen wird. Noch heikler ist das automatische
Speichern von mit get("SomeId")
geladenen Entitäten, ohne explizites save()
.
Grails bietet zur sauberen Strukturierung der Fachlogik eine Services-Schicht,
die sich vergleichsweise einfach einbinden lässt (Namenskonvention,
Auto-Injection), standardmäßig transaktional arbeitet und unter dem
Gesichtspunkt erste Wahl für Datenbankinteraktionen aus dem Controller heraus
ist.
Viele Entwickler ziehen zum Beispiel SQLite in Rails oder HSQLDB (In-Memory) in Grails Oracle im lokalen Set-up vor. Dafür und wenn die Datenbank-Migrationen unter den Blicken der Administratoren durch die Testumgebungen in Richtung Produktion wandern, braucht es ein unabhängiges, textbasiertes Format. In den meisten Projekten kommt hierfür Liquibase (via Plug-in) zum Einsatz. Rails liefert zudem eine eigene Migrations-DSL, die sich mit Rake-Tasks auf der Ziel-DB ausführen lässt. Grails wiederum kennt unterschiedliche Konfigurations-Modi, die die Domain-Klassen mit der Datenbankstruktur beim App-Start oder Klassen-Reload abgleichen und gegebenenfalls anpassen.
Beispiele für Aufbau und Vererbung von GORM Domain-Klassen sehen wie folgt aus:
Skinny Controller
Viel mehr, als Daten aus der Webschicht entgegenzunehmen und nach deren Verarbeitung eine Response (beispielsweise an die View delegiert) zurückzuliefern, muss ein Rails/Grails-Controller gar nicht. Beide Frameworks unterstützen bei der Routing-Konfiguration und bieten Callback-Interceptors. Regel-basierendes, übergreifendes Filtern lässt sich in Rails in der Rack-Middleware beziehungsweise in Grails über Servlet- oder Grails-Filter (mit Hooks zum Beispiel beim View Rendering einer Aktion) implementieren.
Unter anderem hat die Rails-Community “Fat Model, Skinny Controller” als Pattern manifestiert, das Wiederverwendbarkeit und besseres Testing verspricht. Wenn Projekte ohne strukturelle Vorgaben wachsen, führt das unweigerlich zu aufgeblasenen Model-Klassen mit verwässerter Verantwortlichkeit. Grails gibt daher mit Command- und Service-Objekten Möglichkeiten vor, den Persistenz-Teil abzukoppeln. In Rails hingegen gibt es kaum Standards dazu. Dennoch kann man sich bei Framework-unabhängigen Paradigmen wie Data Context Interaction(DCI) oder Hexagonal Architecture Anregungen holen.
Während Rails auch dem Thema Data Binding kaum Beachtung schenkt, kann Grails beispielsweise Request-Parameter automatisiert auf nahezu beliebige Datenstrukturen mappen, die sich in der Signatur der Action-Methode vorgeben lassen. Ist der Parameter eine Domain-Klasse oder ein Command-Object, kann das Programm ihn sogar vor der eigentlichen Verarbeitung validieren und Assoziationen laden.
Layout und Frontend-Assets
ERB (Embedded Ruby), eine Mischung aus beispielsweise HTML mit eingebettetem Ruby, ist Rails' View-Technik und sorgt zusammen mit Template Partials und Helper-Funktionen für wartbare Views. Grails' Gegenstück dazu sind Sitemesh als Layout Engine und Groovy Server Pages (GSP) für Markup und GSP-Tags (ähnlich JSP/ASP). Komplexer Code lässt sich in selbstgeschriebenen GSP-Tags (Taglibs) verstecken.
Die Präprozessor-Verarbeitung von SASS oder CoffeeScript, die Komprimierung von Bildern oder JavaScript in für das Web geeignete Größen sowie Abhängigkeiten zu weiteren Komponenten decken die mitgelieferten Asset-Pipeline Plug-ins ab. Nur wer noch feingranularer testen und optimieren will, muss sich noch selbst mit Bower, Grunt oder Gulp die Hände beschmutzen.
REST einsetzen
Bereits zu einer Zeit (2007), als SOAP noch schwer angesagt war, hat Rails konsequent auf REST gesetzt. Heutzutage helfen sowohl Rails als auch Grails auf ihre Art beim Exponieren und Konsumieren von Ressourcen, beispielsweise initial durch Restful CRUD Scaffolding, Routing oder Content-Negotiation, aber auch durch Model-Serialisierung. Viel Zeit fließt erfahrungsgemäß in die Integration und Anpassung der Schnittstellen, sodass die bereitgestellten Features mit ihren Annahmen zu Hypermedia, Content-Type, Versionierung oder technischer Dokumentation nur teilweise nützlich sind.
Das neuzeitlich-schlanke Rails liefert viele seiner im REST-Kontext gebrauchten Funktionen nur noch über Gems und überlässt die Auswahl dem Entwickler. Ein Beispiel dafür ist ActiveResource, früher Bestandteil des Rails-Core und probates Mittel, um zwei Rails-Applikationen per RESTful HTTP über eine ActiveRecord ähnliche API kommunizieren zu lassen.
Grails hat im Auslieferungszustand einen mächtigen Funktionsumfang. Zum
Beispiel können Entwickler Domain-Klassen einfach per Annotation in eine
Ressource verwandeln. Wird es spezifischer, erfolgt die Implementierung im
Controller, der wiederum Funktionen des grails.rest.RestfulController
und
diverser, anpassbarer Converter, Renderer und Binder (zum Beispiel für
HAL-JSON), nutzen kann.
So sieht Content Negotiation in Grails etwa wie folgt aus:
Asynchrone Verarbeitung
HTTP Requests sind oft der Flaschenhals einer Webanwendung, daher sollte man nur das Nötigste zum schnellen Rendern der Seite tun und lang laufende Aufgaben auf nicht blockende Nebenschauplätze verlegen.
Frühe Versionen von Rails waren nicht thread safe und so wurde der gesamte Request vom Webserver (bspw. Mongrel) mit einer Mutex-Klasse ummantelt. Das ist lange her, aber genau wie das Parallelisierungsproblem in MRI kommt dieser Punkt in Diskussionen immer wieder auf. Blockende HTTP-Zugriffe nach Außen lassen sich so refakturieren, dass sie die Rack-Middleware-Schicht (Webserver API) direkt verarbeitet.
Unter JRuby gibt es keinen Grund, die Java-Concurrency-Bibliotheken nicht zu
nutzen. Allerdings gibt auch unabhängige Gems wie Sidekiq (Celluloid, Threads,
Redis), um Tasks asynchron abzuarbeiten. Grails erspart die Suche nach
entsprechenden Bibliotheken und bietet mit dem integrierten GPars-Framework
eine mächtige Async API (Promise Pattern), die
beispielsweise Services durch Verwenden der @DelegateAsync
-Annotation einen
asynchronen Klon hinzufügt. GORM bietet für viele seiner Funktionen
entsprechende Varianten (async
Namespace) und die Action Response lässt sich
als grails.async.PromiseMap
direkt aus dem Controller zurückgeben:
Nicht nur in Enterprise-Projekten ist entkoppelte, skalierbare und komponentenübergreifende Kommunikation über Message Services gewünscht. Im Rails-Umfeld kann man dafür ebenfalls Sidekiq einsetzen, aber eigentlich sind beliebige JMS-Provider (Java Message Service) unter JRuby beziehungsweise Groovy denkbar. Task Scheduler, die Aufgaben zu festgelegten Zeiten starten, sind eine weitere Möglichkeit.
In Rails kann man tatsächlich einem Betriebssystem-Job die Ausführung eines Rake Task überlassen oder man setzt eines der dazu verfügbaren Gems (zum Beispiel Whenever) ein. Noch einfacher geht es mit dem Quartz-Plug-in in Grails.
Automatisiertes Testen
Beide Frameworks ermöglichen Unit- und Integrationstests, erzeugen ein rudimentäres Test-Codegerüst beim Generieren von Klassen und liefern eine gesondert konfigurierbare Testumgebung. Rails stellte Tests von Anfang an in den Vordergrund. Seit Version 4.0 nutzt es MiniTest als Test-Framework, aber auch andere Langzeit-Alternativen wie Rspec sind möglich. Grails hingegen setzt auf den populären Vertreter Spock.
Ein Beispiel für einen Unit-Test sähe damit etwa wie folgt aus:
Allerdings lassen sich auch Legacy-Grails-Tests und reine JUnit-Tests durchführen, was in der Praxis zu Durcheinander bei den diversen Annotationen, Code-Mixins, Ableitungen und Mock-Möglichkeiten führen kann.
Modularisierung, Caching und Sicherheit
Während sich vor ein paar Jahren noch jedes halbwegs ernstzunehmende Webframework nur mit aufgeblasenem Feature Sets sehen (und vergleichen) lassen konnte, ist dieser Trend inzwischen gegenläufig: Mit jedem Major Release werden altgediente Kernkomponenten in Frage gestellt und in optionalen Modulen weiterentwickelt. Rails hat sein Modularisierungskonzept noch weiter in Ruby-Gems und Engines unterteilt, wobei man sich letztere wie eine Art Mini-/Standalone-App vorstellen kann, die sich in eine Hauptanwendung einbinden (mounten) lässt. Grails bietet entfernt Vergleichbares nur über seinen Plug-in-Mechanismus oder verlinkte Bibliotheken. In der Praxis unterteilt man monolithische Applikationen jedoch eher in Services auf App-Ebene und lässt sie beispielsweise über REST APIs kommunizieren.
Caching ist einer der komplizierteren Aspekte der Informatik und so bieten Rails und Grails vom SQL Query bis hin zu komplett gerenderten HTML-Seiten diverse Caching-Möglichkeiten innerhalb des Framework-Stacks. Dabei kann In-Memory als Standardoption gelten, allerdings lässt sich auch auf die üblichen Cache Stores wie Ehcache oder Redis zurückgreifen. Grails sorgt mit Annotationen und seiner Spring Bean DSL für mehr Übersichtlichkeit, um Methoden-Rückgaben besser im Cache zu lagern als es der programmatische Ansatz von Rails vorgibt.
Eine der Verbesserungen in Rails 4 lässt sich bei verschachtelten Caches finden:
Auch wenn in beiden Frameworks Filter zur Authentifizierung und Autorisierung
leicht selbst implementiert sind, werden viele Entwickler dieses sensible Thema
gern an ein gereiftes Plug-in abgeben wollen. Das Devise-Gem
ist ein solches und wird über Mixins
in die Rails-Modellklasse eingebunden, sodass beispielsweise im Controller ein
authenticate_user!
ausreicht. Grails hat mit Spring Security nur eine
ernstzunehmende, aber sehr umfangreiche Alternative. Das Grails-Plug-in
hilft mit vereinfachter Konfiguration sowie einer DSL und bietet zudem etliche
Erweiterungen (zum Beispiel für OAuth oder LDAP).
Flügeltüren in der Aufpreisliste - ein Fazit
Während die Java-Community schon immer seriös und unternehmensorientiert daher kommt, sind Nutzer und Entwickler von Groovy und Ruby mehrheitlich pragmatisch und geben sich nur ungern mit umständlichen Workflows zufrieden. Der Umgang mit beiden Sprachen und Frameworks macht durch ihre zumutbare Lernkurve und ihre pragmatisch-tolerante Art von Anfang an Spaß. Natürlich muss man sich dabei auf das jeweilige Framework einlassen, die Ideen und Entscheidungen dahinter akzeptieren und nicht dagegen arbeiten.
Ruby ist eine ausgereifte Programmiersprache, die zusammen mit den Rails-Spracherweiterung und den JRuby-Möglichkeiten nur noch besser wird. Rails ist über die Jahre gereift und eben kein kurzlebiger Modetrend mit Kinderkrankheiten mehr. Zur richtigen Zeit, nämlich als Webentwicklung noch wirklich schmerzhaft war, konnte es mit prominenten Anwendungen und guter Community-Arbeit einen bis heute andauernden guten Ruf aufbauen. Dem Rails-Entwicklungsteam, vor allem Hansson, sagt man allerdings auch Arroganz und Engstirnigkeit bei Entscheidungen nach. Ähnlich wie in der PHP-Entwicklung unterschätzen viele gelegentlich die Einfachheit von Rails, und ordentliches Design der Komponenten und Schnittstellen wird besonders in großen oder wachsenden Projekten vernachlässigt.
Eines der größten Argumente, das für Grails spricht, ist Groovy. Die Sprache vereinfacht den Einstieg – gleichermaßen für Ruby- und Java-Entwickler – und die sich durch die Java Virtual Machine ergebenden Vorteile sind gewaltig. Sicherlich, die Community ist kleiner und Grails jünger, dafür haben deren Anwender und Entwickler oftmals ein vertieftes Java-Wissen und kennen daher viele der unter der Haube eingesetzten Techniken bereits. Gerade dieser Zweckverband der besten und umfangreichsten Java Tools macht es nicht gerade zu einem schlanken Framework und ein Austausch von Komponenten ist oftmals nicht vorgesehen oder scheitert an echten Alternativen.
Grails hat im direkten Vergleich ein engeres Korsett an Konventionen und zwingt den Entwickler, mehr über die Strukturierung seiner Anwendung nachzudenken. Wie bei Ruby ist die “Magie”, die in allem zu stecken scheint, Segen und Fluch zugleich. Zudem ist die Zukunft nach dem Ausscheiden von Pivotal als Sponsor für Groovy und Grails ungewiss.
Gibt es einen klaren Gewinner? Nein, vielmehr hängt die Entscheidung für oder gegen eines der vorgestellten Frameworks vom Projekt und seinen Rahmenbedingungen ab. Sollen Java-Entwickler mitarbeiten, die möglicherweise unerfahren mit dynamischen Sprachen sind, dafür aber fit mit Spring & Co., oder gibt es bereits eine signifikante Menge JVM-Code, geht die Empfehlung in Richtung Groovy und Grails. Umgekehrt, wenn die JVM kein Muss ist und Ruby-Vorkenntnisse und -Bibliotheken zum Einsatz kommen, spricht das eher für Ruby und Rails.
Und um es ein letztes Mal im Jargon der Autozeitschrift zu sagen: Rails ist das Webframework mit kraftvollem Turbolader und langer Aufpreisliste, während Grails eher ein vollausgestattetes Sondermodell mit großem Hubraum präsentiert. Schnelles Kurvenfahren ist mit beiden möglich.