Wicket Developer Guidelines – Wicket Best Practices & Do’s and Don’ts

PHallo zusammen,

ich habe eine relativ straffe Meinung darüber, wie Wicket Komponenten auszusehen haben, wie Code auszusehen hat und an welche Guidelines sich alle Entwickler zu halten haben um möglichst einfach wiederverwendbare Wicket Komponenten zu entwickeln, die die Möglichkeiten des Frameworks möglichst effektiv nutzen.

Ich habe mir mal die Mühe gemacht, diese Guidelines niederzuschreiben, eventuell gibts ja das Eine oder andere Team, dass daran interessiert ist, ein wenig Struktur in ihren Wicket Code zu bringen.

Natürlich können diese Guidelines beliebig angepasst werden, ich würde mich über Rückmeldungen freuen.

Eine wichtige Anmerkung: Dieser Artikel ist keine Einführung in das Wicket Framework, ich gehe nicht auf alle Forderungen in den Guidelines ein, sondern dies sind Fakten:).

Diese Fakten werde ich vielleicht in anderen Artikeln erläutern, aber hier sind nur die nackten Regeln, die für eine effektive Arbeit mit dem Framework einzuhalten sind.

Guideline #1 – Komponenten haben eine feste Schnittstelle nach außen

Zur Komponentenschnittstelle zählt zunächst der Konstruktor, der immer mindestens die beiden Parameter “id” und ein entsprechendes Model erwartet.


public UserPanel(String id, IModel model){/* ... */}

Zur API (Definition hier, Methoden die verwendet und auch überschrieben werden können) zählen folgende Methoden:


public boolean isVisible(){

User user = (User)getDefaultModelObject();
return user.isAdmin();

}

Diese Methode kann dann überschrieben werden, wenn die Sichtbarkeit der Komponente auf eine bestimmte Bedingung limitiert werden soll (zum Beispiel der User muss vom Typ Admin sein).


public boolean isEnabled(){

User user = (User)getDefaultModelObject();
      return user.isAdmin();

}

Analog isVisible, nur gehts hier im das Enablement.


public void onConfigure(){}

Es ist garantiert, dass diese Methode nur einmal aufgerufen wird, hier kann beispielsweise auf performante Art und Weise die Sichtbarkeit oder das Enablement gesetzt werden.


public void onInitialize(){}

Diese Methode kann für die Initialisierung einer Komponente verwendet werden, diese Methode wird genau einmal beim Erzeugen der Komponente aufgerufen. Normalerweise wird der Konstruktor hierfür verwendet. Diese kann stattdessen verwendet werden.


public void onDetach(){}

Diese Methode wird aufgerufen, wenn die Komponente detacht wird (typischerweise am Ende eines Requests, aber nicht zwingend nur dann). Hier können zusätzliche Aufräumarbeiten erledigt werden, wie beispielsweise Instanzvariablen aufräumen oder ähnliches.

Es gibt viele andere Methoden, die aber nur in Ausnahmefällen überschrieben werden sollten (onBeforeRender, onComponentTag etc.). Die Erfahrung hat gezeigt, dass es nur in den seltensten Methoden notwendig ist, mit diesen Methoden zu arbeiten. Normalerweise deutet dies auf ein fehlendes Verständnis der Funktionsweise hin und sollte im Team besprochen werden.

Guideline #2 – Komponenten arbeiten mit Models

Wie bereist in Guideline #1 erwähnt erwartet jede Komponente mindestens ein Model im Konstruktor.


public UserPanel(String id, IModel model){/* ... */}

Ein schlechtes Beispiel:


public UserPanel(String id, User user){/* ... */}

Es werden niemals direkte Datentypen als Parameter in eine Komponenten gegeben. Der Datenfluß für Komponenten wird über Models gesteuert.
Models integrieren sich in den Lifecycle von Wicket Komponenten und bieten zusätzlich Funktionen wie DataBinding, Codereduktion, Resourcenmanagement etc.

Guideline #3 – Komponenten sind strukturiert

Normalerweise werden Komponenten im Konstruktor initialisiert, beispielsweise so:


