Wicket 6 – Spaß mit IResource?

Hallo zusammen,

öfter höre ich in meinen Workshops und Vorträgen die Frage, wozu man eine IResource braucht.Da muss ich nicht lange überlegen.

Nehmen wir als Beispiel folgendes Requirement – wir möchten ein QR-Code dynamisch innerhalb unserer Anwendung generieren und zur Anzeige bringen.

Funktioniert über ein Servlet – prinzipiell – hiermit verlassen wir aber die Wicket-Welt und begehen einen Technologiebruch, der nicht notwendig ist. Es geht einfacher, und zwar mit IResource.

Zunächst erzeugen wir uns einen neuen Maven-Archetype.


mvn archetype:generate -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=6.3.0 -DgroupId=de.effectivetrainings -DartifactId=wicket-6-resources -DarchetypeRepository=https://repository.apache.org/ -DinteractiveMode=false

In der pom.xml konfigurieren wir uns noch eine Abhängigkeit auf IText, eine recht nette Bibliothek zum generieren von Barcodes aller Art.


com.itextpdf
itextpdf
5.2.0

Wir schreiben uns zunächst einen einfachen Barcode-Generator. Das ist nichts anderes als die IText Standard-API und deswegen gehe ich nicht näher darauf ein.


/**
* @author martin@effectivetrainings.de
*         Date: 29.11.12
*         Time: 21:42
*/
public class BarCodeGenerator {

public BufferedImage generate(String code)  {
BarcodeQRCode qrCode = new BarcodeQRCode(code,300,300, new HashMap<EncodeHintType, Object>());
java.awt.Image image = qrCode.createAwtImage(Color.BLACK,Color.WHITE);
return toBufferedImage(image);
}

public byte[] asRawBytes(String code){
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try {
ImageIO.write(generate(code), "png",bout);
return bout.toByteArray();
} catch (Exception e) {
//here you should throw some kind of exception
return new byte[0];
}
}

private BufferedImage toBufferedImage(java.awt.Image image){
BufferedImage bi = new BufferedImage(image.getWidth(null),image.getHeight(null),BufferedImage.TYPE_INT_RGB);
Graphics bg = bi.getGraphics();
bg.drawImage(image, 0, 0, null);
bg.dispose();
return bi;

}

Zum Beweis, dass das Ganze funktioniert schreiben wir uns einen sehr einfachen Test. Achtung, dieser Test schreibt eine Testdatei in euer /tmp Verzeichnis. Das ist kein echter Unit-Test, sondern nur ein Smoke-Test um zu prüfen, ob die Generierung tut.

public class BarCodeGeneratorTest {

@Test
public void testGenerateQRCode() throws Exception{
byte[] raw = new BarCodeGenerator().asRawBytes("http://www.effectivetrainings.de");
File outputfile = new File("/tmp/wicket-6-resource-example.png");
FileOutputStream fout = new FileOutputStream(outputfile);
fout.write(raw);

}
}

Der Test macht genau, was wir erwarten würden und generiert in /tmp/ folgende Datei, die mit
jedem aktuellen Smartphone gescannt werden kann.
Ok, damit ist schon viel geschafft, wie aber bringen wir dieses „Bild“ jetzt in unserer Webanwendung zur Anzeige.

Der langweilige  Weg wäre, hierfür ein Servlet zu schreiben und dieses Bild direkt in die Response zu rendern. Viel besser ist, das Ganze in eine IResource zu verpacken.

Da dies ein Standard-Use-Case ist, bringt Wicket hier natürlich schon etwas fertiges mit. Wir erzeugen uns einfach folgene Klasse:


public class QRResource extends DynamicImageResource {
@Override
protected byte[] getImageData(Attributes attributes) {
return new byte[0];
}
}

Die Klasse DynamicImageResource macht genau das, was wir brauchen und bietet schon eine Methode „getImageData“ die zufälligerweise bereits ein byte-array erwartet. Die Implementierung sieht dann so aus:


public class QRResource extends DynamicImageResource {

private BarCodeGenerator generator;

public QRResource(){
generator = new BarCodeGenerator();
}

@Override
protected byte[] getImageData(Attributes attributes) {
return generator.asRawBytes("woher kriegen wir den Code?");
}

public static ResourceReference asReference(){
    return new ResourceReference("qr"){

        @Override
        public IResource getResource() {
                return new QRResource();
        }
    };
  }
}

Die Frage ist nur, woher wir jetzt den Code bekommen, den wir eigentlich in das Bild kodieren möchten. Jetzt endlich kommt Wicket-6 so richtig in Fahrt, denn seid 1.5 kann man Resourcen mounten. Was bedeutet mounten? Die Resource bekommt eine feste URL. Außerdem kann ich Requests an diese Resource schicken kann – Und Requests beinhalten Parameter.

Die Resource mounten machen analog beispielsweise WebPages  in der Application-Klasse.


/**
* @see org.apache.wicket.Application#init()
*/
@Override
public void init()
{
super.init();
mountResource("/qr/${code}", QRResource.asReference());

// add your configuration here
}

Die mountResource-Methode erwartet nicht direkt eine Resource sondern eine ResourceReference, deren Erzeugung wir gleich mit in die QRResource und die Methode „asReference“ verpackt haben.

Das Schöne ist tatsächlich, dass wir mit dem dynamischen Pfad alles haben, was wir brauchen um unsere Webanwendung um dynamische QR-Codes zu erweitern.

Was noch fehlt ist das Auslesen des Parameters in der Resource, noch fehlt uns der eigentliche Text, den wir in den QRCode kodieren möchten.

Die Methode getImageData bekommt als Parameter eine Klasse vom Type „Attributes“. Attributes bietet folgende Methoden.

Wunderschön, wir sehen, wir bekommen direkt Zugriff auf die allseits beliebten PageParameter.

Bauen wir das Ganze in unsere Anwendung ein. Wir löschen einfach alles, was wir nicht brauchen und enden mit einer leeren Page und einem minimalistischen Markup.

public class HomePage extends WebPage {
private static final long serialVersionUID = 1L;

public HomePage(final PageParameters parameters) {
super(parameters);
}
}


<img alt="" src="/qr/effectivetrainings.de" />

Wir haben nicht mal eine „wicket:id“:). Starten wir das Ganze mit der beigefügten Start-Klasse sieht man folgendes.

effective trainings url

Alle Sourcen finden Sich wie immer in meinem Wicket-6-Playground-Repo.

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

Effective Trainings Wicket Workshop

Wicket 6.2, WebSockets und JQuery-Visualize – Die richtige Atmosphäre schaffen

Im letzten Artikel zum Thema Wicket 6 ging es um JQuery und Ajax. Heute geht es um die Native WebSockets Integration – Rock´n´Roll!

Zunächst erzeugen wir uns wieder einen Wicket Maven Archetype.

Das Projekt erzeugen


<code>mvn archetype:generate -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=6.2.0 -DgroupId=de.effectivetrainings -DartifactId=wicket-6-websockets -DarchetypeRepository=<a href="https://repository.apache.org/">https://repository.apache.org/</a> -DinteractiveMode=false</code>

Wieso braucht man überhaupt so etwas wie WebSockets und warum sind sie hier?

WebSockets setzen direkt auf TCP/IP auf und ermöglichen eine bidirektionale Kommunikation zwischen Client und Server – toll… und?

Auf gut deutsch gesagt, wir haben die Möglichkeit, Nachrichten vom Server zum Client zu schicken und zwar ohne, dass der Client (Browser) ständig Pollen (Beim Server anfragen) muss.

Was wäre ein einfacher Use-Case?

Beispielsweise kann man sich vorstellen, wir haben eine Webanwendung, die von unserem Kunden verwendet wird, um Daten zu visualisieren, beispielsweise eingegangene Bestellungen in unserem Online-Shop.

Ohne WebSockets müsste man entweder jedesmal die Seite neu laden, um an die aktuellen Daten zu kommen oder via Ajax (Magie..) ständig beim Server anfragen ob denn nicht zufällig neue Daten vorhanden sind. Beides funktioniert, hat sich etabliert, wurde schon millionenfach implementiert und funktioniert – schick ist eben aber was anderes.

Man kann sich eine WebSocket-Implementierung einfach so vorstellen, dass ein Client eine Anfrage an den Server schickt, diese Anfrage wird aber nicht sofort beantwortet, sondern der Server wartet damit, bis tatsächlich eine Antwort Sinn macht (neue Daten vorhanden sind, die den Client interessieren). Sobald die Sinnhaftigkeit geklärt ist schickt der Server eine Nachricht an alle interessierten Clients und diese können die Visualisierung aktualisieren. Macht Sinn? Definitiv.

Wicket und WebSockets

Wir sind uns denke ich alle einig, dass Wicket das beste Web-Framework ist, das derzeit am Markt verfügbar ist (keine Diskussion!). Wicket 6 hat hier nochmal einen richtigen Schub an schönen Features gebracht – u.a. eben die Native-WebSocket-Integration.

Beispiel

Wir haben uns ja bereits einen wunderschönen Archetype generiert. Wir haben uns auch schon einen brauchbaren Use-Case ausgedacht – Visualisierung von Bestellungen am Client. Implementieren wir das Ganze.

WebSockets aktivieren

Um WebSockets in einer Wicket-Anwendung zu aktivieren verwendet Wicket selbstverständlich ein Behavior..

