Montag, 25. Oktober 2010

Thinking about strategies

Es entstehen immer wieder Debatten bezüglich neuer Programmiersprachenkonstrukte. Closures für Java 7 werden von der Java Community gefordert und schon über einen langen Zeitraum hinweg diskutiert. In Java sind allerdings bereits Funktionsobjekte mit anonymen Klassen umsetzbar. Funktionsobjekte erfordern mehr Schreibarbeit als Closures, erfüllen aber den gleichen Zweck wie Closures.

Der Einfachheit halber und aufgrund der harten umzusetzenden Funktionalitäten für Closures wurden zunächst Funktionsobjekte den Closures in Java vorgezogen. Closures werden deshalb erst in naher Zukunft ein Teil der Java-Syntax sein. Entwurfsentscheidungen nach dem KISS Prinzip sind berechtigt, lösen aber unter Umständen nicht alle Probleme, sodass nur eine Problemverschiebung stattfindet. Zeiger auf Funktionen (C/C++) und Lambda-Ausdrücke (C#) sind in anderen Programmiersprachen gängig. Alle diese Sprachkonstrukte sind allerdings nur eine Implementierung des Strategiemusters, sofern man das Muster verstanden hat, versteht man auch den Anwendungszweck der Sprachkonstrukte.

Es ist eine gute Strategie Konzepte und Anwendungsfälle der gängigen Entwurfsmuster zu lernen. Entwurfsmuster und Java-Idiome lösen immer wiederkehrende Probleme auf eine standardisierte Art und Weise. Die Kernaussagen von Entwurfsmustern sind essentiell und basieren auf den Erkenntnissen der Praxis, die von sehr erfahrenen Programmierern extrahiert und zusammengefasst wurden. Das Strategiemuster ist ein Kandidat, der die Anforderungen für das Variieren von Algorithmen für andere Muster vorgibt und wesentliche Voraussetzungen für saubere Software definiert.

Die Kernaussagen des Strategiemusters sind:
  1. Trenne veränderlichen Quellcode von gleichbleibendem
  2. Kapsele Algorithmen damit diese dynamisch ersetzbar sind
  3. Programmiere auf Basis von Schnittstellen
  4. Ziehe Komposition (soweit möglich) der Vererbung vor
Das Strategiemuster fördert die Flexibilität und Evolvierbarkeit von Applikationen. Die innere Struktur einer Applikation ist dabei sehr flexibel an neue Funktionalitäten anpassbar, ohne Seiteneffekte in anderen Teilen einer Applikation hervorzurufen. Es ist ratsam das Strategiemuster an kritische Stellen einer Applikation, einzusetzen. Kritische Stellen sind aus fachlicher Sicht solche, von denen man weiß, dass sie in der Folge erweitert oder sogar ausgetauscht werden. 

Die Implementierung des Strategiemusters mit Closures, Funktionsobjekten oder Lambda-Ausdrücken spielt nicht die wesentliche Rolle. Wichtig ist die Erkenntnis, dass das Sprachkonstrukt nur ein Mittel für die Implementierung ist. Das Verständnis für Konzepte und Modelle hat neben dem Beherrschen von Sprachkonstrukten deshalb einen hohen Stellenwert für die  Realisierung sauberer Software.

In dem nachfolgenden Szenario wird eine Berechnung mit zwei Argumenten auf Basis einer möglichen Implementierung des Strategiemusters veranschaulicht. Die Implementierung hätte auch auf Basis einer Klassenhierarchie erfolgen können. Das Strategiemuster bietet sich im Kontext paralleler Klassenhierarchien an, um je nach konkreter Strategie die Instanzen von Klassen zu verknüpfen.

Strategie für eine einfache Berechnung:

public interface CalculationStrategy<T extends Number> {

    T operation(T firstArg, T secondArg);
}

Implementierung der Berechnung:

public class Calculator<T extends Number> {

    public T calculate(T firstArg, T secondArg, CalculationStrategy<T> calculation) {
      
        return calculation.operation(firstArg, secondArg);
    }
}

Unit-Test der Berechnung:

import static org.junit.Assert.*;
import org.junit.Test;

public class TestCalculator {

    @Test
    public void testAdd() {
      
        final Calculator<Integer> intCalculator = new Calculator<Integer>();
        int sum = intCalculator.calculate(1, 1, new CalculationStrategy<Integer>() {

            @Override
            public Integer operation(Integer firstArg, Integer secondArg) {
              
                return firstArg + secondArg;
            }                      
        });
      
        assertEquals(2, sum);
    }
  
    @Test
    public void testSubtract() {
      
        final Calculator<Integer> intCalculator = new Calculator<Integer>();
        int dif = intCalculator.calculate(1, 1, new CalculationStrategy<Integer>() {

            @Override
            public Integer operation(Integer firstArg, Integer secondArg) {
              
                return firstArg - secondArg;
            }                      
        });
      
        assertEquals(0, dif);
    }
}

Concept and model knowledge are important for the implementation of clean software!


Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.

Mittwoch, 20. Oktober 2010

Time to talk about the contracts

Java EE 5 hat den Wandel von einem schwergewichtigen zu einem leichtgewichtigen Standard vollzogen. Die vielen nützlichen Funktionalitäten des Java EE Standards, die wesentlich von der Java Community geprägt worden sind, erlauben es, Java EE Anwendungen sehr viel einfacher als in früheren Versionen des Standards zu implementieren. Trotz des Optimismus ist dennoch Vorsicht geboten!

Warum ist der Java EE Standard wichtig? Die Frage ist einfach zu beantworten: Der Java EE Standard definiert ein transparentes  und verlässliches Verhalten für Geschäftskomponenten zur Laufzeit und im Fehlerfall. Die Spezifikationen des Java EE Standards beinhalten die einzuhaltenden Verträge, um das definierte Verhalten aus Sicht des Applikationsservers einhalten zu können. Diese Verträge sind essentiell und führen beim nicht Einhalten wiederum zu undefinierten Verhaltensweisen.

Der Java EE Standard definiert im Detail das Transaktionsverhalten bei einem Fehler. Laufzeitausnahmen (Unchecked Exceptions) führen zu einem Rollback bei Container Managed Transactions (CMT), Applikationsausnahmen (Checked Exceptions) hingegen nicht. Ein Rollback kann darüber hinaus über den Session Context einer EJB mit der Methode setRollbackOnly ausgelöst werden. Eine praxistaugliche Möglichkeit ein Rollback zu veranlassen und dennoch eine Applikationsausnahme zu werfen ist die folgende Annotation: @ApplicationException(rollback = true) an einer Applikationsausnahme. Warum eine Applikationsausnahme für den Client im Fehlerfall häufig günstiger ist, als eine Laufzeitausnahme, soll zum Nachdenken anregen. Der Auftritt einer Ausnahme auf der Clientseite ist jedenfalls definiert und in der EJB Spezifikation haarklein erläutert.

Keine Spekulation im Fehlerfall und kein Trail-and-Error bei der Implementierung, sondern ein klar spezifiziertes und nachvollziehbares Verhalten, das zeichnet den Java EE Standard aus!

Verträge sind nicht nur bei Java EE Applikationen essentiell, sondern ebenfalls in der Programmiersprache Java tief verwurzelt. Jeder professionelle Java-Entwickler kennt beispielsweise den equals Vertrag.

Vertrag der equals Methode:
  1. Reflexiv, für jede nicht Null-Referenz liefert a.equals(a) true
  2. Symmetrisch, für jede nicht Null-Referenz liefert a.equals(b) true wenn b.equals(a) true liefert
  3. Transitiv, für jede nicht Null-Referenz und wenn a.equals(b) und b.equals(c) true liefern, muss auch
    a.equals(c) true liefern
  4. Konsistent, für jede nicht Null-Referenz muss die equals Methode konsistent true oder false liefern
  5. Für einen Null-Referenz a.equals(null) muss die equals Methode false liefern
Zerbricht die equals Methode eines Objektes, so betrifft dies nicht nur das Objekt selbst, sondern auch die Verwender des Objektes. Wird die equals Methode überschrieben, ist auch die hashCode Methode zu überschreiben. Dies sind grundlegende Verträge, die Java zwingend vorschreibt. Viele weitere Verträge der Programmiersprache Java sind einzuhalten. Ein Vertragsbruch wird nicht immer durch den Compiler angezeigt und hat häufig sporadische Laufzeitfehler zur Folge. Unit-Tests und Plugins wie PMD, FindBugs und Checkstyle helfen, die Verträge von Java einzuhalten, lösen aber auf der anderen Seite nicht alle potentielle Vertragsbrüche auf. 

Ein Vertragsbruch ist nicht immer nur syntaktischer Natur, sondern kann auch durch die Semantik einer Applikation entstehen. Häufig spielt die Nebenläufigkeit eine Rolle. Weitergehende Techniken im Bereich der Serialisierung und Reflections können zum Vertragsbruch und zur Fragilität einer Anwendung führen. Grundlegende Techniken der objektorientierten Programmierung, wie beispielsweise die Vererbung können ebenfalls zur Fragilität beitragen. Fallgruben lauern überall und nicht jede Fallgrube ist im dichten Netz der Java API offensichtlich.

Java Brainteaser:

Warum sollen in einem Konstruktor (weder direkt noch indirekt) keine überschriebenen Methoden aufgerufen werden?

Verträge von Java und Java EE sind strikt einzuhalten. Darüber hinaus sind die Menge der Java Idiome und Prinzipien ein gutes Mittel, um saubere Java Software zu schreiben.

Seit Java 5 (Tiger Release) sind die Java Generics ein Teil der Syntax. Generics wurden von der Java Community gefordert, weil die Anwendung der Java Collections nicht typsicher war. Die Entwickler von Java gingen bei den Collections zunächst nach dem KISS Prinzip vor und haben es dem Anwender der Programmiersprache überlassen, sich um die Typsicherheit zu kümmern. In der Praxis haben sich dabei Laufzeitprobleme eingestellt (in der Regel ClassCastExceptions), die durch die Generics mit höherer Typsicherheit wesentlich eingegrenzt wurden.

Generics verwenden intern das Prinzip von "erasure", dabei werden Typparameter zur Kompilierzeit durch Casts ersetzt. Der Byte-Code ist deshalb nicht anders als bei Collections, die keine Generics verwenden. Diese Implementierung erlaubt es Generics mit Legacy Collection Quellcode zu vermischen ohne dabei Kompatibilitätsprobleme hervorzurufen. Der Nachteil dabei ist allerdings, dass die Typsicherheit der Generics durch den Programmierer bei der Anwendung von "raw Collections" gebrochen werden kann.

Bei der Anwendung von Generics sind Wildcards gebräuchlich. Wildcards erlauben die Substitution von Datentypen, sodass in einer generischen Collection nicht nur ein einziger Datentyp gespeichert werden kann.

Wildcard-Prinzip:
  1. Nur Lesen, verwende ? extends T
  2. Nur Schreiben, verwende ? super T
  3. Schreiben und Lesen, verwende T (keine Wildcards)
Dieses Prinzip ist durch das Studieren der Generics mit entsprechender Ableitung ermittelbar. Die Menge an Java Idiomen und Prinzipien sind in Kombination mit dem Einhalten der Java Verträge in der Summe die wesentliche Voraussetzung für korrekte Software die einem hohen Qualitätsstandard entspricht.

Read, read, read, know the contracts, use Java Idioms and write Unit-Tests to be on the safe side!

Freitag, 15. Oktober 2010

Information Hiding Principle by example

Einer der Grundpfeiler der objektorientierten Programmierung ist das Verbergen von Informationen. Für die Implementierung von Klassen ist die Konsequenz aus diesem Grundsatz, das Klassen und deren Mitglieder (Felder und Methoden) nur über eine schlanke Schnittstelle öffentlich zugänglich  sein sollen. Möglichst wenige Details einer Implementierung nach außen tragen und den Zugang zu der Klassenimplementierung so schwer zugänglich wie möglich zu programmieren ist eine "Best Practice". Clean Code Developer fühlen sich dieser "Best Practice" verpflichtet und wenden deshalb das Information Hiding Principle (IHP) an.

Je weniger Details der Verwender einer Klasse von außen sieht, umso geringer ist die Kopplung zwischen der Klassenimplementierung und dem Verwender der Klasse.Geringe Kopplung erhöht die Evolvierbarkeit und schafft isolierte Einheiten, die wiederum eine strenge Voraussetzung für Unit-Tests sind.

Ein Verstoß gegen die Kapselung wird oftmals durch öffentliche Felder signalisiert. Öffentliche Felder sind nicht nur aus Sicht der Evolvierbarkeit unschön, sondern bergen die Gefahr, in nebenläufigen Umgebungen zu zerbrechen. Dies führt häufig zu sporadischen Laufzeitfehlern, die nur sehr schwer zu debuggen und zu finden sind.Öffentliche Felder sind deshalb grundsätzlich zu vermeiden und ausschliesslich in geschützter Form durch Zugriffsmethoden (Getter/Setter) anzusprechen.

Vorteile des Information Hiding Principle (IHP) sind:
  1. Limitierung der Konsequenzen bei Änderungen
  2. Minimale Beeinflussung anderer Klassen bei einer Korrektur
  3. Wesentliche Erhöhung der Wiederverwendbarkeit
  4. Bessere Testbarkeit und höhere Zuverlässigkeit durch Isolation
Datenklassen sollen soweit möglich unveränderlich (immutable) entworfen werden. Dies erreicht man durch die Initialisierung der Felder einer Klasse im Konstruktor und der ausschliesslichen Definition von Getter-Methoden. Setter-Methoden, die eine Zustandsänderung herbeiführen können, sind in diesem Fall beim Entwurf zu vermeiden. Unveränderliche Klassen sind, ohne weitere Synchronisation, sicher in nebenläufigen Umgebungen anwendbar und weniger Fehleranfällig.

Kriterien für unveränderliche Klassen beinhalten:
  1. Keine Methoden, die den Status verändern (Setter)
  2. Ausschliesslich final Felder
  3. Alle Felder sind private
  4. Klasse ist final (nicht durch Vererbung erweiterbar)
Beispiel für eine einfache unveränderliche Klasse (ohne weitere Java-Verträge zu erfüllen) :

package ccd.java.idioms.immutable;

public final class ImmutableClass {

    private final String field;
   
    public ImmutableClass(String field) {
       
        this.field = field;
    }

    public String getField() {
       
        return field;
    }
}

Neben der Kapselung der Felder und Methoden einer Klasse ist es ratsam, die Sichtbarkeit auf die Klassenimplementierung einzuschränken. Grundsätzlich öffentliche Klassen zu verwenden ist fehleranfällig und kann ein Sicherheitsrisiko darstellen. Neben der Einschränkung der Klassensichtbarkeit ist es im Kontext von Sicherheitsanforderungen und der Klassenversionierung unter Umständen angebracht, Packages und JAR-Dateien zu versiegeln (sealed JARs). Versiegelte JAR-Dateien sind eine wesentliche Voraussetzung für die konsistente Versionierung von Klassen.

Eine dedizierte Sicht auf eine Klassenimplimentierung über eine schlanke Schnittstelle ist die optimale Voraussetzung für eine kompakte API. Diese Technik führt zu schnittstellengetriebenen Frameworks, bei der Factory-Methoden eine Sicht auf die öffentliche Schnittstelle einer Klassenimplementierung freigeben.

Öffentliche Schnittstelle für das Implementierungsbeispiel:

package ccd.java.idioms.hidden.implementation;

public interface PublicInterface {
  
    String implementation();
}

Implementierungsbeispiel mit zwei Ansätzen zum Verbergen einer Implementierung:

package ccd.java.idioms.hidden.implementation;

class PackagePrivateClass implements PublicInterface {

    @Override
    public String implementation() {
      
        return "hidden-implementation";
    }
}

public class PublicClass implements PublicInterface { 

    private PublicClass() {/* prevents instantiation */}
 
    public static PublicInterface newPrivateInstantiationInstance() {
     
        return new PublicClass();
    }
 
    @Override
    public String implementation() {
      
        return "hidden-implementation";
    }

    public static PublicInterface newPackagePrivateInstance() {
     
        return new PackagePrivateClass();
    }
}

Unit-Tests des Implementierungsbeispiels:

package ccd.java.idioms.hidden.implementation.test;

import static org.junit.Assert.*;
import org.junit.Test;
import ccd.java.idioms.hidden.implementation.PublicClass;
import ccd.java.idioms.hidden.implementation.PublicInterface;

public class HiddenImplementationTest {

    private static final String HIDDEN_IMPL = "hidden-implementation";

    @Test
    public void testHiddenWithPrivateInstantiation() {
     
        final PublicInterface hiddenWithPrivateInstantiation= PublicClass.newPrivateInstantiationInstance();
        assertEquals(HIDDEN_IMPL, hiddenWithPrivateInstantiation.implementation());
    }
  
    @Test
    public void testHiddenWithPackagePrivate() {
      
        final PublicInterface hiddenWithPackagePrivate= PublicClass.newPackagePrivateInstance();
        assertEquals(HIDDEN_IMPL, hiddenWithPackagePrivate.implementation());             
    }
}

Minimize the accessibility of classes, fields and methods is not only a good advice but still a best practice!


Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.

Freitag, 8. Oktober 2010

Model meets Implementation

Serviceorientierte Architekturen (SOA) setzen sich aus Services zusammen. Ein Service erfüllt einen Vertrag für eine spezifizierte Geschäftslogik. Services sind kohäsiv und lose miteinander gekoppelt. Häufig werden Services prozessorientiert entworfen und in der Folge mit anderen Services orchestriert.

Der wesentliche Vorteil von prozessorientierten Services ist, dass die Service-Definition in der Spezifikationsphase von Fachspezialisten, den Business Analysten entworfen wird. Die Service-Definition wird dabei werkzeugunterstützt entworfen und dient in der Implementierungsphase als Vorlage für die Programmierung. Prozessschritte in Service-Definitionen sind häufig besser verständlich als Use Cases und UML-Diagramme. Die Fachabteilung und Entwickler haben ein gemeinsames Verständnis für Prozessdiagramme, was bei technisch getriebenen UML-Diagrammen nicht immer der Fall ist.

Praxistauglich sind zwei Ansätze zur prozessorientierten Implementierung von Services. Der eine Ansatz definiert Prozessschritte über Datenbankstrukturen. Dabei wird der Status in jedem Prozessschritt neu gesetzt und entsprechend  in nachfolgenden Prozessschritten zur Verarbeitung ausgewertet. Bei dieser Vorgehensweise werden EJBs entworfen, die einzelnen Prozessschritten entsprechen. Diese einfache Art der prozessorientierten Verarbeitung bietet sich dann an, wenn keine Business Process Manegement (BPM) Engine als weitere Komplexitätsstufe in ein Java EE Projekt integriert werden soll. Nachteil der Lösung ist allerdings, dass zwar Prozessschritte abgebildet werden können, aber praktisch keine Möglichkeit zur Orchestrierung von Services besteht.

Die Anforderung der Service-Orchestrierung ist  nur bedingt notwendig und je nach Plattform stärker oder schwächer ausgeprägt. Im Enterprise Service Bus (ESB) Kontext, bietet der ESB selbst die Möglichkeit zur Orchestrierung von Services an, sodass die einzelnen Services nicht zwingend die Fähigkeit der Orchestrierung besitzen müssen. Aufgrund der hohen Komplexität von ESB-Lösungen kann es deshalb sinnvoll sein, prozessorientierte Datenbankstrukturen einer komplexeren BPM Lösung vorzuziehen.

BPM beinhaltet eine weitere Komplexitätsstufe für ein Java EE Projekt. Auf der anderen Seite erfüllt eine BPM Engine folgende wesentliche Anforderungen: Separation of Concerns (Delegierung von Verantwortlichkeiten) und Trennen der Prozessdefinition von der Implementierung. Ein Service ist aufgrund der Anforderungserfüllung flexibel änderbar, sodass sowohl Prozessschritte als auch die Implementierung eines Prozessschrittes ohne Seiteneffekte ausgetauscht werden können. Die Definitionssprache für Services (BPEL) ist standardisiert und die gängigen BPM Engines sind in ähnlicherweise nutzbar. Graphische Werkzeuge stehen zur Prozessdefinition zur Verfügung. Diese Werkzeuge erlauben es, ActionHandler, Events und Exceptions für einen Prozessschritt zu definieren. Die Prozessdefinition ist deshalb nahtlos mit der Implementierung eines Prozesses verknüpfbar. Darüber hinaus fügen sich BPM Engines in das Transaktionskonzept einer Java EE Anwendung ein und können neben entfernter Geschäftslogik, Datenbanken und MOM-Systeme transaktionssicher ansprechen.

Es ist sicherlich Gewohnheit prozessorientiert zu denken und Prozesse zu implementieren. Der Einstieg lohnt sich allerdings, weil Prozessschritte aus kleinen Einheiten bestehen, die nacheinander abgearbeitet werden und flexibel miteinander verknüpfbar sind. Konform zu Design by Contract stellt sich ein sicheres Gefühl für eine Anwendung ein, bei der wesentliche fachliche Überlegungen stattgefunden haben. Das Angst, dass bei fachlichen Änderungen die zuvor implementierte Softwarestruktur zerstört werden könnte, ist beim prozessorientierten Ansatz weniger stark ausgeprägt. Die Fachlichkeit ist im Prozessdiagramm klar definiert.  Das Prozessdiagramm wird zur Laufzeit einer Anwendung intepretiert und per Delegierung (Command Pattern) in einzelnen Prozessschritten mit Quellcode, welcher den Geschäftsanforderungen genügt, angereichert.

BPM ist bei der Programmierung von Web Services nicht mehr wegzudenken und besonders für Anwendungen im Web Services Umfeld empehlenswert. Prozessschritte können dabei synchron oder asynchron verarbeitet werden. Die Orchestrierung von Web Services mit BPEL ist weit verbreitet und erfreut sich großer Beliebtheit.

In dem folgenden Szenario wird aus Sicht des Reviewers der Review-Prozess prozessorientiert mit der JBoss JBPM Engine abgebildet. Die JBPM Engine von JBoss ist als eigenständige Applikation in eine JBoss-Umgebung integrierbar und bietet für Eclipse ein graphisches Werkzeug zur Prozessdefinition an. Prozessdefinitionen können mit JBoss JBPM in einer Datenbank abgelegt werden, sodass langlaufende Prozesse abbildbar sind. Persistente Prozessdefinitionen mit entsprechender Interaktion zur Laufzeit einer prozessorientierten Anwendung sind ein wesentliches Kriterium für Quality of Services.

JBoss JBPM Prozessdefinition für den Review-Prozess

Prozessdefiniiton des Review-Prozesses
 

In den Prozessschritten, die durch Knoten gekennzeichnet sind, werden ActionHandler registriert. ActionHandler implementieren die Geschäftslogik und tauschen Daten zur Verarbeitung und Steuerung von Prozessschritten über den Ausführungskontext aus.

MessageHandler zur Anzeige von Statusübergängen

package ccd.jee.jbpm.handler;

import org.apache.log4j.Logger;
import org.jbpm.graph.def.ActionHandler;
import org.jbpm.graph.exe.ExecutionContext;

public class MessageHandler implements ActionHandler {

    private Logger log = Logger.getLogger(MessageHandler.class);

    private String message;

    @Override
    public void execute(ExecutionContext context) throws Exception {

        processMessage(context);
    }

    private void processMessage(ExecutionContext context) {

        message = (String) context.getVariable("MESSAGE");
        log.info(message);
    }
}

DecisionHandler zur Steuerung des Review-Prozesses

package ccd.jee.jbpm.handler;

import org.jbpm.graph.exe.ExecutionContext;
import org.jbpm.graph.node.DecisionHandler;

public class ReviewDecisionHandler implements DecisionHandler {

    @Override
    public String decide(ExecutionContext executionContext) throws Exception {
       
        boolean result = (Boolean) executionContext.getVariable("SOURCE_CHANGE_OK");
       
        if(result) {
           
            return "OK";
        }
       
        return "nicht OK";
    }
}

Unit-Test des Review-Prozesses

package ccd.jee.jbpm.handler;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.util.HashMap;
import java.util.Map;
import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.graph.exe.ProcessInstance;
import org.junit.Before;
import org.junit.Test;

public class CCDReviewProcessTest {

    private ProcessDefinition processDefinition;
    private ProcessInstance processInstance;

    @Before
    public void setUp() {

       processDefinition = ProcessDefinition.parseXmlResource("CCDReviewProcess/processdefinition.xml");
       assertNotNull("Definition should not be null", processDefinition);

       processInstance = new ProcessInstance(processDefinition);
    }

    @Test
    public void testCCDReviewProcessOK() {

        startState();

        identifyCodeSmellState();

        signCodeSmellState();

        commitSourcesState();

        informAutorState();

        waitForSourceChangeState();

        signalSourceChangeOk();

        sourceChangeOkState();

        endState();
    }

    @Test
    public void testCCDReviewProcessNotOK() {

        startState();

        identifyCodeSmellState();

        signCodeSmellState();

        commitSourcesState();

        informAutorState();

        waitForSourceChangeState();

        signalSourceChangeNotOk();

        sourceChangeNotOkState();

        repeatState();
    }

    private void startState() {

        assertEquals("Instance is in start state", processInstance.getRootToken()
                .getNode().getName(), "Review starten");

        assertNull("Message variable should not exist yet", processInstance
                .getContextInstance().getVariable("MESSAGE"));

        processInstance.getContextInstance().setVariable("MESSAGE",
                "gehe zu Code Smell identifizieren");
       
        processInstance.signal();
    }

    private void identifyCodeSmellState() {

        assertEquals("Instance is in state", processInstance.getRootToken().getNode()
                .getName(), "Code Smells identifizieren");

        processInstance.getContextInstance().setVariable("MESSAGE",
                "gehe zu Code Smell kennzeichnen");
       
        processInstance.signal();
    }
   
    private void signCodeSmellState() {

        assertEquals("Instance is in state", processInstance.getRootToken().getNode()
                .getName(), "Code Smells kennzeichnen");

        processInstance.getContextInstance().setVariable("MESSAGE",
                "gehe zu Quellcode einchecken");
       
        processInstance.signal();
    }
   
    private void commitSourcesState() {

        assertEquals("Instance is in state", processInstance.getRootToken().getNode()
                .getName(), "Quellcode einchecken");

        processInstance.getContextInstance().setVariable("MESSAGE",
                "gehe zu Autor informieren");
       
        processInstance.signal();
    }
   
    private void informAutorState() {

        assertEquals("Instance is in state", processInstance.getRootToken().getNode()
                .getName(), "Autor informieren");

        processInstance.signal();
    }
   
    private void waitForSourceChangeState() {

        assertEquals("Instance is in state", processInstance.getRootToken().getNode()
                .getName(), "Warten auf Korrektur");
    }
   
    private void sourceChangeOkState() {
       
        assertEquals("Instance is in state", processInstance.getRootToken().getNode()
                .getName(), "Korrektur in Ordnung");
       
        processInstance.signal();
    }
   
    private void sourceChangeNotOkState() {

        assertEquals("Instance is in state", processInstance.getRootToken().getNode()
                .getName(), "Korrektur nicht in Ordnung");

        processInstance.signal();
    }

    private void signalSourceChangeOk() {

        final Map<String, Boolean> contextVariables = sourceChangeOK();

        processInstance.addInitialContextVariables(contextVariables);
        processInstance.getContextInstance().setVariable("MESSAGE",
                "gehe zu Korrektur in Ordnung");
       
        processInstance.signal();
    }

    private void signalSourceChangeNotOk() {

        final Map<String, Boolean> contextVariables = sourceChangeNotOK();

        processInstance.addInitialContextVariables(contextVariables);
        processInstance.getContextInstance().setVariable("MESSAGE",
                "gehe zu Korrektur nicht in Ordnung");
       
        processInstance.signal();
    }

    private Map<String, Boolean> sourceChangeOK() {

        final Map<String, Boolean> contextVariables = new HashMap<String, Boolean>();
        contextVariables.put("SOURCE_CHANGE_OK", Boolean.TRUE);
        return contextVariables;
    }

    private Map<String, Boolean> sourceChangeNotOK() {

        final Map<String, Boolean> contextVariables = new HashMap<String, Boolean>();
        contextVariables.put("SOURCE_CHANGE_OK", Boolean.FALSE);
        return contextVariables;
    }
   
    private void endState() {

        assertEquals("Instance is in state", processInstance.getRootToken().getNode()
                .getName(), "Review beenden");
    }

    private void repeatState() {

        assertEquals("Instance is in state", processInstance.getRootToken().getNode()
                .getName(), "Review wiederholen");
    }
}

Das implementierte Szenario verdeutlicht die Trennung der Prozessdefinition und der eigentlichen Implementierung in den Handlern. Zur Laufzeit wird über die JBoss JBPM Engine die Prozessdefinition interpretiert, Handler angesprochen und Ereignisse im Prozessfortschritt ausgelöst. Prozessorientierte Anwendungen können mit Unit-Tests gesichert werden, sodass noch vor der Implementierung der Geschäftslogik der grundsätzliche Prozessablauf testbar ist.

Die Prozessdefinition in EJBs zu integrieren ist einfach und mit ein paar Zeilen Quellcode bewerkstelligt. Dadurch ergeben sich gute Bedingungen für den Einsatz einer BPM Engine in Java EE Anwendungen.

Model first is still a best practice for Web Service development!


Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.

Montag, 4. Oktober 2010

Use lean Java EE with care

Lean Software Development ist eine Disziplin der agilen Softwareentwicklung. Für die Softwareentwicklung bedeutet lean (schlank), nur das Wesentliche ohne überflüssigen Balast zu implementieren. Lean Softwareentwicklung wird von klaren Anforderungen und einem hohen Verständnis für eine Technologieplattform getragen.

Java EE hat aus der Entwicklerperspektive sehr viel Balast verloren. Java EE ist leichtgewichtig, Deployment Deskriptoren sind optional und werden zumeist durch Annotationen ersetzt. EJB 3 Komponenten implementieren schlanke Java Schnittstellen ohne zusätzliche EJB-Artifakte.

Java EE ist vorkonfiguriert ("convention over configuration" bzw. "configuration by exception"), sodass häufig mit wenig Quellcode ein vollfunktionsfähiger Service implementiert wird. Java EE ist durchgängig, sodass mit JSF 2, CDI, EJB 3 und der JPA mit wenig Quellcode Daten von einer Webseite in eine Datenbank geschrieben werden können. Java EE ist klar spezifiziert und durch eine Referenzimplementierung gesichert. Java EE ist ein Framework, der dem Entwickler keine unnötige Last aufbürdet. Java EE ist herstellerunabhängig und auf unterschiedlichen Applikationsservern betreibbar. Die Liste des Lobes könnte noch sehr viel länger werden, im Kern aber nicht mehr Aussagen als: Java EE ist ein Standard mit hohem Reifegrad.

Die Gefahr den Standard zu unterschätzen ist trotz des Reifegrades nach wie vor gegeben. Häufig werden Java EE Anwendungen durch ein bestehendes Datenmodell getrieben. Ein korrektes Datenbankmodell und das Nutzen der Möglichkeiten des Datenbankmanagementsystems ist deshalb essentiell für eine Java EE Anwendung, die dem "Lean Ansatz" gerecht wird.

Ein aufgeblähtes Persistenzmodell mit vielen häufig unnötig vorgeladenen Entitäten bringt eine schlechte Performanz hervor. Die Ladestrategie "Lazy Loading" kann die Situation verbessern, bei falscher Verwendung durch höhere Komplexität allerdings zur Fehleranfälligkeit führen. Konform der Separation of Concerns sind Funktionalitäten, die in der Persistenzschicht einer Java EE Anwendung implementiert werden, ebenfalls in Stored Procedures und Datenbanktrigger auslagerbar. Darüber hinaus trägt der Einsatz von Views wesentlich zur Vereinfachung eines komplexen Datenmodells bei und reduziert die Anzahl der im Persistenzkontext der Java EE Anwendung vorgehaltenen Entitäten.

Schematische Darstellung einer View-Struktur

View-Struktur
Die Abbildung des Persistenzmodells im Datenbankmanagementsystems mit einer View-Struktur ist eine Best Practice für bestehende Datenbankstrukturen in Tabellen, die aufgrund ihrer Nutzung durch andere Applikationen nicht veränderlich sind.

Neben den Views ist es ebenso naheliegend Stored Procedures als "Subqueries" in SQL-Abfragen und Triggern zu verwenden. Beide Varianten sind in der Praxis zu finden. Ein Datenbanktrigger entlastet eine Java EE Anwendung. Datenbanktrigger sind vielfältig bei Datenbankoperationen einsetzbar. In der Persistenzschicht einer Java EE Anwendung stehen Interzeptoren für Entity Manager Operationen (@PrePersist, @PostPersist, etc.) zur Verfügung. Diese Interzeptoren sind konform mit den Datenbanktriggern, belasten aber zusätzlich die Laufzeitumgebung des Applikationsservers.

Szenario mit Datenbanktrigger und Stored Procedure

Separation of Concerns mit Trigger und Stored Procedure


Erläuterung des Szenarios:
  1. Speicherung der Bestellung über die Facade auslösen
  2. JPA Provider speichert die Entität der Bestellung in der Tabelle für Bestellungen
  3. Das Datenbankmanagementsystem feuert den Trigger für Neubestellungen
  4. Der Trigger für Neubestellungen führt die Prozedur zur Aktualisierung der Produkttabelle aus
  5. Die Produkttabelle wird aktualisiert
  6. Das Datenbankmanagementsystem feuert den Trigger zur Aktualisierung der Produktmenge
Das Szenario verdeutlicht, wie Funktionalitäten in das Datenbankmanagementsystem verlagert werden, um die Last in der Persistenzschicht einer Java EE Anwendung zu reduzieren. Es ist deshalb empfehlenswert die Planung der Nutzung des Datenbankmanagementsystems in die Konzeption einer Java EE Anwendung mit einzubeziehen. Diese Vorgehensweise wirkt sich später bei hoher Produktionslast positiv auf die Antwortzeiten einer Java EE Anwendung aus.

Sonntag, 19. September 2010

KISS like CDI

Java EE 5 hat die Entwicklung von Webanwendungen mit Java ServerFaces (JSF) standardisiert. Der Web Beans Standard, der durch JBoss Seam positiv beeinflusst wurde, ist in Java EE 5 allerdings noch nicht vollständig umgesetzt. JBoss Seam hat große Popularität im Kreis der Anwendungsentwickler für Websysteme erworben. Wesentlich für den Erfolg von JBoss Seam sind die Integration von JSF und EJB 3, Bijection, deklaratives Statusmanagement, Workspace Management und die Nutzung von Annotationen zur Entwicklung von Webanwendungen. Das Konzept der Konversationen ist in JBoss Seam bereits umgesetzt, sodass die Programmierung von Wizzards stattfinden kann. Bookmarking, verbessertes Session-Handling und nicht zuletzt der Webframework JSF tragen zu dem Erfolg von JBoss Seam bei.

Contexts and Dependency Injection (CDI) ist im JSR 330 mit Relation zum JSR 299 (Web Beans) zusammengefasst und ein Teil des Java EE 6 Standards. CDI verbindet JSF 2 mit der EJB-Schicht. Beide Technologien implementieren "convention over configuration". Durch diesen Grundsatz und neue Navigationsregeln wird die "faces-config.xml" in vielen Fällen überflüssig. Die Implementierung von Webapplikationen ist in Java EE 6 deutlich einfacher als in der Vorgängerversion. JSF 2 beinhaltet Facelets als standardisierte View-Technik. Die Verwaltung der Ressourcen in einer WAR Datei ist auf ein definiertes Ressourceverzeichnis festgelegt. In einer WAR Datei werden im aktuellen Standard EJBs, Managed Beans und XHTML-Seiten untergebracht. Das Verpacken eines WARs und eines EJB-JARs in einem EAR ist deshalb für ein anschliessendes Deployment nicht mehr notwendig.

Java EE 6 Stack mit CDI
Der Blogbeitrag implementiert ein Szenario mit JSF 2, CDI, EJB 3.1 und der JPA. Wie einfach die Programmierung von Konversationen geworden ist und wie wenig Quellcode erstellt wird, zeigt die Implementierung eines Wizzards für eine einfache Teilnehmerregistrierung. Die Teilnehmerregistrierung wird als WAR Datei bereitgestellt und verwendet keine JSF 2 spezifischen Konfigurationsdateien.

Architektur des Wizzards für die Teilnehmerregistrierung

Der Quellcode wird in diesem Beitrag nur auszugsweise vorgestellt, um einen Eindruck über die Leichtigkeit der Implementierung einer Konversation in Form eines Wizzards zu vermitteln. Die Validierungen (JSR 303) sind nur beispielhaft und ohne tieferen fachlichen Sinn in dem Szenario implementiert.

Erste XHTML-Seite des Wizzards

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>Attendant Registration</title>
    </h:head>
    <h:body>
        <h3>Enter Attendant Data (Page 1 of 2)</h3>
        <h:form>
            <h:panelGrid columns="2">
                <h:outputLabel for="firstName" value="First Name"/>
                <h:inputText id="firstName" value="#{attendant.firstName}"/>
                <h:outputLabel for="lastName" value="Last Name"/>
                <h:inputText id="lastName" value="#{attendant.lastName}"/>
                <h:panelGroup/>
                <h:commandButton value="Next" action="#{attendantController.navigateToAddressPage}"/>
            </h:panelGrid>
        </h:form>
    </h:body>
</html>

Zweite XHTML-Seite des Wizzards

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>Attendant Registration</title>
    </h:head>
    <h:body>
        <h3>Enter Attendant Data (Page 2 of 2)</h3>
        <h:form>
            <h:panelGrid columns="2">
                <h:outputLabel for="zipcode" value="Zipcode"/>
                <h:inputText id="zipcode" value="${attendant.zipcode}"/>               
                <h:outputLabel for="city" value="City"/>
                <h:inputText id="city" value="${attendant.city}"/>
                <h:outputLabel for="street" value="Street"/>
                <h:inputText id="street" value="${attendant.street}"/>              
                <h:commandButton value="Previous" action="#{attendantController.navigateToNamePage}"/>
                <h:commandButton value="Next" action="#{attendantController.navigateToConfirmationPage}"/>
            </h:panelGrid>
        </h:form>
    </h:body>
</html>

Controller des Wizzards

package ccd.jee.jsf.attendant.controller;

import java.io.Serializable;
import javax.ejb.EJB;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import ccd.jee.jsf.attendant.model.Attendant;
import ccd.jee.session.bean.attendant.dao.AttendantDAO;

@Named
@RequestScoped
public class AttendantController implements Serializable {

    enum NAVIGATION_RULES {
  
        NAME_PAGE("name-page"),
        ADDRESS_PAGE("address-page"),
        CONFIRMATION_PAGE("confirmation-page");
      
        private final String page;
      
        private NAVIGATION_RULES(String page) {
          
            this.page = page;
        }      
    }
  
    private final NAVIGATION_RULES navRuleName = NAVIGATION_RULES.NAME_PAGE;
    private final NAVIGATION_RULES navRuleAddress = NAVIGATION_RULES.ADDRESS_PAGE;
    private final NAVIGATION_RULES navRuleConfirmation = NAVIGATION_RULES.CONFIRMATION_PAGE;
  
    @EJB
    private AttendantDAO attendantDAO;

    @Inject
    private Conversation conversation;
  
    @Inject
    private Attendant attendant;

    public String attendantConversation() {
      
        startConversation();     
        return navRuleName.page;
    }

    public String navigateToNamePage() {     
   
        return navRuleName.page;
    }

    public String navigateToAddressPage() {      
      
        return navRuleAddress.page;
    }

    public String navigateToConfirmationPage() {

        endConversation();              
        return navRuleConfirmation.page;
    }
  
    private void startConversation() {
  
        conversation.begin();
    }
  
    private void endConversation() {
      
        attendantDAO.store(attendant);
        conversation.end();
    }
}

Managed Bean des Wizzards

package ccd.jee.jsf.attendant.model;

import java.io.Serializable;
import javax.enterprise.context.ConversationScoped;
import javax.inject.Named;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Named
@ConversationScoped
@Entity
public class Attendant implements Serializable {
   
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
   
    @NotNull(message="First name must be typed!")
    @Size(min=2, max=40, message="First name between 2 and 40 characters!")
    private String firstName;
      
    @NotNull(message="Last name must be typed!")
    @Size(min=2, max=40, message="Last name between 2 and 40 characters!")
    private String lastName;
   
    private String zipcode;
   
    @NotNull(message="City must be typed!")
    @Size(min=2, max=40, message="City between 2 and 40 characters!")
    private String city;   
   
    @NotNull(message="Street must be typed!")
    @Size(min=2, max=40, message="Street between 2 and 40 characters!")   
    private String street;
   
    public String getCity() {
        return city;
    }

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

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
     
    public String getZipcode() {
        return zipcode;
    }

    public void setZipcode(String zipcode) {
        this.zipcode = zipcode;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }
   
    public int getId() {
        return id;
    }
}

DAO-Schnittstelle des Wizzards

package ccd.jee.session.bean.attendant.dao;

import javax.ejb.Local;
import ccd.jee.jsf.attendant.model.Attendant;

@Local
public interface AttendantDAO {

     public void store(Attendant attendant);
}

DAO-Implementierung des Wizzards

package ccd.jee.session.bean.attendant.dao;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import ccd.jee.jsf.attendant.model.Attendant;

@Stateless
public class AttendantDAOBean implements AttendantDAO {

    @PersistenceContext
    private EntityManager em;

    @Override
    public void store(Attendant attendant) {
       
        final Attendant newAttendant = assembleAttendant(attendant);       
        storeAttendant(newAttendant);           
    }

    private void storeAttendant(final Attendant newAttendant) {
       
        em.persist(newAttendant);      
    }

    private Attendant assembleAttendant(Attendant attendant) {
       
        final Attendant newAttendant = new Attendant();
        newAttendant.setFirstName(attendant.getFirstName());
        newAttendant.setLastName(attendant.getLastName());
        newAttendant.setZipcode(attendant.getZipcode());
        newAttendant.setStreet(attendant.getStreet());
        newAttendant.setCity(attendant.getCity());
       
        return newAttendant;
    }
}

Interessant bei der Implementierung des Wizzards ist der Controller in dem die Konversation geöffnet und geschlossen wird, sowie das Managed Bean. Das Managed Bean wird per CDI in den Controller injiziert und in den XHTML-Seiten mit den Feldern des Beans verknüpft. Die Felder des Managed Beans werden während der Konversation mit Daten befüllt und am Ende per DAO in die Datenbank geschrieben. Die Annotierung des Managed Beans als Entity stellt kein Problem dar und reduziert den Implementierungsaufwand. CDI wirkt sich deshalb sehr positiv auf das DRY-Prinzip aus, weil keine Redundanzen zu programmieren sind. In Vorgängerversionen des Java Enterprise Standards musste man noch mit Value Objects (bzw. Data Transfer Objects) arbeiten. Zur Assemblierung wurde ein Value Object Assembler eingesetzt, dass Lookup der Beans war aufwendiger als Annotierungen zu verwenden und Konversationen konnten nur sehr viel mühsamer mit dem erweiterten Persistenzkontext programmiert werden.

In der Summe vereinfacht der Java EE 6 Standard die Programmierung von Webapplikationen mit JSF 2, CDI und EJB 3.1 deutlich, sodass man sich bewusst die Frage stellen darf, was in der nächsten Version des Standards noch erreicht werden soll. Der gute Eindruck von Java EE 5 wird durch die Java EE 6 Technologien nochmals verbessert, sodass man dem aktuellen Standard einen hohen Reifegrad zuordnen kann.

CDI is a KISS Framework and amazingly simple!


Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.

Donnerstag, 16. September 2010

Three Steps to Test Driven Enteprise JavaBeans

Enterprise JavaBeans (EJBs) sind Komponenten, die in einem Applikationsserver laufen. EJBs werden deshalb als "managed" bezeichnet. Der Applikationsserver stellt den EJBs Laufzeitdienste für die Transaktionssteuerung, Synchronisation, Pooling und rollenbasierte Sicherheit zur Verfügung.

EJBs sind Geschäftskomponenten, die keine Benutzeroberfläche haben.  EJBs implementieren konkrete Geschäftsfunktionalitäten und sind im Backend über lokale Schnittstellen miteinander vernetzt. EJB 3 vereinfacht mit Hilfe von Dependency Injection (DI) den Zusammenbau des Komponentennetzwerkes. Im Komponentennetzwerk sind die EJBs auf konkrete "Use Cases" eines Geschäftsmodells zugeschnitten. Das Komponentennetzwerk mündet häufig in einer oder mehreren Facaden, welche die Schnittstellen zur Außenwelt repräsentieren. Facaden sind grobkörnig ("Coarse Grained") und mit einer Schnittstelle für den entfernten Zugriff ausgestattet. 

In der Regel wird pro EJB eine Testsuite erstellt. Unit-Tests auszuführen ist kein Problem, weil in der Entwicklungsumgebung der Applikationsserver lokal mitläuft. Der Applikationsserver hält dabei  Verbindungen zu Datenbanken und anderen externen Systemen als Ressourcen vor. Nur in Ausnahmefällen sind Mock-Objekte für externe Schnittstellen zu programmieren, die nicht über den in der Entwicklungsumgebung lokal installierten Applikationsserver zu erreichen sind.

Im optimalen Fall sind alle externen Ressourcen verfügbar und eine lokale bzw. entfernte Datenbankverbindung konfiguriert. Grundsätzlich ist es empfehlenswert, die Unit-Tests gegen die Facade einer Applikation laufen zu lassen. Vorausgesetzt, dass die EJBs, die in der Facade münden, ebenfalls zuvor mit Unit-Tests abgesichert worden sind. Testläufe gegen die Facade sind wesentlich, weil später in Produktion alle Clients über die Facade das Komponentennetzwerk erreichen wollen.

Bei diesen Bedingungen stellt sich die berechtigte Frage, warum EJBs bei der Entwicklung gemockt werden sollen? Im Fall der EJB-Schnittstellen sind Mock-Objekte interessant, weil vorab ohne den Applikationsserver zu berücksichtigen Schnittstellen designed, getestet und abgestimmt werden können. Für die in einem EJB implementierten Algorithmen gilt das gleiche wie, bei herkömmlichen Java Applikationen, sodass Mock-Objekte für die Programmierung konkreter Algorithmen im Inneren eines EJBs einzusetzen sind.

Für die Gegner von Mocks, die aber Unit-Tests mögen (solche Kandidaten gibt es), bietet sich die Methode der "Setter Injection" an. "EJB Setter" in Unit-Tests anzuwenden ist seit Java EE 5 möglich. Man bedenke, EJBs sind ab Version 3 leichtgewichtige Komponenten (POJOs).

Beispiel - Setter Injection

@Stateless
public class AccountBean implements Account {

    private User user;

    @EJB
    public void setUser(User user) {

         this.user = user;
    }
}

Die Methode "setUser(User user)" kann in einem Unit-Tests verwendet werden, um das User-Bean zu initialisieren. Im Applikationsserver hingegen wird das User-Bean automatisch per Dependency Injection vom EJB-Container initialisiert. Setter Injection löst das Problem der Testbarkeit von EJBs, die zum Zeitpunkt des Testlaufes nicht im EJB-Container laufen sollen. Unabhängig von dieser Testform wird strengstens empfohlen die EJBs auch in der "managed" Umgebung des Applikationsservers zu testen.

Zurück zu den Mock-Objekten (Fakes). Der Mock-Framework Mockito eignet sich für die Erzeugung von Fakes. Bei der Entwicklung des Szenarios für den Blogbeitrag wurde Mockito verwendet, um die Schnittstelle für einen Account Service zu implementieren.

Die Implementierung des Account Service beinhaltet drei Schritte
  1. Fake
    Design und Implementierung der Account Service Schnittstelle mit Mockito

  2. Programmierung
    Umsetzen der Account Service Schnittstelle mit EJB 3

  3. Unit-Test
    Test der deployten Account Service Schnittstelle mit einem Unit-Test
  4.  
     Schritt 1 - Fake

    package ccd.jee.session.beans.account;

    import static org.junit.Assert.assertFalse;
    import static org.junit.Assert.assertTrue;
    import static org.mockito.Mockito.mock;
    import static org.mockito.Mockito.when;
    import org.junit.Before;
    import org.junit.Test;
    import ccd.jee.session.beans.account.domain.AccountUser;

    public class AccountServiceDesign {

        private AccountUser user;
        private Account account;
      
        @Before
        public void setUp() {
           
            setUpUser();      
            setUpAccount();
        }
      
        @Test
        public void testCreateUser() {

            final boolean result = account.createUser(user);
            assertTrue(result);
        }
         
        @Test
        public void testLogin() {

            final boolean result = account.login(user);
            assertTrue(result);
        }
      
        @Test
        public void testLoginFailed() {

            final AccountUser loginUser = mock(AccountUser.class);
            when(account.login(loginUser)).thenReturn(false);
            final boolean result = account.login(loginUser);      
            assertFalse(result);
        }
      
        @Test
        public void testDeleteUser() {

            final boolean result = account.deleteUser(user);
            assertTrue(result);
        }

        private void setUpUser() {
           
            user = mock(AccountUser.class);
            when(user.getAlias()).thenReturn("alias");
            when(user.getPassword()).thenReturn("password");
        }

        private void setUpAccount() {
           
            account = mock(Account.class);
            when(account.login(user)).thenReturn(true);
            when(account.createUser(user)).thenReturn(true);
            when(account.deleteUser(user)).thenReturn(true);
        }
    }

    Schritt 2 - Programmierung

    Schnittstelle des Account Service:

    package ccd.jee.session.beans.account;

    import javax.ejb.Remote;
    import ccd.jee.session.beans.account.domain.AccountUser;

    @Remote
    public interface Account {

        public boolean login(AccountUser user);
        public boolean createUser(AccountUser user);
        public boolean deleteUser(AccountUser user);
    }

    Entität eines Benutzers:

    package ccd.jee.session.beans.account.domain;

    import java.io.Serializable;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.NamedQuery;

    @Entity
    @NamedQuery(name = "findUser",
                query="SELECT o FROM AccountUser o WHERE o.alias = :alias AND o.password = :password")
    public class AccountUser implements Serializable {

        @Id
        @GeneratedValue  
        private int id;
      
        private String alias;
        private String password;
      
        public String getAlias() {
      
            return alias;
        }
      
        public String getPassword() {

            return password;
        }
      
        public void setAlias(String alias) {
            this.alias = alias;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public int getId() {
            return id;
        }
    }

    Implementierung des Account Service:

    package ccd.jee.session.beans.account;

    import javax.annotation.PostConstruct;
    import javax.ejb.Stateless;
    import javax.persistence.EntityManager;
    import javax.persistence.NoResultException;
    import javax.persistence.PersistenceContext;
    import javax.persistence.Query;
    import org.apache.log4j.Logger;
    import ccd.jee.session.beans.account.domain.AccountUser;

    @Stateless(mappedName="ejb/AccountBean")
    public class AccountBean implements Account {

        @PersistenceContext
        private EntityManager em;

        private Logger logger;
      
        @PostConstruct
        public void setUp() {
          
            logger = Logger.getRootLogger();
        }
      
        @Override
        public boolean createUser(AccountUser user) {
          
            if(userNotExists(user)) {
              
                em.persist(user);                  
                return true;
            }
                      
            return false;
        }
      
        @Override
        public boolean deleteUser(AccountUser user) {
                      
            if(userExists(user)) {
                  
                em.remove(user);              
                return true;
            }
          
            return false;
        }

        @Override
        public boolean login(AccountUser user) {
                  
            return userExists(user);
        }

        private boolean userExists(AccountUser user) {
          
            return !userNotExists(user);
        }
      
        private boolean userNotExists(AccountUser user) {
          
            try {
              
                findUser(user);          
            }  
            catch(NoResultException ex) {
                  
                logger.debug("User not exists: " + user.getAlias());
                return true;
            }
          
            logger.debug("User exists: " + user.getAlias());
            return false;
        }
      
        private AccountUser findUser(AccountUser user) {
          
            final Query query = em.createNamedQuery("findUser");
            query.setParameter("alias", user.getAlias());
            query.setParameter("password", user.getPassword());
          
            return (AccountUser) query.getSingleResult();
        }
    }

     Schritt 3 - Unit-Test

    package ccd.jee.session.beans.account;

    import static org.junit.Assert.assertFalse;
    import static org.junit.Assert.assertTrue;
    import javax.naming.Context;
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    import org.junit.BeforeClass;
    import org.junit.Test;
    import ccd.jee.session.beans.account.domain.AccountUser;

    public class AcountServiceTest {

        private static final String JNDI_NAME = "ejb/AccountBean";
        private static Context context;
        private static Account account;

        @BeforeClass
        public static void setUp() throws NamingException {
          
            context = new InitialContext();      
            account = (Account) context.lookup(JNDI_NAME);  
        }
      
        @Test
        public void testCreateUser() {
          
            final AccountUser user = getAccountUser();
          
            final boolean result = account.createUser(user);      
            assertTrue(result);
        }

      
        @Test
        public void testLogin() {
              
            final AccountUser loginUser = getAccountUser();
          
            final boolean result = account.login(loginUser);
            assertTrue(result);
        }
      
        @Test
        public void testLoginFailed() {
              
            final AccountUser loginUser = getAccountUser("wrong-alias","wrong-password");
          
            final boolean result = account.login(loginUser);
            assertFalse(result);
        }
      
        @Test
        public void testDeleteUser() {
          
            final AccountUser user = getAccountUser();
          
            final boolean result = account.deleteUser(user);
            assertTrue(result);
        }
      
        private AccountUser getAccountUser(String alias, String password) {
          
            final AccountUser user = new AccountUser();
            user.setAlias(alias);
            user.setPassword(password);
          
            return user;
        }

        private AccountUser getAccountUser() {
          
            return getAccountUser("alias", "password");
        }
    }

    Die Vorgehensweise in drei Schritten hat sich bewährt. Im ersten Schritt konnte mit dem Fake die Schnittstelle des Account Service und die Entität des Benutzers angelegt und mit Mockito getestet werden. Auf Basis der erzeugten Artifakte wurde im zweiten Schritt die EJB-Programmierung vorgenommen. In der Folge ist im dritten Schritt das Deployment auf dem Glassfish v3 für Java EE 6 erfolgt und ein entsprechender Unit-Test gegen die entfernte Schnittstelle des Account Service geschrieben worden.

    TDD konform wurde zunächst die Schnittstelle des Account Service entworfen, abgestimmt und getestet. Im Account Service erkennt man eine Symmetrie zwischen dem Anlegen und dem Löschen eines Benutzers. Beim Löschen  wird geprüft, ob der Benutzer vorhanden ist. Beim Anlegen hingegen, ob noch kein Benutzer mit dem gleichen Aliasnamen und Passwort existiert.

    Das umgesetzte Szenario des Account Service ist sicherlich nicht komplex. Das Prinzip der drei Schritte ist allerdings auch in komplexeren Szenarien anwendbar. Vorteil dabei ist, dass die Schnittstellen von EJBs früh getestet und abgestimmt werden können, ohne die eigentliche Programmierung der EJBs begonnen zu haben.


    Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.

    Mittwoch, 15. September 2010

    Asynchronous Java EE 6

    Die klassische Technik für die Programmierung von asynchronen Java EE Services basiert auf einer Message Oriented Middleware (MOM). Die Komponenten im Umfeld der Middleware setzen sich dabei in der Regel aus einem JMS-Client, einer Queue/Topic und einer Message Driven Bean (MDB) zusammen. Message Driven Beans eignen sich für den hohen Durchsatz von XML-basierten Nachrichten, die im Message Driven Bean verarbeitet und in ein Domainmodell überführt werden. Ein MOM-System erfüllt wesentlich die Anforderungen an Quality of Services (QoS) und garantiert Ausfallsicherheit, verlässliche Nachrichtenübermittlung sowie Fehlertoleranz mit einer "Dead Letter Queue".

    Die Programmierung gegen das MOM-System erfolgt mit der standardisierten JMS-API. Zahlreiche "Best Practices" gruppieren sich im Bereich der nachrichtenorientierten Programmierung. Die Entkopplung von kohäsiven Services und das allgemeine Kommunikationformat XML haben serviceorientierte Architekturen (SOA) geprägt. SOA-Implementierungen werden häufig mit einem Enterprise Service Bus (ESB) abgebildet. Der ESB erlaubt es unterschiedliche Protokolle und XML-Strukturen miteinander zu verknüpfen, Datentransformationen vorzunehmen und lose gekoppelte Services mit hoher Kohäsion zu implementieren. Serviceorientierte Architekturen sind modern und beliebt, weil Services auf der Makroebene "Don't Repeat Yourself" (DRY) vermeiden und "Separation of Concerns" (SoC)  umsetzen. Das XML-Format verspricht Flexibilität durch die vielfältigen Transformations- möglichkeiten und automatisiert die Überprüfbarkeit mit XML-Schemata. Die Verarbeitung von XML-Daten ist mit JAXP und JAXB so einfach wie niemals zuvor, sodass aus XML-Strukturen nahezu beliebige Domainmodelle aufgebaut werden können.

    Trotz guter technische Bedingungen für die Implementierung von Services, ist der Anspruch beim Entwurf und der Programmierung von Services höher als beim monolithischen Architekturansatz. Der Aufwand für den stabilen Entwurf eines Services, sollte deshalb nicht unterschätzt werden. Beim Entwurf sind allgemeine Komponenten strikt von fachlichen Komponenten zu trennen, weil die allgemeinen Komponenten wiederum von anderen Services genutzt werden können. Schnittstellen sind ohne technischen Balast zu entwerfen und in der Folge in versionierter Form zu publizieren. Konform zu "Design by Contract" erfüllen Services einen fest definierten Vertrag, der sich aus Zusicherungen zusammensetzt. Die Programmierung von Services im Kontext eines MOM-Systems ist eine eigenständige Programmiertechnik mit Correlation-IDs und Reply-Adressen, die vielfältig vom Fat Client bis hin zum Web Service genutzt wird.

    Im Java EE 6 Standard besteht neben den Message Driven Beans nun auch die Möglichkeit, die Methoden einer Session Bean mit @Asynchronous zu kennzeichnen. Die Java Concurrency Utilities haben Futures im Gepäck, die für die asynchrone Verarbeitung geeignet sind und im Umfeld der asynchronen Bean-Methoden verwendet werden. Asynchrone Methoden benötigen keine zusätzliche Middlewareschicht. Sie sind vielmehr eine  vereinfachte leichtgewichtige Alternative für die asynchrone Kommunikation im bisher synchronen Universum der Session Beans.

    Wie einfach asynchrone Methoden angesprochen werden und wie gering der Aufwand für die Programmierung ist, zeigt das nachfolgende Szenario. In dem implementierten Szenario schreiben zwei Methoden eine Trace-Ausgabe. Die eine Methode liefert einen Rückgabewert und die andere Methode nicht. Würde man für die Implementierung des Szenarios ein Message Driven Bean anstelle der asynchronen Methoden verwenden, wäre der Aufwand für die Programmierung deutlich höher.

    Schnittstelle des asynchronen Services:

    package ccd.jee.session.beans.asynchronous;

    import java.util.concurrent.Future;
    import javax.ejb.Remote;

    @Remote
    public interface Async {

        public void print(String message);
        public Future<Boolean> print(String ... messages);  
    }

    Implementierung des asynchronen Services:

    package ccd.jee.session.beans.asynchronous;

    import java.util.concurrent.Future;
    import javax.annotation.PostConstruct;
    import javax.ejb.AsyncResult;
    import javax.ejb.Asynchronous;
    import javax.ejb.Stateless;
    import org.apache.log4j.Logger;

    @Stateless(mappedName="ejb/AsyncBean")
    public class AsyncBean implements Async {

        private Logger logger;
      
        @PostConstruct
        void setUp() {
           
            logger = Logger.getRootLogger();
        }
      
        @Asynchronous
        @Override
        public void print(String message) {
           
            trace(message);
        }

        @Asynchronous
        @Override
        public Future<Boolean> print(String ... messages) {

            for(String message : messages) {
               
                trace(message);
            }
           
            return new AsyncResult<Boolean>(true);
        }

        private void trace(String message) {
           
            logger.info(message);
        }
    }

    Unit-Test des asynchronen Services:

    package ccd.jee.session.beans.asynchronous;

    import static org.junit.Assert.*;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    import javax.naming.Context;
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    import org.junit.BeforeClass;
    import org.junit.Test;

    public class AsyncTest {

        private static final String JNDI_NAME = "ejb/AsyncBean";
        private static Context context;
        private static Async asynch;
      
        private static final FutureTask<Boolean> future = new FutureTask<Boolean>(new Callable<Boolean>() {
          
            public Boolean call() {
              
                try {
                  
                    return asynch.print(getMessages()).get();
                }
                catch (InterruptedException ex) {
                  
                    throw new IllegalStateException(ex);              
                }
                catch (ExecutionException ex) {
                  
                    throw new IllegalStateException(ex);
                }
            }

            private String[] getMessages() {
              
                return new String [] {"first-message","second-message"};
            }
        });
      
        @BeforeClass
        public static void setUp() throws NamingException {
          
            context = new InitialContext();      
            asynch = (Async) context.lookup(JNDI_NAME);              
        }
      
        @Test
        public void testPrintMessage() {
                  
            asynch.print("single-message");
        }

        @Test
        public void testPrintMessages() throws NamingException, InterruptedException, ExecutionException {
          
            final Thread thread = new Thread(future);
            thread.start();      
          
            while (!future.isDone()) {
              
                 Thread.sleep(100);
            }
          
            assertTrue(future.get());
        }
    }


    Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.