public class UserPanel{

public UserPanel(String id, IModel model){

super(id, model);
   BirthdatePanel bdPanel = new BirthDatePanel("birthDate", new PropertyModel(model,"birthdate"));
   /*
      Konvertierungslogik, Validierungslogik etc..
   */
   add(bdPanel);

}

}

Dies macht den Konstruktor sehr unübersichtlich und schwer lesbar. Es empfiehlt sich, die Konstruktion von Komponenten in eigene Methoden auszulagern.

public UserPanel(String id, IModel model){

super(id, model);
   add(birthDayPanel());
}

private BirthdayPanel birthDayPanel(){
BirthdatePanel bdPanel = new BirthDatePanel("birthDate", new PropertyModel(model,"birthdate"));
   /*
      Konvertierungslogik, Validierungslogik etc..
   */
   return bdPanel;
}

Hierdurch wird der Code viel besser lesbar. Folgende Attribute sind für diese Factory-Methoden verbindlich:

  • Die Methoden sind private und nicht für die Verwendung in Subklassen (protected) oder sogar in anderen Komponenten (public) zu verwenden.
  •   Die Methoden ändern nichts an der Komponentenhierarchie (es wird kein add(…) oder replace(…) aufgerufen, diese Methoden sind nur dafür zu verwenden, interne Komponenten zu erzeugen, Validators bzw. Converer hinzuzufügen etc.
  • Die Methoden fangen niemals mit Adjektiven an (nicht addBirthdayPanel sondern birthdayPanel).

Guideline #4 – Komponenten räumen hinter sich auf

Wicket Komponenten werden am Ende eines RequestCycles serialisiert. Es gibt Mechanismen, die garantieren das bestimmte Objekte nicht serialsiert werden (weil diese nicht serialisierbar werden können oder zu groß sind). Werden Objekte als Instanzvariablen gespeichert sind diese aus dem Wicket-Lifecycle herausgelöst und werden serialisiert, egal ob dies nun beabsichtigt ist oder nicht. Hier ein Beispiel:


public UserPanel{

private IModel userModel;

public UserPanel(String id, IModel model){

super(id, model);
this.model = model;

}

}

Es kann durchaus Sinn machen, Models temporär als Instanzvariablen zu speichern, beispielsweise in folgendem Beispiel:


public UserPanel{

private IModel userModel;

public UserPanel(String id, IModel model){

super(id, model);
this.model = model;
add(nameLabel());
}

private Label nameLabel(){
   return new Label("name", new PropertyModel(model,"name"));
}

}

Wird dies so implementiert, muss manuell sichergestellt werden, dass alle Komponenten und Models am Wicket Lifecycle partizipieren, und zwar indem die onDetach-Methode überschrieben wird und manuellalle Instanzvariablen wie Models und Komponenten detached werden.


public UserPanel{

private IModel userModel;

public UserPanel(String id, IModel model){

super(id, model);
this.model = model;
add(nameLabel());
}

private Label nameLabel(){
   return new Label("name", new PropertyModel(model,"name"));
}

public void onDetach(){
this.model.detach();
}

}

Guideline #5 – Komponenten haben sprechende Ids

Die wicket:idsdie vergeben werden, spiegeln die Fachlichkeit wieder, nicht die Art der Komponente. Was man sehr oft sieht:

<form wicket:id="customerForm">
<div wicket:id="customerEmailInputTextField"/>
</form>

Hier werden sowohl fachliche als auch technische Aspekte vermischt. Auf der Einen Seite geht aus der ID hervor, dass es sich um eine Möglichkeit zur Emaileingeabe handelt (email), auf der anderen Seite geht die Art der Komponente daraus hervor (inputTextField). Dies ist unnötig und mehrdeutig, folgendes Pattern hat Gültigkeit:

<form wicket:id="customerForm">
<div wicket:id="email"/>
</form>

Aus der ID der Form (customerForm) geht bereits hervor, dass es sich um eine Komponente handelt, die Kundendaten entgegennimmt. Die einzige Fachlichkeit, die jetzt für die einzelnen Komponenten noch von Belang ist, ist welche Daten über welches Eingabefeld eingegeben werden können.

<div wicket:id="email"/>
<div wicket:id="name"/>

Damit ist alles gesagt.

Guideline #6 – Komponenten sind getestet

Jede Komponente (wirklich jede!) besitzt einen ihr zugeordneten UnitTest, der zumindest die folgende Form hat:


@Test
public void testRender(){

//some logic to start the component like tester.startPanel(...)
// this test assures, that component renders and does not throw any Exceptions

}

Hierdurch wird sichergestellt, dass zumindest alle Komponenten rendern und funktionieren. Natürlich sollten Komponenten auf Herz und Nieren getestet werden, aber der obligatorische RenderTest ist Pflicht und kann nicht umgangen werden. Wer Komponenten erstellt und keinen renderTest dazu, dem sind Teamprügel sicher (zumindest wenn ich in diesem Team bin)

Guideline #7 – Komponenten sind dokumentiert

Jede Komponente ist mittels Javadoc dokumentiert (muss ich das wirklich erwähnen?). Der Javadoc erklärt kurz, knapp und präzise die Fachlichkeit der Komponente sowie mögliche Abhängigkeiten zu anderen Komponenten. Nicht mehr aber auch nicht weniger!!Hier ein Beispiel:


/**

Component renders a Form that allows to submit the following Data</pre>
<ul>
	<li>Name</li>
	<li>Adress</li>
</ul>
<pre>Important: Note that this Component uses the <code>AdressPanel</code> internally.

@author Martin Dilger

*/

public class UserPanel extends Panel {

//....

}

Dieser Javadoc beschreibt kurz was die Komponente macht und dass sie intern ein AddressPanel verwendet. Das reicht.

Guideline #8 – Komponenten sind performant

Jede Komponente ist so zu entwickeln, dass sie möglichst schonend mit den vorhandenen Ressourcen umgeht, hierzu gelten folgende Grundregeln:

Die Methode onConfigure() ist isVisible() und isEnabled() vorzuziehen, da diese nur einmalig durchlaufen wird, isVisible() und isEnabled() mehrmals.

Beispiel:


/*...*/

public boolean isEnabled(){

return ((User)getDefaultModelObject()).isAdmin()

}

public boolean isVisible(){

return ((User)getDefaultModelObject()).isAvailable()

}

/*...*/

Der Code hier sieht auf den ersten Blick völlig in Ordnung aus. Das Problem ist, dass nicht genau klar ist, welche Art Model in das Panel hingegeben wurde. Hierzu muss man wissen, dass der Aufruf IModel#getObject() extrem teuer werden kann, da Models oft geschachtelt sind (sieht Guideline #12) und im Worst-Case das innerste Model ein LoadableDetachableModel ist, dass einen ServiceCall zu einem BackendSystem macht (gut, das würde normalerweise durch einen Caching Mechanismus relativiert, aber man weiß ja nie).

Beispiel:


new PropertyModel(new PropertyModel(new LoadableDetachableUserModel(),"user"), "email").getObject()

Dieser Aufruf von getObject() würde theoretisch einen Servicecall über das LoadableDetachableUserModel machen, dann ein getObject ()auf der PropertyModel<User> und anschliessend nochmal ein getObject() auf dem PropertyModel<Email>. Die PropertyModels würden wiederum per Reflection auf die einzelnen Getter bzw. Attribute zugreifen. Sehr teuer und langsam.

Um wieder zurück auf das ursprüngliche Beispiel zu kommen:

Besserer Ansatz:


public void onConfigure(){

User user = (((User)getDefaultModelObject();

setVisible(user.isAvailable());

setEnabled(user.isAdmin());

}

Auf diese Weise wird das getObject(), und damit die beschriebene Model-Kaskade nur einmal durchlaufen, statt wie im vorigen schlechten Beispiel mindestens 6 Mal. Eine kostenlose Performancesteigerung um ~85%.

Guideline #9 – Model#getObject ist mit Bedacht zu verwenden.

Wie bereits in Guideline #8 diskutiert, ist Model#getObject aus Performancesicht mit Vorsicht zu geniessen. Ein weiteres Problem besteht darin, dass Komponenten auf den Daten ihrer Models basieren. Dazu folgendes Beispiel:


public UserPanel extends Panel {

public UserPanel(String id, IModel model){

super(id, model);

add(nameField());
add(address(model.getObject().getAdress()));

}

}

Dadurch, dass das Address-Objekt aus dem Model für das Erzeugen des AdressPanels mittels model.getObject() entpackt wird, brechen wir mit dem Lifecycle des Frameworks und die Daten des AdressPanels sind nicht unbedingt diesselben, wie die aus dem UserPanel (da wir hier zwei verschiedene Models haben).

Model#getObject() ist in folgenden Situationen aufrufbar:

  • isEnabled / isVisible / onConfigure
  • Userinteraktionen wie onClick-Handler etc.

Niemals sollte Model#getObject aufgerufen werden:

  • Aufbau von Komponenten (Konstruktor etc., wie im obigen Beispiel)
  • Konditionaler Aufbau von Komponenten

Guideline #10 – Komponenten reichen ihre Models weiter

Jede Komponente reicht ihre übergebenen Models an ihre Superklasse weiter.


public class UserPanel extends Panel {

public UserPanel(String id, IModel model){

super(id, model);

}

}

Dazu gibt es keine Ausnahme, jedes Komponente reicht ihr Model an ihre Superklasse weiter.

Guideline #11 – Models werden geschachtelt.

Gerade bei der Arbeit mit CompoundPropertyModels (die Wicket-ID ist gleichzeitig der Pfad in das Domainobjekt) kann es passieren, dass man sehr lange Wicket-IDs bekommt.

Beispiel:


add(new TextField("user.address.city.zip"));

resultiert intern in dem Aufruf model.getObject().getAddress().getCity().getZip().

Soweit möglich, sollten hier stattdessen verschachtelte Models verwendet werden.

Beispiel:


//addressModel

PropertyModel</pre>
<address>addressModel = new PropertyModel</address><address>(model, "customer.address");

PropertyModel cityModel = new PropertyModel(addressModel, "city");

PropertyModel zipModel = new PropertyModel(cityModel, "zip");

add(cityInputField(cityModel));

add(zipInputField(zipModel));

Mehrere geschachtelte Models sind langen Property-Strings vorzuziehen, vor allem dann, wenn die so erzeugten Models
mehrfach verwendet werden können.

Hinweis:

Das war das Minimum-Set an Guidelines, an die sich Wicket-Entwickler meiner Ansicht nach zu halten haben. Falls ihr diese Guidelines verwendet / weiterentwickelt würde ich mich über Rückmeldung hierzu freuen.

Links:

Hier noch einige Links mit zusätzlichen Informationen:

Wicket Gotchas

Wicket Best Practices – Ganz ähnlich zu diesem Artikel (mit einigen Unterschieden), ansonsten gehen die Infos sogar noch ein wenig tiefer.

Wicket Best Practices (Ralf Ebert)

Wicket Documentation

Wicket Ajax


War dieser Blogeintrag für Sie interessant? Evtl. kann ich noch mehr für Sie tun.

Trainings & Know-How aus der Praxis zu

  • Apache Wicket 1.4.x, 1.5.x, 1.6.x
  • GIT – Best Practices, Einsatz, Methoden
  • Spring
  • Java
  • Scrum & Kanban
  • Agiles Arbeiten
Consulting & Softwareentwicklung

  • Requirements Engineering
  • Qualitätssicherung
  • Software-Entwicklung
  • Architektur
  • Scrum & Kanban

3 Gedanken zu „Wicket Developer Guidelines – Wicket Best Practices & Do’s and Don’ts

  1. B. Frank

    bei Guideline #3 wird das BirthdayPanel zweimal hinzugefuegt.
    einmal bei ..add(birthDayPanel()); … und dann bei … add(bdPanel);…

    Es wuerde sich anbieten diese Factorymethoden als static zu deklarieren, um solche Fehler zu vermeiden. z.B. private static BirthdayPanel birthDayPanel()

    Ansonsten: sehr interrasanter Artikel…

    Antwort
  2. Pingback: Wicket in meinem MoinMoin Wiki | Hotchpotch Blog

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ photo

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s