Bauen wir uns zunächst eine sehr einfaches Formular, mit dem wir Bestellungen simulieren können. Im Archetype gibt es ja bereits die Klasse HomePage. Diese passen wir einfach folgendermaßen an (da es sich hierbei um Wicket Standard Bordmittel handelt, gehe ich darauf nicht genauer ein, es soll ja schließlich um WebSockets gehen).

Wir brauchen ausserdem einige einfache Domainklassen –  Food und Order (wir bauen einen Lieferservice für Essen – nein nicht Pizza – dafür gibts schon genug Beispiele).


public enum Food {
PIZZA, LEBERKAS, BURGER, SALAT, SPIEGELEI
}

public class Order implements Serializable {

//some meaningless random order id
private String orderId = String.valueOf(Math.random());
private String name;
private String street;
private String zip;
private String city;
private Food food;

public Order(){}

public String getOrderId() {
return orderId;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getStreet() {
return street;
}

public void setStreet(String street) {
this.street = street;
}

public String getZip() {
return zip;
}

public void setZip(String zip) {
this.zip = zip;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public Food getFood() {
return food;
}

public void setFood(Food food) {
this.food = food;
}

@Override
public String toString() {
return "Order{" +
"name='" + name + '\'' +
", street='" + street + '\'' +
", zip='" + zip + '\'' +
", city='" + city + '\'' +
", food=" + food +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

Order order = (Order) o;

if (city != null ? !city.equals(order.city) : order.city != null) return false;
if (food != order.food) return false;
if (name != null ? !name.equals(order.name) : order.name != null) return false;
if (orderId != null ? !orderId.equals(order.orderId) : order.orderId != null) return false;
if (street != null ? !street.equals(order.street) : order.street != null) return false;
if (zip != null ? !zip.equals(order.zip) : order.zip != null) return false;

return true;
}

@Override
public int hashCode() {
int result = orderId != null ? orderId.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (street != null ? street.hashCode() : 0);
result = 31 * result + (zip != null ? zip.hashCode() : 0);
result = 31 * result + (city != null ? city.hashCode() : 0);
result = 31 * result + (food != null ? food.hashCode() : 0);
return result;
}
}

Über die Sinnhaftigkeit dieser Domainklassen würde sich vortrefflich streiten lassen, für das Beispiel ist das aber ausreichend.

Hier jetzt die einfache Wicket Implementierung des Formulars.


public class HomePage extends WebPage {
private static final long serialVersionUID = 1L;

public HomePage(final PageParameters parameters) {
super(parameters);
Form orderForm = new Form("form", new             CompoundPropertyModel(new Order())){
@Override
protected void onSubmit() {
super.onSubmit();
System.out.println(getModelObject());
}
};
orderForm.add(new TextField("name"));
orderForm.add(new TextField("street"));
orderForm.add(new TextField("zip"));
orderForm.add(new TextField("city"));
orderForm.add(new DropDownChoice("food", Arrays.asList(Food.values())));
add(orderForm);
}
}

und das ensprechende Markup dazu.

<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
<meta charset="utf-8"/>
<title>Apache Wicket Quickstart</title>
<link href='http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:regular,bold' rel='stylesheet'
type='text/css'/>
<link rel="stylesheet" href="style.css" type="text/css" media="screen" title="Stylesheet"/>
<style>
fieldset {
width: 350px;
float: left;
text-align: right;
}
</style>
</head>
<body>
<form style="margin: 25px" wicket:id="form">
<h2>Wo gibts das beste Mittagessen?</h2>
<fieldset>
<label>Name:</label> <input type="text" wicket:id="name"/><br/>
<label>Strasse:</label> <input type="text" wicket:id="street"/><br/>
<label>PLZ: </label><input type="text" wicket:id="zip"/><br/>
<label>Stadt:</label> <input type="text" wicket:id="city"/><br/>
<label>Was möchtest Du essen?</label> <select wicket:id="food"/><br/>
<input type="submit"/>
</fieldset>
</form>
</body>
</html>

Das Ganze ergibt ausgeführt das, schick nich?

Ziel ist es, unsere eingegangenen Bestellungen zu visualisieren. Das halten wir möglichst einfach (schön wäre es jetzt, wenn wir eine lokale DB hochziehen würden, hier schön die Bestellungen via Hibernate persistieren etc..). Das sparen wir uns, wir nehmen die einfachste Datenbank, die man sich vorstellen kann – ein gutes altes Singleton mit einer gekapselten HashMap (ist es hier gerade jemandem kalt den Rücken hinunter gelaufen?).


public class DB {

private static DB instance = new DB();

private Map<String, List<Order>> orders;

private DB() {
 this.orders = Collections.synchronizedMap(new HashMap<String, List<Order>>());
 }

public void store(Order order) {
 List<Order> orders = this.orders.get(order.getFood().name());
 if(orders == null){
 this.orders.put(order.getFood().name(),new ArrayList<Order>());
 }
 this.orders.get(order.getFood().name()).add(order);

}

/*
 * do not copy that....
 * */
 public Map<String, Object> countOrdersByFood() {
 Map<String, Object> foodCount = new HashMap<String, Object>();
 for(Map.Entry<String, List<Order>> order : this.orders.entrySet()){
 foodCount.put(order.getKey(),order.getValue().size());
 }
 return foodCount;
 }

public static final DB get() {
 return instance;
 }
 }

Bitte nicht kopieren, hässlicher gehts kaum… Das interessante an unserer „DB“ ist die Methode „countOrdersByFood“ die eine Map zurückgibt, die die Anzahl von Bestellungen für bestimmte Gerichte beinhaltet. Genau diese Methode verwenden wir später für unsere Visualisierung. Dass der Rückgabewert eine Map vom Typ <String,Object> ist, hat mit der Convenience-Klasse JsonUtils zu tun, die standardmässig mit Wicket ausgeliefert wird.

Das Einzige was wir jetzt noch machen müssen ist in der onSubmit des Formulars folgender Call.


protected void onSubmit() {
 super.onSubmit();
 DB.get().store(getModelObject());
 }

Damit ist unsere Infrastruktur aufgesetzt. Jetzt gehts an die Visualisierung. Hierfür verwenden wir das hervorragende JQuery-Visualize-Plugin (bietet sich ja an, da JQuery nativ schon über Wicket verfügbar ist)

Integration des Visualize Plugins

Wir brauchen das entsprechende Plugin und natürlich die native JQuery Bibliothek.
Hierfür bauen wir uns ein kleines HeaderItem, das beides rendert und dafür sorgt, dass die Bibliotheken zur Laufzeit verfügbar sind.


public class JQueryVisualizePlugin extends JavaScriptUrlReferenceHeaderItem {

private static final String VISUALIZE_PLUGIN_URL = "https://raw.github.com/filamentgroup/jQuery-Visualize/master/js/visualize.jQuery.js";

public JQueryVisualizePlugin() {
 super(VISUALIZE_PLUGIN_URL, "jquery-visualize", true, "utf-8","");
 }

@Override
 public Iterable<?> getRenderTokens() {
 return Arrays.asList("jquery-visualize");
 }

@Override
 public Iterable<? extends HeaderItem> getDependencies() {
 List<HeaderItem> deps = new ArrayList<HeaderItem>();
 deps.add(JavaScriptHeaderItem.forReference(WicketEventJQueryResourceReference.get()));
 return deps;
 }
 }

Eine Erklärung für diese Implementierung findet sich im letzten Artikel.

Jetzt brauchen wir nur noch eine Page, um unseren OrderReport zu rendern.


public class OrderReportPage extends WebPage {

@Override
 public void renderHead(HtmlHeaderContainer container) {
 super.renderHead(container);
 container.getHeaderResponse().render(new JQueryVisualizePlugin());
 }
 }

und das entsprechende Markup dazu.


<html xmlns:wicket="http://wicket.apache.org">
 <head>
 <meta charset="utf-8"/>
 <title>OrderReport</title>
 </head>
 <body>
 <table>
 <caption>Essensbestellungen nach Art</caption>
 <thead>
 <tr>
 <td></td>
 <th scope="col">Pizza</th>
 <th scope="col">Leberkas</th>
 <th scope="col">Burger</th>
 <th scope="col">Salat</th>
 <th scope="col">Spiegelei</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <td>1</td>
 <td>2</td>
 <td>3</td>
 <td>4</td>
 <td>5</td>
 <td>6</td>
 </tr>

</tbody>
 </table>
 </body>
 </html>

Das JQuery-Visualize Plugin erwartet eine ganz  bestimmte Table-Struktur, um damit zu arbeiten. Wir möchten die Anzahl Bestellungen visualisieren, also definieren wir pro Menüart eine Spalte die initial mit 1-6 befüllt sind.

Beispiele für das JQuery Visualize Plugin finden sich hier.

Fangen wir an. Um die Visualisierung zu testen brauchen wir folgendes JavaScript, das wir initial einfach in der Page rendern.


$('table').visualize();

Das machen wir einfach genauso direkt in der Page.


@Override
 public void renderHead(HtmlHeaderContainer container) {
 super.renderHead(container);
 container.getHeaderResponse().render(new JQueryVisualizePlugin());
 container.getHeaderResponse().render(new OnDomReadyHeaderItem("$('table').visualize()"));
 }

Das Skript soll erst dann ausgeführt werden, wenn der DOM komplett aufgebaut ist, deswegen verwenden wir ein OnDomReadyHeaderItem. Was hier gerendert wird ist das.


<script language="javascript">
<pre id="line1">/*<![CDATA[*/
Wicket.Event.add(window, "domready", function(event) {
$('table').visualize();
;});</pre>
</script>

Schauen wir uns das Ganze im Browser an, sehen wir folgendes:

Na wenn das mal nicht einfach war.

Aktuell sind unsere Werte hardkodiert, das ist natürlich eher uninteressant. Was wir möchten ist, jedesmal, wenn auf dem Server eine Bestellung eingeht, soll ein Client notifiziert werden, so dass der Chart aktualisiert werden kann – LiveCharting also!

Client Notifications mit WebSockets

Endlich wird es Zeit, WebSockets zu aktivieren. Das geht ganz einfach. Der Ort, auf dem wir an Server-Events interessiert sind ist die OrderReportPage.

Um WebSockets jetzt endlich verwenden zu können, müssen wir dafür sorgen, dass die Bibliothek aus den Wicket-Extensions mitgeladen wird (kommt natürlich nicht mit dem Core mit). Hierfür deklarieren wir folgende Abhängigkeit in der Projekt-Pom.


<dependency>
 <groupId>org.apache.wicket</groupId>
 <artifactId>wicket-native-websocket-jetty</artifactId>
 <version>0.3</version>
 </dependency>

Es gibt verschiedene Implementierungen für WebSockets (verschiedene Jetty-Versionen und Tomcat). Wir verwenden hier die Jetty-Version in der Version 0.3 (derzeit die aktuellste). Das allein reicht aber leider noch nicht.

Zusätzlich dürfen wir nicht den Standard-Wicket-Filter verwenden, sondern eine spezielle Implementierung für WebSockets. Also auf in die web.xml und folgende Filter-Deklaration verwenden.


<filter>
 <filter-name>wicket.wicket-6-websockets</filter-name>
 <filter-class>org.apache.wicket.protocol.http.Jetty7WebSocketFilter</filter-class>
 <init-param>
 <param-name>applicationClassName</param-name>
 <param-value>de.effectivetrainings.WicketApplication</param-value>
 </init-param>
 </filter>

Das Ganze funktioniert natürlich nur, wenn wir auch wirklich mit dem Jetty7 arbeiten. Startet man jetzt, bekommt man die vielsagende Fehlermeldung:

java.lang.IllegalStateException: Websockets not supported on blocking connectors

Warum nur wird es Einem hier so schwer gemacht?

Was fehlt ist eine Anpassung in der Start-Klasse.
Das Problem ist diese Zeile in der Jetty-Konfiguration.


SocketConnector connector = new SocketConnector();

Für WebSockets machen aber Blocked-Sockets keinen Sinn, also nehmen wir einfach einen Nicht-Blockierenden?


SelectChannelConnector connector = new SelectChannelConnector();

Um WebSockets auf der OrderReportPage zu aktivieren verwenden wir das WebSocketBehavior mit den zugehörigen Methoden onConnect, onClose, onMessage für Textnachrichten und onMessage für Binaries.


add(new WebSocketBehavior(){
 @Override
 protected void onConnect(ConnectedMessage message) {
 super.onConnect(message);
 }

@Override
 protected void onClose(ClosedMessage message) {
 super.onClose(message);
 }

@Override
 protected void onMessage(WebSocketRequestHandler handler, TextMessage message) {
 super.onMessage(handler, message);
 handler.push("Hallo Client!!");
 }

@Override
 protected void onMessage(WebSocketRequestHandler handler, BinaryMessage binaryMessage) {
 super.onMessage(handler, binaryMessage);
 }
 });

Sobald sich ein Client per WebSocket verbindet liefert er unserer Applikation bestimmte Daten, die wir benötigen, um eine Nachricht an den Client zu schicken, hierfür ist die onConnect-Methode im Behavior zuständig.

Was der Client uns liefert ist

  • applicationName
  • sessionId
  • pageId

Diese Informationen bekommen wir über die ConnectedMessage, die als Parameter übergeben wird.

Warum brauchen wir genau diese Parameter? Die Applikation identifiziert die Applikation, weiter nicht interessant. Die SessionId identifiziert den Client eindeutig, das brauchen wir, da ja beliebig viele Clients verbunden sein können. Die PageId ist interessant, hierzu muss man wissen, was intern passiert.

Kommt ein WebSocketRequest vom Client beim Server an, wird (analag eines AjaxRequests) die Seite aus dem DiskPageStore anhand der übergebenen PageId geladen und einmal komplett durch den Komponentenbaum dieser Seite iteriert (mittels dem Standard Event Mechanismus) und ein
WebSocketConnectedPayload, WebSocketClosedPayload oder ein WebSocketPayload übergeben. Theoretisch kann also jede Komponente einzeln auf WebSocketEvents reagieren, indem die onEvent(..) Methode überschrieben wid und auf das ComponentEvent reagiert wird.
Um jetzt aber tatsächlich effektiv mit WebSockets arbeiten zu können müssen wir ein bisschen tricksen.

Eine Connection wird eindeutig über Applicationname, SessionId und PageId identifizeirt. Wir brauchen eine Art Registry um die Connections zu verwalten.

Hier ein sehr einfacher und simpler Ansatz, der für unsere Zwecke aber völlig ausreichend ist.
Wir nehmen einfach an, die Session-ID identifiziert eindeutig unsere Connections, das funktioniert wunderbar, so lange wir nur eine Seite in der Anwendung haben, die Server-Nachrichten empfängt, bei mehreren Seiten müssen wir uns was besseres ausdenken.


public class ConnectionRegistry {

private Map<String, List<ClientConnection>> connections;

public ConnectionRegistry(){
 this.connections = Collections.synchronizedMap(new HashMap<String, List<ClientConnection>>());
 }

public void clientConnects(String applicationName, String sessionId, Integer pageId){
 List<ClientConnection> clientConnections = this.connections.get(sessionId);
 if(clientConnections == null){
 connections.put(sessionId, new ArrayList<ClientConnection>());
 }
 this.connections.get(sessionId).add(new ClientConnection(applicationName,sessionId,pageId));
 }

public void clientDisconnects(String applicationName, String sessionId, Integer pageId){
 List<ClientConnection> connections = this.connections.get(sessionId);
 if(connections != null){
 connections.remove(new ClientConnection(applicationName,sessionId,pageId));
 }
 }

public List<ClientConnection> getConnectionsBySessionId(String sessionId){
 List<ClientConnection> connections =  this.connections.get(sessionId);
 return connections != null ? connections : new ArrayList<ClientConnection>();
 }

}

public class ClientConnection implements Serializable {

private String applicationName;
 private String sessionId;
 private Integer pageId;

public ClientConnection(String applicationName, String sessionId, Integer pageId) {
 this.applicationName = applicationName;
 this.sessionId = sessionId;
 this.pageId = pageId;
 }

public String getApplicationName() {
 return applicationName;
 }

public String getSessionId() {
 return sessionId;
 }

public Integer getPageId() {
 return pageId;
 }

@Override
 public boolean equals(Object o) {
 if (this == o) return true;
 if (o == null || getClass() != o.getClass()) return false;

ClientConnection that = (ClientConnection) o;

if (applicationName != null ? !applicationName.equals(that.applicationName) : that.applicationName != null)
 return false;
 if (pageId != null ? !pageId.equals(that.pageId) : that.pageId != null) return false;
 if (sessionId != null ? !sessionId.equals(that.sessionId) : that.sessionId != null) return false;

return true;
 }

@Override
 public int hashCode() {
 int result = applicationName != null ? applicationName.hashCode() : 0;
 result = 31 * result + (sessionId != null ? sessionId.hashCode() : 0);
 result = 31 * result + (pageId != null ? pageId.hashCode() : 0);
 return result;
 }
 }

Die Registry stellen wir über die Applikation bereit.


public class WicketApplication extends WebApplication
 {
 private ConnectionRegistry registry;

/**
 * @see org.apache.wicket.Application#getHomePage()
 */
 @Override
 public Class<? extends WebPage> getHomePage()
 {
 return HomePage.class;
 }

/**
 * @see org.apache.wicket.Application#init()
 */
 @Override
 public void init()
 {
 super.init();
 registry = new ConnectionRegistry();
 mountPage("/report", OrderReportPage.class);
 }

public ConnectionRegistry getRegistry(){
 return registry;
 }

public static WicketApplication get(){
 return (WicketApplication) Application.get();
 }
 }

Sobald ein Client eine WebSocket-Verbindung öffnet oder schliesst, reagieren wir im WebSocketBehavior.


@Override
 protected void onConnect(ConnectedMessage message) {
 super.onConnect(message);
 WicketApplication.get().getRegistry().
 clientConnects(message.getApplication().getName(),
 message.getSessionId(), message.getPageId());
 }

@Override
 protected void onClose(ClosedMessage message) {
 super.onClose(message);
 WicketApplication.get().getRegistry().
 clientDisconnects(message.getApplication().getName(),
 message.getSessionId(), message.getPageId());
 }

Das Schöne ist, wir bekommen jetzt überall Zugriff auf die offenen Connections per Client.


WicketApplication.get().getRegistry().getConnectionsBySessionId(Session.get().getId())

Jetzt wird es Zeit, die WebSockets einzusetzen, hierzu spendieren wir unserem Formular noch einen AjaxSubmitButton und folgenden Code in der onSubmit() – Methode.


orderForm.add(new AjaxSubmitLink("submit") {
 @Override
 protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
 SimpleWebSocketConnectionRegistry registry = new SimpleWebSocketConnectionRegistry() ;
 for(ClientConnection clientConnection :
 WicketApplication.get().getRegistry().getConnectionsBySessionId(Session.get().getId())) {
 IWebSocketConnection connection = registry.getConnection(Application.get(), clientConnection.getSessionId(), clientConnection.getPageId());
 if (connection != null) {
 WebSocketRequestHandler webSocketHandler = new WebSocketRequestHandler(this, connection);
 webSocketHandler.push("My WebSocket message");
 }
 }

}
 });

Über die SimpleWebSocketConnectionRegistry bekommt man Zugriff auf die derzeit offenen Connections. Wir erzeugen einen WebSocketRequestHandler, und dieser bietet funktioniert ziemlich analog dem AjaxRequestTarget. Ich habe also Methode wie appendJavaScript, prependJavaScript, add etc.

Über die Methode push(…) können wir eine einfache TextMessage an den Client schicken. Dieser muss nur noch auf Messages reagieren.

ClientSide Code

Folgnder Code verwendet die definieren Wicket-Callbacks für den Client-Code.


$(document).ready(function() {

Wicket.Event.subscribe("/websocket/open", function(jqEvent) {
 alert("connection opened");
 });

Wicket.Event.subscribe("/websocket/message", function(jqEvent, message) {
 alert("message received " + message);
 });

});

Mit Wicket.Event.subscribe könnten wir uns für die WebSocket-Messages registrieren. Messages werden standardmässig über den Kanal „“/websocket/message“ verschickt. Diesen Client rendern wir mit der OrderReportPage, die ja standardmässig auf die Nachrichten von der Bestellseite reagieren soll.

Hierfür definieren wir uns eine neue Resource.


public class WebSocketClientResourceReference extends PackageResourceReference {
 public WebSocketClientResourceReference() {
 super(HomePage.class, "orderreport-client.js");
 }

@Override
 public Iterable<? extends HeaderItem> getDependencies() {
 List<HeaderItem> headerItems = new ArrayList<HeaderItem>();
 headerItems.add(JavaScriptHeaderItem.forReference(WicketWebSocketJQueryResourceReference.get()));
 return headerItems;
 }
 }

Diese Referenz rendern wir einfach mit der OrderReportPage.


@Override
 public void renderHead(HtmlHeaderContainer container) {
 super.renderHead(container);
 container.getHeaderResponse().render(new JQueryVisualizePlugin());
 container.getHeaderResponse().render(new OnDomReadyHeaderItem("$('table').visualize();"));
 container.getHeaderResponse().render(JavaScriptHeaderItem.forReference(new WebSocketClientResourceReference()));
 }

Senden wir die Form jetzt testweise ab, öffnet sich automatisch die OrderReportPage im Browser und wir sehen folgendes:

Schon mal gar nicht schlecht. Damit sind wir fast am Ziel. Zuletzt müssen wir die OrderReportPage noch ein wenig tunen. Bisher ist unsere Tabelle mit der Bestellübersicht nur hardkodiert.

Wir haben jetzt zwei Möglichkeit, entweder wir machen aus der Tabelle eine WicketKomponente und fügen diese nach jedem Request mit der add(..)-Methode hinzu, damit diese neu gezeichnet wird, oder wir bauen das ganze manuell mit JQuery.

Problem beim ersten Ansatz ist, dass wir über den WebSocketRequestHandler zwar die PageInstanz bekommen, aber nicht direkt Zugriff auf die Wicket-Komponenten auf der Seite. Man müsste also über Getter- oder ähnliches die interne Struktur der Seite nach aussen exposen. Nicht schön, bauen wir das Ganze also einfacher mit JQuery.

In der onSubmit-Methode schicken wir keine einfache Textnachricht, sondern wir verschicken einfach JSON.


WebSocketRequestHandler webSocketHandler = new WebSocketRequestHandler(this, connection);
 try {
 webSocketHandler.push(JsonUtils.asArray(DB.get().countOrdersByFood()).toString());
 } catch (JSONException e) {
 e.printStackTrace();
 }

Hierfür definieren wir folgende Html Struktur in der OrderReport Page.


<table style="visibility:hidden">
 <caption>Essensbestellungen nach Art</caption>
 <thead>
 <tr>
 <th scope="col">Pizza</th>
 <th scope="col">Leberkas</th>
 <th scope="col">Burger</th>
 <th scope="col">Salat</th>
 <th scope="col">Spiegelei</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <td id="PIZZA">0</td>
 <td id="LEBERKAS">0</td>
 <td id="BURGER">0</td>
 <td id="SALAT">0</td>
 <td id="SPIEGELEI">0</td>
 </tr>

</tbody>
 </table>

und folgendes Skript im JavaScript-Client.


Wicket.Event.subscribe("/websocket/message", function(jqEvent, message) {
 var json = JSON.parse(message);
 for(i in json){
 $('#'+json[i].name).html(json[i].value);
 }
 $('.visualize').trigger('visualizeRefresh');
 })

Und ab jetzt triggert jede Bestellung ein Refresh der Ansicht auf allen aktuell geöffneten OrderReport Pages.

Fazit

Insgesamt muss ich sagen, es ist schön, dass WebSockets mittlerweile funktionieren, aber die Integration scheint mir noch ein wenig holprig. Gerade wenn es um die Integration mit verschiedenen Seiten geht (meiner Ansicht nach DER Use-Case ist das ganze ein wenig kompliziert).

Oder habe ich etwas übersehen? Geht es evtl einfacher als in diesem Artikel vorgestellt? Über Hinweise wäre ich dankbar, ansonsten wünsche ich viel Spaß mit Wicket und WebSockets.

Der Source-Code für dieses Beispiel befindet sich hier im Unterordner /wicket-6-websockets.

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

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

Links

Die Ankündigung von WebSockets

WIKI

Demo Applikation von Martin Grigorov mit Scala und Actors

Wicket 6.2 und JQuery – Dream Team in Action

In meinen letzten beiden Artikeln zum Thema Wicket 6 habe ich mich mit Wicket 6 und JQuery und Formularvalidierung beschäftigt. Heute möchte ich das Thema JQuery ein wenig auskosten. Wie wahrscheinlich jeder mittlerweile weiß, ist die Javascript-Backing-Library von Wicket mittlerweile JQuery. Betrachten wir doch mal, was damit jetzt alles möglich ist.

Zunächst bauen wir uns einen schönen Maven-Archetype.


mvn archetype:generate -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=6.2.0 -DgroupId=de.effectivetrainings -DartifactId=wicket-6-jquery -DarchetypeRepository=https://repository.apache.org/ -DinteractiveMode=false

Anschließend räumen wir die ganzen Klassen und Templates ein wenig auf, damit wir mit einem sauberen Stand starten können.


public class HomePage extends WebPage {
}

<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
<meta charset="utf-8" />
<title>Apache Wicket Quickstart</title>
</head>
<body>

</body>
</html>

Damit wir auch was schönes zu sehen bekommen laden wir zusätzlich  jquery-ui in der Version 1.9.1. Hierfür implementieren wir folgendes in der Klasse HomePage. Wir ziehen uns die Bibliothek einfach direkt von der JQuery UI Homepage. (Das wäre aber nichts, was man in einem produktiven System machen würde..)


@Override
public void renderHead(HtmlHeaderContainer container) {
super.renderHead(container);
IHeaderResponse response = container.getHeaderResponse();
response.render(JavaScriptHeaderItem.forUrl("http://code.jquery.com/ui/1.9.1/jquery-ui.js","jquery-ui"));
}

Starten wir das Ganze sieht man zwar noch nichts (bisher haben wir ja keinen Content angelegt), im Sourcecode der Seite sieht man aber folgendes.

<script type="text/javascript" id="jquery-ui" src="http://code.jquery.com/ui/1.9.1/jquery-ui.js"></script>

Hm, eine Besonderheit haben wir aber. JQuery-UI benötigt zwingend JQuery, das bedeutet, das aktuelle Setup ist invalide.
Wie aber deklariert man eine derartige Abhängigkeit?

Mit Wicket 1.5.x wäre das etwas kompliziert, mit Wicket 6 total einfach. Ziehen wir die JQuery-UI-Resource in eine eigene Klasse.


public class JQueryUIResourceReference extends JavaScriptUrlReferenceHeaderItem {

private static final String URL= "http://code.jquery.com/ui/1.9.1/jquery-ui.js";

public JQueryUIResourceReference() {
super(URL, "jquery-ui-1.9.1", true, "utf-8", "");
}

/**
* jedesmal wenn die jquery-ui resource reference gerendert wird muss auch
* die jquery-bibliothek gerendert werden.
* @return
*/
@Override
public Iterable<? extends HeaderItem> getDependencies() {
List<HeaderItem> deps = new ArrayList<HeaderItem>();
deps.add(JavaScriptHeaderItem.forReference(JQueryResourceReference.get()));
return deps;
}
}

Schön ist die Methode „getDepdendencies“
Diese Methode liefert eine Liste von HeaderItems, die von der aktuellen Resource verwendet wird. Wicket stellt sicher, dass jede Abhängigkeit in der richtigen Reihenfolge gerendert wird.

Folgende Deklaration in der Page stellt sicher, dass beide Resourcen gerendert werden.


@Override
public void renderHead(HtmlHeaderContainer container) {
super.renderHead(container);
IHeaderResponse response = container.getHeaderResponse();
response.render(new JQueryUIResourceReference());
}

Bauen wir die üblichen Tabs

Folgendes Markup ist die Basis-Struktur für die JQuery-UI-Tabs.


<div id="tabs">
<ul>
<li><a href="#fragment-1"><span>Tab 1</span></a></li>
<li><a href="#fragment-2"><span>Tab 2</span></a></li>
<li><a href="#fragment-3"><span>Tab 3</span></a></li>
</ul>
<div id="fragment-1">
erster
</div>
<div id="fragment-2">
zweiter
</div>
<div id="fragment-3">
dritter
</div>
</div>

Bauen wir uns ein einfaches eigenes Panel.

public class TabbedPanel extends Panel {
public TabbedPanel(String id) {
super(id);
}

@Override
public void renderHead(HtmlHeaderContainer container) {
super.renderHead(container);
container.getHeaderResponse().render(new JQueryUIResourceReference());
container.getHeaderResponse().render(
JavaScriptHeaderItem.forReference(
new PackageResourceReference(TabbedPanel.class, "tabbed-script.js")));
}
}

Das entsprechende Markup sieht so aus

<html xmlns:wicket="http://wicket.apache.org">
<wicket:panel>
<div id="tabs">
<ul>
<li><a href="#fragment-1"><span>Tab 1</span></a></li>
<li><a href="#fragment-2"><span>Tab 2</span></a></li>
<li><a href="#fragment-3"><span>Tab 3</span></a></li>
</ul>
<div id="fragment-1">
erster
</div>
<div id="fragment-2">
zweiter
</div>
<div id="fragment-3">
dritter
</div>
</div>
</wicket:panel>
</html>

Das einzig besondere ist die renderHead()-Methode im TabbedPanel. Denn die Reihenfolge, in der Resourcen gerendert werden ist Depth-First.
Alle Komponenten rendern ihr Resourcen und erst dann kommt die Page. Da die JQuery- und JQuery-UI Referenz bisher in der Page deklariert ist,
rendert die TabbedPanel Komponente ihr Javascript und erst dann werden die JQuery-Referenzen gerendert. Autsch… das geht so nicht, ein einfacher
Workaround hierzu ist, die JQuery-Referenz einfach nochmals zu deklarieren. Wicket ist so schlau und rendert die Referenz nur einmal, und zwar an der richtigen Stelle.

Bingo!
War dieser Blogeintrag für Sie interessant? Braucht Ihr Team Unterstützung? Kontaktieren Sie mich, ich bin hier um zu helfen.

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

Git Tip of the Day – Spiel das Commit Game

Manchmal ist es ganz interessant zu sehen, wer wie viel comitted, wer also der „fleissigste“ ist (Sarkasmus Ende.).

Sehen kann man das ganz einfach mit folgendem Befehl:


git shortlog --numbered --summary

Ich habe das mal testweise im Wicket-Source-Repository gemacht, mit folgendem Ergebnis.


2932  Eelco Hillenius
2816  Igor Vaynberg
1867  Juegen Donnerstag
1780  Martin Tzvetanov Grigorov
1484  Johan Compagner
1321  Jonathan Locke
963  Matej Knopp
899  Martijn Dashorst
422  Alastair Maw
352  Peter Ertl
277  Jean-Baptiste Quenot
235  Frank Bille Jensen
218  Janne Hietamaki
205  Gerolf Seitz
98  martin-g
94  Sven Meier
91  Emond Papegaaij
76  Pedro Henrique Oliveira dos Santos
69  svenmeier
62  Gwyn Richard Evans
51  Jeremy Ryan Thomerson
42  Timo Heikki Rantalaiho
40  sourceforge-skipoles
16  Carl-Eric Menzel
14  Ate Douma
12  Maurice Marrink
8  Jan Blok
4  Jeremy Thomerson
2  Jesse Long
1  Thomas Götz
1  Pedro Santos
1  cvs2svn
1  Joe Schaefer
1  robert mcguinness
1  slowery
1  Michael Mosmann
1  Bertrand Guay-Paquet
1  Sven

Social Refactoring – das beste Investment

Im letzten Artikel  habe ich darüber philosophiert, warum das Team verantwortlich ist. Heute geht es um ein extrem wichtiges Thema, und zwar um das Thema „Social Refactoring“.

Viele von uns haben bereits die Erfahrung gemacht, was es bedeutet, in einem agilen Team zu arbeiten, völlig egal ob es nun um Scrum, Kanban oder um etwas völlig anderes geht.

Da ist seit längerem als Scrummaster in einem agilen Team beschäftigt bin (und zuvor auch bereits in sehr vielen unterschiedlichen Teams Scrummaster und auch Entwickler war) kann ich hier ein wenig aus der Erfahrung berichten.

Ich persönlich freue mich immer sehr, wenn Teams engagiert sind, sauberen Code zu produzieren, die gesamte Code-Basis in Clean-Code-Manier sauber zu halten und konstante Refactorings anstreben. Gibts nicht immer, habe ich aber tatsächlich schon erlebt.

Was ich aber auch sehr oft erlebt habe ist, dass die Code-Basis zwar sauber gehalten wird, die sozialen Strukturen im Team aber sträflich vernachlässigt werden. Wie macht sich das bemerkbar?

Kein Collective-Code-Ownershop

Der Code im Team gehört nicht allen, sondern es gibt sie, die Spezialisten. Hier haben wir den EJB-Experten, der sich nur um die Service-Schicht kümmert. Wir haben den JPA-Experten, der alles unterhalb der Repository-Schicht beherrscht. Es gibt den UI-Experten, der funky Oberflächen bastelt. Und insgesamt gilt, jeder verteidigt sein Revier. Da wird geknurrt und gefaucht, was das Zeug hält, sobald ein „Fremder“ versucht, im Nachbarrevier zu wildern.

Fingerpointing und Blaming

Ich glaube, dass wir am besten lernen (zumindest ist es bei mir so), wenn wir Fehler machen können/dürfen/sollen, ohne dafür zur Rechenschaft gezogen zu werden. Gerade bei uns in der Entwicklung ist es extrem wichtig, zu experimentieren. Verschiedene Lösungsansätze funktionieren Problemabhängig besonders gut/schlecht, was sich aber meistens nicht sofort beurteilen lässt.

Oft erlebe ich aber, dass jeder Fehler gnadenlos ausgenutzt wird und beinahe unmittelbar der „Blame and Point“-Pattern angewandt wird. Am schlimmsten ist es, wenn dies nicht einmal direkt geschieht, sondern in der Kaffeeküche oder im Code.

Ein Commit-Kommentar den ich kürzlich gelesen habe, lautete in etwa so:

„Clean up this mess. This Code should not be refactored but deleted.“

Dont talk to Strangers

Manchmal finde ich es bedenklich, wie wenig tatsächlich in Teams während der täglichen Arbeit gesprochen wird. Oft erlebe ich es, dass sich jeder um „seinen Kram“ kümmert, alles andere ist erst mal egal.

Das Daily-Scrum, dass meiner Ansicht eine Grundlage für nachfolgende Kommunikation sein soll wird oft nur betrachtet als die „nervigen 15 min, damit der Scrummaster Ruhe gibt“.

Jeder schnappt sich seinen Task, „Wiedersehen, wir sehen uns morgen um 10.“.

Nur die Starken kommen in Garten

Die meisten Teams sind durchaus unterschiedlich besetzt (zumindest anfangs). Es gibt die Entwickler mit viel Erfahrung, aber wenig Motivation. (Lieblingssatz: „Haben wir schonmal probiert, funktioniert nicht“).

Es gibt die mit viel Erfahrung und hoher Motivation (die „Keyplayer“, „King of the Hill“, „der Architekt“).

Es gibt Entwickler mit wenig Erfahrung und hoher Motivation (oft „Formbar“ oder „Formfleisch“ genannt).

Und die „Schlusslichter“, die Entwickler mit wenig Erfahrung und noch weniger Motivation (oft als „Bremsscheibe“ tituliert).

Das ist zunächst prinzipiell überhaupt nichts schlimmes und völlig normal. Wenn sich dieses Gefüge aber im Lauf der Zeit nicht ändert und nach 5-6 Sprints immer noch die gleiche Konstellation vorherrscht, dann läuft etwas grandios schief.

Wie wirkt man gegen?

Ich hoffe es wurde klar, dass die Formulierungen von oben sehr überzogen und überspitzt sind. Aber in jeder Formulierung steckt zumindest ein Körnchen Wahrheit. Die Motivation, an bestehenden Misständen etwas zu ändern muss aus dem Team kommen. Wir als Scrummaster können / und sollten hier nur lenkend eingreifen (ich weiß, leichter gesagt, als getan).

Ich bezeichne dieses Vorgehen gerne als „Social-Refactoring“ und halte es für genauso wichtig wie Refactoring im Code (und jeder der mich kennt, weiß dass ich Refactoring liebe:)).

