Quelle aller Worte: Internationalisierung mit GWT UIBinder

10. September 2011

Als Gott sah was beim Turmbau zu Babel passierte, entschied er sich die Sprache der Menschen zu teilen, so dass der Eine den Anderen nicht mehr verstehen möge. Ähnlich scheinen sich die Designer von GWT gefühlt haben, als sie die Internationalisierung von GWT 2.x entworfen haben.

Das Internationalisierungsprinzip von GWT ist an sich sehr einfach. Man nehme ein paar properties-Dateien, inkludiere das I18N-Modul und lasse sich die entsprechende Java-Datei generieren. Soweit so gut. Leider nicht so, wenn man den UIBinder verwenden möchte. Hier haben sich bereits diverse verzweifelte Entwickler einen “abgebrochen” um herauszufinden, welche Dateien an welcher Stelle liegen müssen, damit man in den jeweiligen ui.xml-Dateien eine vernünftige Übersetzung erhält.

Grundsätzlich gilt, dass Übersetzungs-Keys aus ui.xml-Dateien immer im Package com.google.gwt.i18n.client mit dem Namen LocalizableResource_[locale].properties liegen müssen. Diesen Umstand verdanken wir dem MessagesWriter, der vom UiBinderGenerator herangezogen wird. Dieser generiert zwar die Messages-Dateien für alle Keys, die sich in einer ui.xml-Datei befinden in eine eigene Messages-Klasse, importiert aber mittels

writer.write("import static com.google.gwt.i18n.client.LocalizableResource.*;");

ausschließlich die Übersetzungen aus LocalizableResource. Soweit so gut, möchte man meinen. Problematisch wird es dann, wenn man Übersetzungen sowohl im Java-Code, als auch im in den ui-Dateien haben will. Typisches Beispiel sind hier Tabellen vom Typ CellTable, deren Spalten normalerweise in den View-Klassen definiert werden. Will man hier nun sprachlich angepasste Spaltenköpfe haben, muss man diese mittels einer Messages-Klasse übersetzen.
In diesem Moment steht man vor der Frage: Möchte ich zwei Übersetzungsdateien verwalten, in denen ggf. Keys doppelt auftreten, oder möchte ich nur Eine für beide Mechanismen haben?

Ohne auf die unsäglichen und teilweise unpraktikablen Varianten einzugehen, die sich im Netz bereits befinden bzw. die kryptisch auf der GWT-Dokumentation vorgestellt wurde, stelle ich hier meine zwei Lösungen vor, die ich für sinnvoll erachte.

Variante 1: ui:with

Die klassische Variante. Man nehme einfach die existierenden Messages und importiere sie wie folgt in die ui.xml-Datei.

 <ui:with field='messages' type='com.my.app.MyMessages'/>
 <g:Label text="{messages.hello}" />

Diese Variante hat den Vorteil, dass man auf die komplette Bandbreite der Erweiterungen, die für Messages-Klassen bereitgestellt wurde zugreifen kann.

Variante 2: Customized Messages-Interface

In Variante 1 nutzen wir den Mechanismus, der eigentlich für den Java-Code gedacht war für den UiBinder. Natürlich geht es auch anders herum. Wir “kopieren” einfach den Mechanismus der MessagesWriter-Klasse. Wir bauen uns ein Interface und importieren die LocalizableResources.

package com.mypackage;

import static com.google.gwt.i18n.client.LocalizableResource.*;

public interface MyAppMessages extends Messages {
  @DefaultMessage("Example")
  String example();
}

Der GWT-Compiler greift hier einfach das Interface und verknüpft die Methoden mit den Keys aus der jeweiligen LocalizableResource-Datei. Damit lassen sich auch innerhalb von Java-Dateien die Keys aus LocalizableResource verwenden.

Achtung:
Leider gibt es nicht die Möglichkeit LocalizableResource-Dateien über den i18n-Aufruf in eine Klasse übersetzen zu lassen, da LocalizableResource bereits ein existierendes Interface ist und die Übersetzung zu einer sogenannten zyklischen Vererbung führt.

Fazit

