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.