Was ist nun die Geheimwaffe des Scrummasters?

Gewalt? Drohung? Blaming? Weinen?

Natürlich nicht, sondern die Retrospektive.

Ich als Scrummaster erlebe es sehr oft, dass die Retrospektive als relativ unnütz, unwichtig und zu langweilig erachtet wird. „Wieso, es läuft doch alles gut“ bekomme ich des öfteren zu hören. Als Scrummaster sollte man hier hart bleiben, denn es gibt immer Punkte, über die man sprechen kann. Aus einigen Jahren agiler Erfahrung kann ich sagen, dass Retrospektiven in etwa so etwas wie „Pair-Programming beim Social-Refactoring“ sind.

Es gibt zwei Bücher, die ich zum Thema Retrospektive empfehlen würde:

     

Und mein wichtigster Tipp aus der Praxis, denken Sie sich was aus! 

Der grösste Fehler den Sie machen können ist, immer das Gleiche Programm in der Retrospektive abzuspulen. Ich habe es schon erlebt, dass Teammitglieder, die 2 Min vorher da waren schon angefangen haben, ihre üblichen „Zettel“ zu schreiben – was lief gut, was schlecht etc. Und das bevor die Retrospektive überhaupt angefangen hat. Da wird es Zeit, gegen zu steuern.