Mit beiden Varianten lässt sich das bestehende Problem lösen, dass man nur einen Mechanismus zur Internationalisierung innerhalb von GWT-Projekten nutzen möchte. Welche der beiden zum Einsatz kommt hängt stark von der bestehenden Struktur und – was nicht zur vernachlässigen ist – von der Motivation des Entwicklers, welche Schritte für ihn in seinem Arbeitsprozess besser integriert werden können, ab. Während Variante 1 den Aufruf des i18n-Creators erfordert, muss in Variante 2 das Interface immer manuell angepasst werden.

Trügerische Sicherheit: Fehlermeldungen ohne Kontextinformationen

30. Juni 2011

Die Applikation. Unendliche Weiten. … *räusper*

So schlimm ist die Welt der Software-Entwicklung nun zwar auch nicht, dass man blindlinks irgendwo im Dunkeln rumdüst und völlig überraschend in einer Raumzeit-Anomalie hängt oder von Q für die Verbrechen der Menschheit angeklagt wird.
Aber gerade bei neuen Software-System hat man viele Stellen noch nicht berührt und muss sich häufig auf bereits existierendes Verhalten verlassen, wenn man neue Komponenten integriert. So geschehen heute. Ich möchte kurz vorstellen – die Aufgabe:

Rufe via REST eine URL auf, die den Status einer Entity umsetzt.

Zusätzliche Herausforderung:

Kann sein, dass die Gegenstelle nicht da ist.

Es folgte eine Odyssee von URL-Anpassungen. So mach ich mich daran, den Aufruf zu implementieren und bekomme ein einziges Wort zurück “StatusNotAllowedException“. Ich denk mir so: “Ok, das hört sich doch gut an.”, da ich wusste, dass die Status-Änderung mit dem aktuellen Objekt wirklich nicht erlaubt bzw. sein sollte. Witzigerweise war aber das Problem, dass ich einen völlig falschen Call getriggert hatte – klassisch Copy&Paste. Erst nachdem ich verkündete, dass der Aufruf implementiert sei, wurde ich aufgeklärt, dass die Antwort nicht korrekt ist. Nach kurzem scharfen Hinsehen war die URL dann auch schnell ausgetauscht.

Also ging es auf zu Runde 2. Wie bereits angesprochen gab es ja noch die Nebeninfo, dass evtl. die Empfängerstelle noch nicht implementiert ist. Darum gab es auch hier wieder für mich keinen wirklichen Argwohn als die Schnittstelle eine 404 zurücklieferte. Vorgewärmt von der ersten Fehlermeldung hab ich gleich gefragt, ob die Empfängerseite wirklich nicht da sei, worauf ein “doch, ist da” zurückgeliefert wurde.
Also schauen wir uns die URL an und entdecken: Oha, die Ports sind falsch. Aber nicht nur bei einer URL, sondern bei allen. Auch das geändert und somit kommen wir zu …

… Runde 3: Fehlermeldungen, die nicht da auftauchen wo sie erwartet werden. Leider gab es dann noch einen Schusselfehler bei der Änderung der Ports, so dass ein Aufruf an den alten Port geschickt wurde und mit unverhohlener Freundlichkeit meinte “kenn ich nicht”. In meinem Gesicht manifestierte sich die Frage “was kennst du nicht?”. In meinem Log tauchte nichts auf, im Log auf dem Server, auf dem wir dachten, dass der Aufruf aufschlagen sollte kam auch nichts. Zwar lag das eigentliche Problem am falschen Port, aber die Fehlermeldung sagte nichts darüber aus, was gerade schief gelaufen ist. Auch im Log des eigentlichen Servers – da wo der Call gelandet war – stand nichts aussagekräftiges. Am Ende half nur das 4-Augen-Prinzip um den Fehler zu finden.

Fazit: Ein Hoch auf die verbale Kommunikation und ein leidiges Buh auf schlecht formulierte Fehlermeldungen. Zwar muss man schon einiges an Schusseligkeit an den Tag legen um so viele Fehler hintereinander zu machen – und es passiert doch – aber gerade beim Ersten hätte es beinahe dazu geführt, dass ich mich in einer Sicherheit wähnte, die gar nicht da war und die uns im Produktiv-System ggf. teuer hätte zu stehen kommen können. Darum sollten Fehlermeldungen stets so viel Informationen enthalten, dass sie das eigentliche Problem konkret darstellen und entweder mögliche Ursachen oder mögliche Lösungen vorschlagen.

