Erfahrungen bei der Migration von Grails 1.3.x nach 2.0.x
Nachdem für das Grails-Framework die Version 2.0 im Dezember 2011 freigegeben wurde, stellt sich für bestehende Projekte, die auf älteren Grails-Versionen realisiert bzw. begonnen wurden, die Frage nach der Migration. Die Analyse, ob sich eine Migration lohnt muss für jedes Projekt individuell durchgeführt werden, da ein entsprechender Aufwand dahinter steckt. Klar ist aber auch, dass Projekte, die noch länger laufen und weiterentwickelt werden an einer Migration kaum vorbeikommen.
Warum sollte man migrieren?
Wie im User Guide [1] beschrieben gibt es mit Grails 2.0 zahlreiche Neuerungen, die entsprechende Mehrwerte bieten. Außerdem ist davon auszugehen, dass neue Plugins sowie die Weiterentwicklungen bestehender Plugins auf die Version 2.x setzen. Damit würden diese nicht zur Verfügung stehen, bzw. müssten selbst auf Tauglichkeit für ältere Version angepasst und getestet werden.
Was ist zu beachten bei der Migration?
Auf unserem Blogeintrag Grails 2.0 Upgrade Guide sind die entsprechenden Seiten zusammengefasst, mit denen man auf jeden Fall vor Beginn der Migration auseinandersetzen sollte. Darüber hinaus sind die Informationen über Änderungen, die potentiell zu Problemen nach der Migration führen, im Grails User Guide [2] hilfreich.
Neben dem geplanten Bug-Fix Release 2.0.1 gab es in den letzten Tagen zwei weitere Bug-Fix-Releases, so dass zwischenzeitlich die aktuellste Version 2.0.3 ist [3]. Mit dem Release 2.0.2 wurde eine erhebliche Security-Lücke im Bereich des Data-Binding behoben [4]. Leider schlichen sich auch ein paar gravierende Probleme mit Domain-Klassen und Entwicklungsumgebungen ein, so dass kurze Zeit später die Version 2.0.3 herausgebracht wurde.
Wenn eine Migration ansteht sollte man deshalb unbedingt auf die momentan aktuellste Grails-Version 2.0.3 migrieren.
Fallen und Probleme bei der Migration
Nachfolgend werden Hinweise gegeben und auf Probleme eingegangen, die im Rahmen von Migrationen bei exensio aufgetreten sind. Es wird nur auf ausgewählte Fälle eingegangen, da es viele Hinweise schon im Upgrade-Guide gibt.
DataSource.groovy
Wurde bisher für Entwicklung oder Tests die mitgelieferte HSQL Datenbank verwendet, dann sollte nach Möglichkeit durch Anpassung der Settings in der Datei DataSource.groovy auf die H2 Datenbank gewechselt werden. Mit H2 kann die DB-Konsole direkt aus der Grails-Webapplikation aufrufen werden.
executeQuery Methode
Schon in älteren Grails-Versionen wurde angekündigt, dass zukünftig in Domain-Klassen für die executeQuery-Methode die Parameter nicht mehr als String übergeben werden können. Mit der Version 2.0 änderte sich die Methoden-Signatur endgültig.
def rev = Template.executeQuery('from Template t where t.ui = ? and t.revision = (select max(tMax.revision) from Template tMax where tMax.ui = ? ) ', ui, ui)
Die Migration erfolgt hier einfach, in dem die Parameter wie nachfolgend aufgezeigt als Liste übergeben werden:
def rev = Template.executeQuery('from Template t where t.ui = ? and t.revision = (select max(tMax.revision) from Template tMax where tMax.ui = ? ) ', [ui, ui])
groovyPagesTemplateEngine
In Services wird die Variable groovyPagesTemplateEngine über Spring injiziert und kann für das Erzeugen von Page-Templates verwendet werden. Aus den Templates können wiederum entsprechende GSP-Seiten erzeugt werden. Bei einem migriertem Projekt wird von diesem Mechanismus intensiv Gebrauch gemacht und die entsprechenden Service-Methoden werden von Controllern und Taglibs aufgerufen. Nach der Migration startete allerdings der Server aufgrund von Initialisierungs-Problemen der Variable groovyPagesTemplateEngine durch Spring nicht mehr.
Lösung war hier die Variable groovyPagesTemplateEngine nicht mehr in den Services zu deklarieren. Stattdessen wird, wie nachfolgend dargestellt, die TemplateEngine in einer Helper-Klasse erzeugt und kann von den Services abgefragt werden.
public synchronized static GroovyPagesTemplateEngine getEngine() {
if (engine == null) {
engine = getApplicationContext().getBean(GroovyPagesTemplateEngine.BEAN_ID)
}
return engine
}
Transiente Attribute in Domain-Klassen
Bedingt durch die Behebung der Sicherheitslücke in Grails 2.0.2 fließen transiente, statische oder dynamisch getypte Variablen in Domain-Klassen standardmäßig nicht mehr in das Data-Binding ein. Um dennoch ein Data-Binding zu erreichen steht, wie unten beispielhaft skizziert, das Attribut bindable zur Verfügung.
def attachment
static transients = ['attachment']
static constraints = {
attachment bindable: true // Required since Grails 2 for tranisent attributes
}
afterInterceptor
Über den afterInterceptor [5] kann in Controller-Actions nach deren Aufruf eingegriffen werden. Bei einer bestehenden exensio-Applikation wurde bisher im afterInterceptor unter bestimmten Bedingungen ein Redirect durchgeführt.
def runAfterAction (model, modelAndView) {
// Redirect
if(model.redirectParams) {
redirect( model.redirectParams )
}
}
Nach Durchführung der Migration funktionierte der Redirect nicht mehr, bzw. führte nicht auf die gewünschten Controller. Das Problem konnte nur gelöst werden, in dem der Redirect aus dem afterInterceptor entfernt wurde und direkt am Ende der betroffenen Actions eingefügt wurde.
Abstrakte Domain-Klassen
Eine wesentliche Änderung mit Grails 2.0 ist der Umgang mit Vererbung innerhalb von Domain-Klassen. Eine abstrakte Vater-Klasse resultiert ab Grails 2.0 in einer eigenen Datenbank-Tabelle. Damit würde sich für eine migrierte Applikation das Datenbankschema ändern, was in der Regel unerwünscht ist. Außerdem machen für neu erstellte Grails 2.0 Anwendungen zentrale Vaterklassen durchaus Sinn, die nicht in einer separaten Tabelle resultieren. In diesen Klassen können bspw. Attribute wie der Version-Count, das Erstellungs- oder Änderungsdatum modelliert werden. Diese Attribute sollen direkt als Spalten in der Datenbank-Tabelle der eigentlichen Domain-Klasse resultieren, um zusätzliche JOINS zu vermeiden.
Dies kann einfach erreichr werden durch Ablage der abstrakten Domain-Klassen im src-Verzeichnis anstatt im Domain-Klassen-Verzeichnis.
Generieren von Links
In bestimmten Fällen ist es notwendig in Services Links zu generieren. Dies war bisher ziemlich umständlich, da die TagLibrary entsprechend im Service verfügbar gemacht werden musste, um dann den entsprechenden Aufruf für die Generierung durchführen zu können:
def href = g.createLink(controller:'content', action:'show', id:c.id)
Ab Grails 2.0 geht dies eleganter über die Variable LinkGenerator, die über Spring direkt injiziert wird.
def href = grailsLinkGenerator.link(controller:'content', action:'show', id:c.id)
Fazit
Die Migration ist bei größeren Projekten kein Selbstläufer und muss entsprechend eingeplant werden. Es gibt viele Hilfestellungen, man muss aber auch damit rechnen auf Probleme stoßen zu denen man im ersten Moment keine direkte Hilfe im Netz findet.
Nichtsdestotrotz macht die Version 2.0.3 jetzt einen stabilen Eindruck auf die man migrieren kann. Für den 30. April ist bereits Grails 2.1 angekündigt. Laut Jira sind für dieses Release aktuell ca. 100 Erweiterungen und Bugfixes vorgesehen. Dies zeigt, dass die Weiterentwicklung stetig voran schreitet und spätestens dann sollte man sich mit dem Thema der Migration befassen.
Links
[1] Neuerungen in Grails 2.0
[2] Upgrading from previous versions of Grails
[3] Grails 2.0.3 Release Ankündigung
[4] Secure data binding with Grails
[5] afterInterceptor