Die Retrospektive muss für die Teammitglieder interessant sein, damit sie wirklich effektiv wird.

Ein Erlebnis, eine Aha!

Beispiel:

Die Mittags-Retro

Wir machen regelmässig eine Mittags-Retro, das Team geht zusammen zum Mittagessen oder holt sich was und setzt sich irgendwo draussen hin und dann wird völlig zwanglos diskutiert. Der Vorteil hiervon ist, dass allein durch den Umgebungswechsel völlig neue Impulse entstehen können und interessante Punkte auf die Agenda gebracht werden.

Stammtisch-Retro

Wieso muss die Retrospektive immer förmlich ablaufen. Manchmal ist es durchaus in Ordnung, die Retrospektive auf kurz vor dem Feierabend zu legen und sich dann ein Bier dazu zu genehmigen. Schafft eine entspannte Atmosphäre und funktioniert erstaunlich gut. Natürlich sollten sich die Teilnehmer dabei nicht betrinken!:).

Hierzu gibt es viele Tipps in den obigen beiden Büchern und ich rate dringend, einfach mal ein wenig experimentierfreudig zu sein und das Eine oder Andere auszuprobieren. Die meisten Teilnehmer danken es einem.

Zu guter Letzt, was ich dringend empfehle ist der sogenannte Team-Radar.