Warum “agile” nicht immer agil ist …

13. Juni 2011

Nach mehreren Jahren (angeblicher) agiler Softwareentwicklung ist es interessant zu sehen, wie der Begriff “agile” gerne zum Motto eines chaotischen und unkoordinierbaren Gewusels werden kann.

Aber bevor ich in einen Rausch von Anschuldigungen, Verfluchungen und verzweifelten Rundumschlägen verfalle – eine kurze Vorstellung von dem was agile Softwareentwicklung leisten soll.
Wikipedia beschreibt die Zielsetzung von agiler Softwareentwicklung wie folgt:

Das Ziel Agiler Softwareentwicklung ist es, den Softwareentwicklungsprozess flexibler und schlanker zu machen, als das bei den klassischen Vorgehensmodellen der Fall ist. Man möchte sich mehr auf die zu erreichenden Ziele fokussieren und auf technische und soziale Probleme bei der Softwareentwicklung eingehen. Die Agile Softwareentwicklung ist eine Gegenbewegung zu den oft als schwergewichtig und bürokratisch angesehenen traditionellen Softwareentwicklungsprozessen wie dem Rational Unified Process oder dem V-Modell.

Häufig wird sich vor allem auf die ersten beiden Sätze konzentriert, die selbstverständlich eine große Bedeutung haben. Aber aus meiner Sicht hat aber der letzte Satz eine interessante Nebenbedeutung.
Im Folgenden werde ich mich auf die Vorgehensweise nach Scrum konzentrieren, da sie meist als Vorzeige-Prozess verwendet wird und sich viele Firmen dies auf die “Fahnen” schreiben.
Agile Softwarentwicklung wird als Gegenbewegung zu bürokratischen Prozessen angesehen. Was hier nur häufig scheinbar hinzugefügt wird, ist das Abschaffen von (notwendigen) formalen Regeln.

  • So ignoriert man des öfteren, dass ein Sprint in sich unveränderbar ist – das dies auch legitim sein kann, werde ich gleich erläutern.
  • Daily Scrums z.B. werden teilweise wie ein Kaffeeklatsch behandelt oder – noch schlimmer – werden als Plattform für das Management verwendet um dem Team neue Priorisierungen mitzuteilen.
  • Beliebt sind auch Sprintplanungen, in denen dem Team vorgeschrieben wird, wieviel Zeit es für welche Aufgaben haben darf.
  • Teammitglieder erscheinen gar nicht oder zu spät zu Meetings.
  • Daily Scrums beinhalten entweder zu viele oder zu wenige “Pigs” bzw. “Chickens” verhalten sich wie “Pigs”.
  • Sprintziele bzw. Commitments werden nicht ernst genommen.

Eine kleine Übersicht über Anhaltspunkte, dass es mit dem agilen Prozess nicht ganz so gut bestellt ist, gibt folgende Seite über Scrum-Smells.

Die Konsequenzen, wenn der “Gestank” (dt. für Smell) nicht berücksichtigt wird, sind einerseits lange Entwicklungszeiten und häufige Wartungsaktionen, da die Anforderungen nicht klar waren/sind oder Aufgaben permanent verschoben werden und andererseits Versuche Probleme mit klassisch bürokratischen Mitteln zu kompensieren, wie minutiös ausgearbeiteten Konzepten. Dadurch verlangsamt sich zusehends und dramatisch die Entwicklungszeit und damit die Reaktionszeit auf Kundenwünsche und Bugs. Ebenso wird die Flexibilität des Teams auf die reine Programmierung reduziert, die dann ggf. noch durch (von sich selbst übermäßig überzeugte) Code-Reviewer ad absurdum geführt wird.

Da dies kein komplettes Buch über die Vor- und Nachteile von Scrum respektive agiler Softwareentwicklung werden soll, möchte ich nur Folgendes festhalten:

Agile Softwareentwicklung besitzt genausoviel oder teils sogar mehr Formalismus als klassische Softwareentwicklung. Allerdings wird versucht den bürokratischen Aufwand zu reduzieren. Agil heisst hier vor allem, dass das Team wesentlich flexibler agieren und reagieren kann, als es in klassischen Prozessen ist und nicht, dass die Aufgabenstellung beliebig agil geändert werden kann. Dieser erhöhte Formalismus bedarf aber ebenso einer durchgängigen Disziplin aller Beteiligten. Ist diese Disziplin gegeben lassen sich auch innerhalb einer Iteration die Anforderungen bis zu einem bestimmten Grad ändern. Falls nicht, ist davon – wie es im Allgemeinen in der Literatur vorgegeben ist – abzuraten.

Java + Groovy: Sample-Project PdfRenderer auf Github

29. Dezember 2010

Das Buch “Groovy in Action” führt den Begriff “Beauty through Brevity” ins Feld, der auch gern in Verbindung mit Scala oder Clojure einhergeht. Lässt man diese Sprachen für sich alleine mag das auch gelten, doch für viele Entwicklungen, die noch mit älterem Java-Code arbeiten, ist es wichtig, dass die Kombination verschiedener Sprachen ein stimmiges Gesamtbild abgibt – und hier lauern gern die ein oder anderen Stolperdrähte.

Unter https://github.com/Zigu/PdfRenderer habe ich ein älteres Projekt von mir in ein neues “Misch-Gewand” geworfen und geschaut ob es funktioniert. Und ja, das tut es – mit Einschränkungen.

Grundsätzlich kann man sagen, dass die Kombination Maven2 + Java + Groovy eine sehr angenehme Sache ist, wenngleich der Weg dorthin etwas dornig war. Insbesondere durch die maßlos veraltete Dokumentation von GMaven – ich verzichte bewusst auf eine Verlinkung – wodurch nicht klar war, wie einfach es eigentlich ist Maven dazu zu bringen auch Groovy-Code zu verarbeiten.

Vorsicht ist allerdings geboten beim Einsatz von Closures in Vererbungshierarchien. Es gibt in Groovy 1.7.6 noch ein Problem, wenn eine konkrete Kindklasse aufgerufen wird, deren abstrakte Oberklasse ein Closure enthält, dass private Felder dieser Elternklasse verwendet. Eine kurze Skizzierung des Problems:

abstract class A {
  private List _myList;
  private ListElementHandler _handler;

  public void handleList() {
    _myList.each{ _handler.do_something_with(it) }
  }
}

class B extends A { ... }

class C {
  public void call() {
    def b = new B()
    b.handleList()
  }
}

Leider kommt es im Zuge des Abarbeitung der Klasse C zu einer Exception, dass das Feld _handler nicht zur Klasse B gehört. Dieses Problem ist bereits in den Groovy-Bugtracker aufgenommen worden.

Trotz dieser kleineren Hürden, verlief die Umstrukturierung ziemlich glatt und am Ende entstand größtenteils ein stimmiges Gesamtbild mit einigen sehr angenehmen “Abkürzungen” dank Groovy.

Erster Blick auf GWT 2.1.1 Requestfactory

16. Dezember 2010

Vor einigen Tagen wurde der erste Release-Kandidat for GWT 2.1.1 veröffentlicht. Was dieser enthält kann man hier nachlesen.

Nachdem ich bereits über einen Workaround für GWT 2.1 bezüglich des RequestFactory-Einsatzes mit Spring gesprochen habe war für mich vor allem das neue RequestFactory-API interessant. Dieses hat jetzt in u.a. in der Service-Annotation einen locator mit dem man auf Klassen referenzieren kann, die keine statischen Methoden enthalten. Ein schönes Beispiel für den Einsatz findet sich in der GWT Google Group (zugehöriger Thread).

Der folgende Auszug ist aus dem eben genannten Thread.

public class SpringServiceLocator implements ServiceLocator {
        @Override
        public Object getInstance(Class<?> clazz) {
                HttpServletRequest request = RequestFactoryServlet.getThreadLocalRequest();
                ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getSession().ge tServletContext());
                return context.getBean(clazz);
        }
}

“I annotate my RequestContext with the GWT @Service annotation (not the
Spring one). Example below:”

@Service(locator = SpringServiceLocator.class, value =
AddressService.class)
public interface AddressRequest extends RequestContext {
        // Get address by id
        Request<AddressProxy> get(Long id);
}