Der Scrummaster liest zu Beginn jeder Retrospektive 5 Begriffe vor, und bittet alle Teammitglieder für diese 5 Begriffe anonym eine Wertung abzugeben auf einer Skala von 1-10. Was daraus entstehen kann, sieht man in diesem Foto.

Team Radar

Agiler Team Radar

Die 5 Begriffe sind immer diesselben. In diesem Fall haben wir die Begriffe

  • Qualität
  • Teamwork
  • Kommunikation
  • Pair-Programming
  • Zufriedenheit

gewählt. Das sind Eigenschaften, Praktiken, Vorgehen die dem Team wichtig erscheinen. Mit diesem Hilfsmittel lässt sich selbst aus Retrospektiven mit wenig Output (gibt es immer mal, beispielsweise kurz vor der Urlaubszeit) noch wichtige Information generieren.

Jeder Sprint bekommt eine eigene Farbe, und auf jeder Achse wird der Mittelwert aller abgegebenen Wertungen eingetragen.

Am Beispiel Qualität lässt sich folgende Entwicklung ablesen:

Entwicklung auf dem Radar

Entwicklung auf dem Radar

Das Team hatte das Gefühl, dass die Qualität über mehrere Sprints immer weiter abgenommen hat. Dies ist natürlich rein subjektiv und hat nicht unbedingt etwas mit der tatsächlichen Code-Qualität zu tun. Hier geht es allein darum, wie das Team empfindet.

Wir haben es über mehrere Sprints nicht hinbekommen, das Gefühl des Teams in dieser Hinsicht zu verbessern, bis wir schließlich irgendwann auf den Trichter gekommen sind, was dem Team so Bauchweh verursacht. Diese Ursache haben wir beseitigt und schließlich im „blauen Sprint“ einen ersten Erfolg gefeiert.

Der Team-Radar ist ein wirklich exzellentes Mittel, um die Grundstimmung im Team einzufangen.

Insgesamtes Fazit eines Scrummasters – Social-Refactoring ist wichtig. Selbst das beste Team muss an sich arbeiten. Es funktioniert nicht einfach so, aber jede Minute die ein Team hierfür investiert zahlt sich meistens doppelt und dreifach aus. In diesem Sinne.

War dieser Blogeintrag für Sie interessant? Braucht Ihr Team Unterstützung? Kontaktieren Sie mich, ich bin hier um zu helfen.

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

Apache Wicket – Readable URLs mit Mount-Pathes

Heute bin ich in meinem aktuellen Projekt vor einem interessanten Problem gestanden. Nehmen wir an, wir haben ein Webprojekt, basierend auf Apache Wicket.

Ein Archetype lässt sich für die Version 6 so generieren:

mvn archetype:generate -DgroupId=de.effectivetrainings -DartifactId=wicket-6-mountpathes -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -Dversion=1.0-SNAPSHOT

Definieren wir uns eine neue WebPage.

/**
* @author Martin Dilger
*/
public class MyPage extends WebPage {
public MyPage(PageParameters parameters){
super(parameters);
add(new Label(parameters.get("category").toString()));
}
}

Dazu das passende Html


MyPage rendert :</pre>
<div wicket:id="category"></div>
<pre>

Direktsprung auf die Seite

Nehmen wir an, wir möchten per Direkteinsprung auf die MyPage springen. Ohne weiteres würde das über diesen Link
funktionieren.

http://localhost:8080/wicket/bookmarkable/de.effectivetrainings.MyPage

Nicht besonders schick, oder?

Package Mounts

        @Override
	public void init()
	{
		super.init();
                mountPackage("pages",MyPage.class);
	}

Damit sind alle Pages im Package der Klasse MyPage über folgenden Link aufrufbar

http://localhost:8080/pages/MyPage
http://localhost:8080/pages/HomePage

Der Mount-Pfad ist hierbei einfach der Klassenname

Parameter

Eine Seite lässt sich auf mit Parametern rendern (man liest oft Restful.. hat aber nicht viel miteinander zu tun).

/**
	 * @see org.apache.wicket.Application#init()
	 */
	@Override
	public void init()
	{
		super.init();
        mountPackage("pages",MyPage.class);
        mountPage("/pages/parameter/${category}", MyPage.class);
	}

Wir machen die Page MyPage zugreifbar unter /pages/parameter/sport zum Beispiel, der Parameter sport wird hiermit als GET-Parameter über die übergebenen PageParameter zur Verfügung gestellt. Der Zugriff funktioniert einfach so:

parameters.get("category").toString()

Man kann auch mehrere Parameter übergeben.

@Override
	public void init()
	{
		super.init();
        mountPackage("pages",MyPage.class);
        mountPage("/pages/parameter/${category}/${weekday}", MyPage.class);
	}

Ein Aufruf könnte so aussehen:

http://localhost:8080/pages/parameter/sport/montag
http://localhost:8080/pages/parameter/sport/montag/

Ein Beispiel für dieses Mounting wäre eine Online-TV Zeitschrift, die alle Sportsendungen am Montag anzeigen soll.
Um alle Sportsendungen anzuzeigen gilt weiterhin dieser Link, der auch funktioniert.

http://localhost:8080/pages/parameter/sport

Was aber, wenn wir alle Sendungen am Montag anzeigen möchten?
Ein Aufruf wäre

http://localhost:8080/pages/parameter/montag

Optionale Parameter

Wie soll Wicket unterscheiden, ob der Parameter montag jetzt die Kategorie oder der Wochentag ist?

Man könnte die Seite so aufrufen

http://localhost:8080/pages/parameter//montag/

Nein.. keine besonders gute Idee.

Optionale Parameter als Rettung!

mountPage("/pages/parameter/#{category}/${weekday}", MyPage.class);

Einfach ein # statt des $ verwenden, und schon sind diese Parameter optional und werden auch so behandelt.


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

Scrum und Kanban in der Praxis

Dieser Artikel erzählt einige Episoden aus dem Alltag eines „überwiegend“ fiktiven Scrum-Teams, den üblichen Anforderungen, Problemen und Erfolgen. Der Autor ist derzeit Entwickler und Scrummaster in einem agilen Team.

Das Scrum-Team, das wir die nächsten paar Tage begleiten besteht aus Karl, Markus und Georg (Java-Entwickler) sowie Hans (Tester) und Arne (Scrummaster).
Das Team entwickelt einen Online-Shop und arbeitet mit Scrum.
Scrum ist wahrscheinlich die bekannteste agile Vorgehensweise und erlaubt es, Produkte inkrementell in kleinen Schritten zu entwickeln.
Das Team arbeitet in Sprints, die üblicherweise eine Länge von zwei Wochen haben. Das Team vereinbart mit dem Kunden (den wir gleich kennenlernen werden), was am Ende jedes Sprints geliefert werden soll. Geliefert werden die mit dem Kunden vereinbarten Teilfunktionalitäten, diese aber tatsächlich fertig. Das bedeutet für unser Team – Unit-Tests vorhanden, Peer Review durchgeführt, dokumentiert, Acceptance Test auf definierter Testumgebung durchgeführt und jeder im Team ist so zufrieden mit der durchgeführten Arbeit, dass man sie sich am liebsten zu Hause an den Kühlschrank pinnen möchte.

Der erste und wichtigste Termin des Tages jedes Scrum-Teams ist das Daily Scrum.

10:30 – Das wichtigste Meeting des Tages

Das wichtigste Meeting des Teams ist das Daily Scrum. Das Daily Scrum dauert höchstens 15 Minuten und startet pünktlich um 10:30. Zu diesem Zeitpunkt sind auch die nachtaktiven Teammitglieder ausgeschlafen und können erste klare Gedanken fassen.
Das Daily Scrum ist deshalb so wichtig für das Team, weil es diese 15 Minuten nutzt, um sich für den Tag zu synchronisieren.

Heute startet das Daily Scrum mit Karl.

Karl erzählt, dass er seit gestern an einem Feature zur Validierung von Kundendaten im Frontend  des Online-Shops gearbeitet hat.
Das beantwortet Frage 1: Was habe ich seit gestern geschafft?
Anschließend erzählt Karl, dass er dieses Feature gerne zusammen mit Hans dem Tester auf eine Testumgebung deployen und ausführlich testen möchte.
Das beantwortet Frage 2: Was möchte ich heute machen?
Zuletzt erzählt Karl, dass er dringend auf eine Zulieferung aus der Grafikabteilung wartet um die Validerungsfehler im Frontend korrekt darstellen zu können.
Bis die Grafikabteilung die fehlenden Stylesheets liefert kann Karl das Feature nicht abschließen.
Damit beantwortet Karl Frage 3: Was behindert mich aktuell?
Als Karl die dritte Frage beantwortet, wird sofort Arne der Scrummaster hellhörig, denn genau das ist die Aufgabe und der Job eines Scrummasters. Der Scrummaster löst Probleme. Er sorgt dafür, dass das Team zu jedem Zeitpunkt  produktiv arbeiten kann.
Was wird Arne also machen? Er wird direkt nach dem Daily Scrum der Grafikabteilung ein paar Büros weiter einen Besuch abstatten (er wird nach Möglichkeit nicht anrufen und auch keine E-Mail schreiben) und versuchen, die dringend benötigte Lieferung irgendwie zu beschleunigen.

Diese 15 Minuten sind unglaublich wichtig für das Team, weil so sichergestellt ist, dass jeder zumindest grob die Aufgaben der anderen Teammitglieder kennt und der Fortschritt des Projektes täglich besprochen wird.

Das wichtigste Werkzeug

Wir haben das wichtigste Meeting eines Scrum-Teams kennengelernt, jetzt werden wir das wichtigste Werkzeug kennenlernen – das Board.

Abbildung 1: Das Board

Das Board visualisiert die Arbeit des Teams. Am Board ist jederzeit für jeden (auch beispielsweise für den Kunden) ersichtlich, welche Features aktuell umgesetzt werden, welches Features bereits abgeschlossen sind und was noch für den restlichen Sprint geplant ist.

Betrachten wir hierzu das Board des Teams ein wenig genauer.

Abbildung 2: Das Board des Teams

Das Board des Teams kann gar nicht groß genug sein. Es ist mehr als 4 Meter lang und bedeckt eine komplette Bürowand.
Es ist unterteilt in mehrere Spalten, die die einzelnen Schritte repräsentieren, die eine Anforderung bis zur Fertigstellung durchläuft.
Gibt der Kunde eine neue Anforderung in das Team, landet diese zunächst als Post-It Zettel in der TODO-Spalte (Abb. 3). Üblichweise verbraucht ein Scrum-Team Unmengen von Post-Its.

Abbildung 3: Anforderungen auf dem Board

Die TODO-Spalte wird vom Kunden priorisiert. Je weiter oben sich eine Anforderung in der TODO-Spalte befindet, desto wichtiger ist dem Kunden die Umsetzung und desto früher wird die Anforderung vom Team bearbeitet.
Sobald sich das Team entscheidet, eine neue Anforderung in Bearbeitung zu nehmen, wandert die diese aus der TODO- in die DEV-Spalte.

Damit ist sofort ersichtlich, dass aktuell an dieser Anforderung gearbeitet wird. Ist die Entwicklung abgeschlossen, wandert die Anforderung weiter in die QS-Spalte. Die QS-Spalte ist ein Entwicklungsschritt, der vom Team definiert wurde um die Qualität zu verbessern. Eine Anforderung in dieser Spalte ist bereits fertig entwickelt und wird nochmals von einem Entwickler überprüft, der nicht an der Umsetzung beteiligt war.
Irgendwann ist auch dieser Entwickler zufrieden und die Anforderung wandert erneut eine Spalte weiter in die TEST-Spalte. Hier wird Hans der Tester aktiv und testet die Anforderungen gegen die Akzeptanzkriterien des Kunden.
Ist Hans zufrieden gibt auch dieser grünes Licht und die Anforderung wandert wieder einen Schritt weiter in die DEPLOYMENT-Spalte.
Eine Anforderung in dieser Spalte ist fertig implementiert, vom Kunden abgenommen und auf dem Weg in das Produktiv-System.
Dieser Schritt wird leider nicht direkt vom Team selbst durchgeführt, sondern von einer externen Abteilung, die aber im gleichen Gebäude ist. Das bedeutet aber keinesfalls, dass das Team aus der Pflicht genommen wird, ein schnellstmögliches Deployment sicherzustellen.
Das Team unterstützt das Deployment und fordert ständig Informationen zum aktuellen Status ein.
Erst dann, wenn die Anforderung komplett umgesetzt, gereviewed und getestet ist und im Livesystem von echten Kunden verwendet wird, wandert die Anforderung in die DONE-Spalte und ist somit abgeschlossen.

Abbildung 4: Anforderungen wandern auf dem Board von links nach rechts

Der Kunde

Wir haben bereits öfter vom „Kunden“ gesprochen. Wer ist nun dieser Kunde?
Das Team arbeitet für den Product-Owner. Der Product-Owner des Teams ist Henning.  Henning war früher Projektmanager und ist heute Product-Owner mit Leib und Seele.
Henning entwickelt die Vision des Produktes. Er weiß genau, was vom Team wann umgesetzt werden muss, um den Online-Shop zum Erfolg zu führen. Er definiert die Anforderungen, die er dem Team übergibt.
Wie sieht jetzt eine typische Anforderung Scrum aus?
Eine Anforderung in Scrum wird User-Story genannt. Eine User-Story hat idealerweise die folgende Form:

Als X möchte ich Y um Z.

Das beantwortet die drei wichtigsten Fragen, die das Team braucht, um eine Anforderung umzusetzen.

Wer möchte etwas haben?
Was möchte derjenige haben?
Warum wird diese Funktionalität gebraucht?

Ein Beispiel für eine ausformulierte User-Story ist in Abb. 5 zu sehen. Dieser eine Satz genügt einem gut eingespielten Scrum-Team um eine Anforderung umzusetzen. Alle weiteren Fragen werden in separaten Planungs-Meetings geklärt.

Abbildung 5: Eine typische User-Story

Ein neuer Tag, ein neuer Sprint

Überspringen wir einige Tage. Das Team hat den laufenden Sprint abgeschlossen und sowohl Henning als auch das Team sind sehr zufrieden mit dem aktuellen Status des Projektes.
Für den neuen Sprint ist das Sprint-Backlog (die TODO-Spalte) gut gefüllt (ähnlich Abb. 3).
Das Team ist hochmotiviert und beginnt direkt mit der Arbeit und den ersten Stories, die auch direkt in die DEV-Spalte wandern.
Karl übernimmt Anforderung A („Als Kunde möchte ich meine Kundendaten ändern, um nach einem Umzug trotzdem Bestellungen an die richtige Adresse geliefert zu bekommen“), Markus Anforderung B („Als Kunde möchte ich alle Links im Online-Shop mit neuen Styles gerendert bekommen, damit ich diese besser finden kann.“).
Anforderung B ist von Markus sehr schnell umgesetzt und wandert weiter in die QS-Spalte, wo sie von Georg nochmals überprüft wird.
Markus nimmt inzwischen bereits die Anforderung C („Als Administrator möchte ich tägliche Reportings über eingegangene Bestellungen erhalten, um schnell auf Lagerengpässe reagieren zu können“) in Bearbeitung, denn er will keine Zeit verschwenden. Am Ende des ersten Tages ist auch Karl mit Anforderung A fertig, so dass sich auf dem Board das Bild aus Abb. 6 ergibt.

Abbildung 6: Das Scrum-Board in Aktion

Leider muss Markus sehr schnell feststellen, dass die Anforderung C nicht umgesetzt werden kann, da die Schnittstelle zur Anbindung an das Lagerhaltungssystem noch nicht bereit steht. Die Story ist also blockiert.
Markus weist Arne, den Scrummaster auf dieses Problem hin, markiert die blockierte Story auf dem Board mit einem roten Blitz und nimmt sofort die Anforderung D in Bearbeitung. Es ergibt sich das Bild aus Abb. 7.

Abbildung 7: Ein Problem zeichnet sich ab

Arne der Scrummaster betrachtet das Board und kann nur seinen Kopf schütteln.
Sein Bauchgefühl sagt ihm, dass zu viele Stories parallel in Bearbeitung sind.
Ein Scrum-Board dient zur Visualisierung der Arbeit des Teams. Die Stories auf dem Scrumboard verhalten sich wie Autos auf einer Autobahn. Eine Autobahn hat keinen anderen Zweck, als auf ihr möglichst schnell von Punkt A nach Punkt B zu gelangen.
Analog möchten die Stories auf dem Board möglichst schnell von der TODO- in die DONE-Spalte. Erst wenn ein Feature live ist und von echten Kunden verwendet wird, wird dadurch auch ein echter Wert für den Kunden, also für Henning geschaffen.
Was passiert auf einer Autobahn, wenn zu viele Autos gleichzeitig unterwegs sind? Es kommt zum Stau. Genau dasselbe geschieht auf dem Board. Sind zu viele Stories gleichzeitig in Bearbeitung werden die Stories dadurch nicht schneller, sondern viel langsamer abgeschlossen. Teammitglieder blockieren sich gegenseitig und die Reintegration von Features wird komplizierter (beispielsweise durch Konflikte beim Mergen von verschiedenen Änderungen).
Ein weiteres Problem das sofort jedem im Team auffällt: Hans der Tester legt bisher die Beine hoch und dreht Däumchen. Denn bisher hat es keine Story auch nur bis in die TEST-Spalte geschafft. Das Schlimmste, was Hans jetzt passieren kann ist, dass alle Anforderungen gleichzeitig fertiggestellt werden und sich das Bild in Abb. 8 ergibt.
Hans wird mit so viel Arbeit überhäuft, dass nun die TEST-Spalte das Board blockiert, und er keine weiteren Stories aufnehmen kann.
Und nach wie vor ist das Team weit davon entfernt, eine Story bis ins Live-System gebracht zu haben.

Der Scrummaster wird aktiv

Arne hat kürzlich ein Buch über Kanban gelesen. Kanban wird von vielen oft als Alternative zu Scrum verstanden. Arne aber versteht Kanban eher als ein Set von Spezialwerkzeugen, um die Produktivität des Teams zu steigern.
Eine erste Maßnahme, die Arne dem Team vorstellt ist das Messen der Lead-Time. Die Lead-Time ist eine hilfreiche Metrik, die sehr einfach zu erfassen ist und üblicherweise sehr schnell zu ersten „Aha-Momenten“ führt.
Das Team misst die Lead-Time für eine erste Story und das Ergebnis ist in Abb. 9 zu sehen.

Abbildung 9: Das Team misst die Lead-Time

Abbildung 9: Das Team misst die Lead-Time

Die Lead-Time bezeichnet die Zeit, die eine Story braucht, um von der TODO- bis in die DONE-Spalte zu gelangen. Sie wird gemessen, indem auf der jeweiligen Story am Board jedes Mal das Datum vermerkt wird, wenn die Story eine Spalte weiter wandert.
Beispielsweise wurde die Anforderung in Abb. 9 am 01.04. vom Product-Owner ins Team gegeben. Sie wurde am 02.04. in Bearbeitung genommen und am 08.04 an Hans den Tester übergeben.
Am Ende des Sprints kann über die Lead-Time aller Stories ein Mittelwert gebildet werden, der nichts anderes als eine Metrik für die Geschwindigkeit des Teams darstellt.
Insbesondere Engpässe lassen sich so auch sehr schnell erkennen. Dauert der Sprung von der DEV- in die TEST-Spalte jedes Mal sehr lange, wird dies am Board sofort sichtbar und man kann hinterfragen, wieso das so ist und was dagegen getan werden muss.
Das Team ist begeistert und nimmt sich vor, die Messung der Lead-Time zukünftig für alle Stories durchzuführen.

WIP-Limits für das Team
Allein durch die Einführung der Lead-Time wird dem Team schon viel klarer, wo die Engpässe liegen. Die Messung der Lead-Time macht Probleme sichtbar, löst diese aber nicht.
Arne macht einen weiteren Vorschlag.
Kanban bietet viele Werkzeuge, die zum Ziel haben, Arbeit fokussiert und konzentriert zum Abschluss zu bringen.
Das vielleicht wichtigste Werkzeug hierfür sind „Work in Progress“-Limits (WIP-Limits).
Durch die Definition von WIP-Limits limitiert das Team die Anzahl an Stories oder auch Tasks, die parallel in Bearbeitung sein dürfen.
WIP-Limits sind kein intuitives Werkzeug. Es braucht zu Anfang ein wenig Zeit, dieses Werkzeug effektiv einzusetzen.
Das Team und Arne haben das Problem auf dem Board zuvor schon richtig erkannt. Es wird zu viel Arbeit parallel gemacht.
Arne schlägt vor, einen Sprint lang mit WIP-Limits zu arbeiten um zu sehen, wie sich dieses Werkzeug auf die Produktivität auswirkt.

Dies geschieht ganz einfach dadurch, dass auf dem Board für jede Spalte oder auch zusammengefasst für mehrere Spalten definiert wird, wie viele Stories gleichzeitig in Bearbeitung sein dürfen (Abb. 10).

Abbildung 10: WIP-Limits begrenzen die parallele Arbeit

Das Team setzt sich zusammen und definiert, dass in der DEV- und QS-Spalte nicht mehr als zwei Stories gleichzeitig in Bearbeitung sein sollten.
Arbeitet Karl also an „Story A“ und Markus an „Story B“, ruht Georg sich in der Zwischenzeit aus? Keinesfalls. Da durch das definierte WIP-Limit nicht mehr als zwei Anforderungen gleichzeitig bearbeitet werden dürfen, wird das Team gezwungen, zusammen zu arbeiten. Georg unterstützt seine Teammitglieder um möglichst schnell eine Anforderung mindestens in die TEST-Spalte zu bringen. Dies schafft einen freien Slot und das Team kann „Story C“ in Bearbeitung nehmen.
Das Team definiert, dass Hans der Tester nur eine Story gleichzeitig bearbeiten sollte, um diese möglichst schnell an das Deployment-Team weiterreichen zu können.
Betrachten wir die Situation in Abb. 11.

Abbildung 11: WIP Limits fördern Team-Work

Hans ist mit dem Testen von „Story B“ beschäftigt. Die Stories „A“ und „C“ befinden sich bereits in der QS-Spalte. Das Board blockiert, denn das definierte WIP-Limit für die TEST-Spalte gestattet es nicht, weitere Stories aufzunehmen.
Können sich die Entwickler nun zurücklehnen? Ist das der Vorteil von WIP-Limits? Keinesfalls, denn um einen freien Slot zu schaffen und damit „Story D“ möglichst schnell in Bearbeitung nehmen zu können, unterstützen die Entwickler Hans bei technischen Problemen (Test-Umgebung funktioniert nicht), versorgen ihn mit Know-How über die Implementierung (erleichtert das Testen) oder bringen ihm einfach frischen Kaffee, um ihn wach zu halten.

Schon nach wenigen Tagen im Sprint zeichnet sich ab, dass die Lead-Time der Stories viel kleiner ist, als noch vor der Einführung von WIP-Limits.
WIP-Limits zwingen das Team, fokussiert an wenigen Anforderungen zu arbeiten und diese zum Abschluss zu bringen.

Scrum oder Kanban oder Scrumban?

Was ist nun besser und sollte verwendet werden? Scrum oder Kanban? Für das Team ist dies keine „Entweder-oder-Entscheidung“, sondern idealerweise verwendet man das Beste aus beiden Welten.
Das Team arbeitet weiterhin mit festen Sprintlängen, was Henning die Planung erleichtert und macht weiterhin Daily-Scrums.
Zusätzlich misst das Team die Lead-Time und arbeitet mit WIP-Limits.
Natürlich bieten sowohl Scrum als auch Kanban noch viel mehr Ansätze, Werkzeuge und Ideen als in diesem kurzen Artikel vorgestellt werden konnten.
Ich arbeite derzeit als Entwickler und Scrummaster in einem agilen Team. In diesem Team nutzen wir die Vorteile aus beiden Welten und alle Werkzeugen, die uns sinnvoll erscheinen.
Mein Rat ist, gehen Sie nicht dogmatisch sondern pragmatisch vor. Agil arbeitet man nicht schwarz oder weiß. Sie können aus einer Vielzahl an Werkzeugen wählen. Nutzen Sie diejenigen, die für Sie sinnvoll erscheinen und experimentieren Sie.
Stellt sich nach einem oder zwei Sprints heraus, dass ein Werkzeug nicht das erhoffte Ergebnis bringt, dann nehmen Sie das Thema in der Retrospektive auf und diskutieren Sie darüber.
Das Ziel ist immer das Gleiche. Schaffe Mehrwert für Deinen Kunden.
Das geht nur durch produktives Arbeiten und ständige Verbesserung.

Kontaktieren Sie mich, wenn Sie Fragen zum Artikel oder allgemein zu agiler Arbeit haben. Ich hoffe, das Lesen des Artikels hat Ihnen genauso viel Freude bereitet wie mir das Schreiben.

Vielen Dank an Wolfgang Wiedenroth für die wertvollen Tipps.


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