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.

    Dienstag, 14. September 2010

    Is F.I.R.S.T Spooky?

    Der Test F.I.R.S.T Ansatz polarisiert. Die Anhänger von Test F.I.R.S.T sprechen von der Verwendersicht, Fakes, Test Fixtures, Isolation, loser Kopplung und Wiederholbarkeit. Tatsächlich sind das alles Eigenschaften von Unit-Tests, dem Treiber für Test F.I.R.S.T. Die Gegner dieses Ansatzes sprechen von nicht automatisierbaren GUI-Tests, externen Systemen, die zu berücksichtigen sind, der Komplexität der zu implementierenden Funktionalität, Seiteneffekten und der braunen Masse des Quellcodes.

    Bei Greenfield-Projekten ist die Polarisierung des Ansatzes weniger stark als bei Brownfield-Projekten. Das Problem bei Brownfield-Projekten ist häufig, dass der Quellcode nicht testgetrieben entwickelt wurde und deshalb keine isolierten Schnittstellen für die Programmierung von Unit-Tests vorliegen. In diesem Fall ist die Testbarkeit herzustellen, eine mehr oder weniger große Hürde für den Entwickler. Grundsätzlich spielt der Zeitrahmen des Projektes  eine Rolle. Der Eindruck entsteht, dass die testgetriebene Weiterentwicklung eines Brownfield-Projektes durch das vorzunehmende Refactoring zu aufwendig sei. Der Zeitrahmen könnte wackeln. Sicherlich ist in Brownfield-Projekten oftmals der Fall, dass die Abhängigkeiten im Quellcode groß sind, sodass aufgrund des Aufwandes kein umfangreiches Refactoring zur Herstellung der Testbarkeit  geplant ist. Stellt man die Testbarkeit allerdings nicht her, begibt man sich in die Gefahr, sich im Netz der Abhängigkeiten zu verfangen und durch Seiteneffekte bei der Weiterentwicklung stark behindert zu werden.

    Die Bedenken gegen den Einsatz von Unit-Tests zur Neu- und Weiterentwicklung einer Software sind  unterschiedlich stark ausgeprägt, sodass der Ansatz manchmal schon früh im Keim erstickt wird. Sicherlich sind GUIs nur schwierig mit diesem Ansatz zu implementieren. Doch wurde die GUI nach dem Model View Controller (MVC) Pattern programmiert, lässt sich das Modell testgetrieben implementieren. Unit-Tests erfordern, dass kleine isolierte testbare Einheiten, die entkoppelt voneinander sind, entstehen. Wie der Verwender diese Einheiten sieht, ist wesentlich für die Strukturierung der Tests. Die Verwendersicht fördert die Benutzerfreundlichkeit der gerade entstehenden Anwendung und wirkt sich positiv auf die Lesbarkeit des Quellcodes aus.

    Bei der Diskussion des Test F.I.R.S.T Ansatzes erkennt man Relationen zu Design by Contract. Bei beiden Ansätzen denkt man über die Verwender und einen zu erfüllenden Vertrag nach. Der Schluß liegt deshalb nahe, dass alleine das Nachdenken über den Verwender des Quellcodes die Qualität deutlich verbessert. Ein weiteres Qualitätsmerkmal ist die Isolation, die eine hohe Flexibilität in der Weiterentwicklung mit sich bringt ohne weitläufige Seiteneffekte hervorzurufen. Die lose Kopplung fördert die Austauschbarkeit von Komponenten und die Wiederverwendbarkeit. In der Summe eine angenehme Ausgangsposition für die Wartung mit einhergehenden Refactoringmaßnahmen.

    Auffällig ist, dass die testgetriebene Entwicklung offensichtlich Zufriedenheit bei Entwicklern hervorruft. Der Grund dafür könnte die Gewissheit sein, ein Stück fehlerfreie Software programmiert zu haben und dies jederzeit mit einem Testlauf beweisen zu können. Man sagt, dass Entwickler die testgetrieben arbeiten früher Feierabend haben, Tests zum Lernen nutzen, bei einer Quellcodebasis zuerst die Tests und dann die API-Dokumentation studieren, den Quellcode per Shortcut von der Entwicklungsumgebung generieren lassen und sich über einen roten Balken genauso wie über einen grünen Balken freuen. Vielleicht sind es auch nur die vielen kleinen Erfolgserlebnisse bei der Programmierung, das Wechselspiel von rot zu grün, die kleinen Schritte, die es nicht erlauben sich zu verlaufen und die Frustration stattfinden zu lassen.

    Spooky

    Theorie zu F.I.R.S.T

    F.I.R.S.T bezeichnet die fünf Eigenschaften von sauberen Unit-Tests, die zu stabilen und qualitativ hochwertigen testgetriebenen Komponenten führen. Bedenke! Unit-Tests sind mit der gleichen Qualität wie der eigentliche Quellcode zu erstellen.
    1. Fast - Tests müssen schnell sein, viele hundert in Sekunden.

    2. Isolated - Tests isolieren Fehler und testen gezielt nur eine Funktionalität. Falls Fehler durch einen Test nicht isoliert werden können, sind kleinere Testeinheiten zu schreiben. Tests hängen nicht voneinander ab.

    3. Repeatable - Tests müssen zu jeder Zeit wiederholbar sein. Wiederholbare Tests sind automatisch ausführbar und dürfen deshalb keine Abhängigkeiten zu externen Services und/oder Ressourcen haben.

    4. Self validating - Testergebnisse haben einen boolschen Wert. Entweder ein Test war erfolgreich oder der Test ist fehlgeschlagen.

    5. Timely - Tests werden zum richtigen Zeitpunkt geschrieben. Bei Test F.I.R.S.T noch bevor der eigentliche Quellcode erstellt wurde.

    Montag, 13. September 2010

    Generic Database Service for Java EE 5

    Java EE 5 unterscheidet sich deutlich von seiner Vorgängerversion. Java EE 5 ist leichtgewichtig und nutzt Annotations und "Configuration by Exception" anstelle von Deployment Deskriptoren. Die Implementierung von Home- und speziellen Komponentenschnittstellen ist nun obsolet, sodass eine einfache Java Schnittstelle ausreichend ist. Java EE 6 geht sogar soweit, dass sofern EJBs nur lokal verwendet werden, gar keine Schnittstelle mehr zu implementieren ist.

    Die Deklaration einer Java EE 5 Entität erfolgt über Annotationen. Die Annotation @Entity ist eine Marker-Annotation, die dem Persistentprovider anzeigt, dass es sich um ein Objekt handelt, das in einer Datenbank gespeichert werden soll. Zusätzlich zu der Marker-Annotation ist noch das Primärschlüsselfeld festzulegen. Das Primärschlüsselfeld wird mit @Id und @GeneratedValue (optional mit entsprechender Generierungsstrategie) gekennzeichnet. An der Entität können global zugreifbar mehrere "Named Queries" deklariert werden. "Named Queries" bieten sich im JPA-Umfeld aufgrund der Annotierung an der Entität an.

    Komplexere Datenbankstrukturen sind mit der JPA umsetzbar. Die Standardrelationen einer Datenbank bis hin zu Vererbungsstrategien sind programmierbar. JPA unterstützt neben einfachen Datenbankabfragen mit der JPQL auch komplexere Joins und native SQL Queries. Typsichere und objektorientierte Abfragen mit der Criteria API werden allerdings erst in JPA 2 mit dem Java EE 6 Standard unterstützt.

    Ein rudimentärer Datenbankservice  ist relativ einfach mit Java Generics umsetzbar. Der Service bietet dabei allgemeine Dienste zum Erzeugen, Löschen, Aktualisieren und Lesen von Entitäten an. Der in diesem Blogbeitrag implementierte Datenbankservice nutzt den EntityManager und die JPA Query Schnittstelle von Java EE 5.

    Szenario: Datenbank Service

    Schnittstelle des Datenbankservices:

    package ccd.jee.services.db;

    import java.util.List;

    public interface DBService<T> {

        public void store(T t);
        public void remove(T t);
        public T update(T t);
        public T find(Class<T> t, Object id);
        public List<T> findByNamedQuery(String queryName);  
    }

    Implementierung des Datenbankservices (noch nicht vollständig ausprogrammiert!):

    package ccd.jee.services.db;

    import java.util.List;
    import javax.ejb.Local;
    import javax.ejb.Stateless;
    import javax.ejb.TransactionAttribute;
    import javax.ejb.TransactionAttributeType;
    import javax.persistence.EntityManager;
    import javax.persistence.PersistenceContext;
    import javax.persistence.Query;

    @Stateless
    @Local(DBService.class)
    public class DBServiceBean<T> implements DBService<T> {

        @PersistenceContext
        private EntityManager em;
         
        @Override
        public void store(T t) {

            em.persist(t);            
        }

        @Override
        public void remove(T t) {

            final T mt = em.merge(t);   
            em.remove(mt);      
        }

        @Override
        public T find(Class<T> t, Object id) {

            return em.find(t, id);      
        }

        @Override
        public T update(T t) {

            final T mt = em.merge(t);
            return mt;      
        }

        @Override
        @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
        public List<T> findByNamedQuery(String queryName) {

            final Query query = em.createNamedQuery(queryName);
            return query.getResultList();
        }
    }

    Entität eines Blogeintrages (ohne den equals und hashCode Vertrag zu erfüllen!):

    package ccd.jee.domain.blog;

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

    @Entity
    @NamedQueries({
          
            @NamedQuery(name = "queryAll", query= "SELECT o FROM BlogEntry o")
    })
    public class BlogEntry implements Serializable {
      
        @Id
        @GeneratedValue
        private int id;
      
        private String title;
        private String entry;
      
        public int getId() {
            return id;
        }
      
        public String getTitle() {
            return title;
        }
      
        public String getEntry() {
            return entry;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public void setEntry(String entry) {
            this.entry = entry;
        }

        @Override
        public String toString() {
            return "BlogEntry [entry=" + entry + ", title=" + title + "]";
        }
    }

    Schnittstelle des Blog Services:

    package ccd.jee.session.beans.blog;

    import java.util.List;
    import ccd.jee.domain.blog.BlogEntry;

    public interface Blog {

        public void storeBlogEntry(BlogEntry blogEntry);
        public void removeBlogEntry(BlogEntry blogEntry);
        public BlogEntry updateBlogEntry(BlogEntry blogEntry);
        public BlogEntry findBlogEntry(Class<BlogEntry> blogEntryClass, Integer id);   
        public List<BlogEntry> getAllBlogEntries();
    }

    Implementierung des Blog Services:

    package ccd.jee.session.beans.blog;

    import javax.ejb.EJB;
    import javax.ejb.Remote;
    import javax.ejb.Stateless;
    import javax.naming.NamingException;
    import ccd.jee.domain.blog.BlogEntry;
    import ccd.jee.services.db.DBService;

    @Stateless(mappedName="ejb/BlogService")
    @Remote(Blog.class)
    public class BlogBean implements Blog {

        @EJB
        private DBService<BlogEntry> dbService;
      
        @Override
        public void storeBlogEntry(BlogEntry blogEntry) {
          
            dbService.store(blogEntry);
        }
      
        @Override
        public void removeBlogEntry(BlogEntry blogEntry) {
          
            dbService.remove(blogEntry);
        }

        @Override
        public BlogEntry updateBlogEntry(BlogEntry blogEntry) {
                  
            return dbService.update(blogEntry);
        }
      
        @Override
        public BlogEntry findBlogEntry(Class<BlogEntry> blogEntryClass, Integer id) {
          
            return dbService.find(BlogEntry.class, id);
        }

        @Override
        public List<BlogEntry> getAllBlogEntries() {
          
            return dbService.findByNamedQuery("queryAll");
        }
    }

    Unit-Test des Blog Services:

    package ccd.jee.session.beans.blog;

    import static org.junit.Assert.assertEquals;
    import java.util.List;
    import javax.naming.Context;
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    import org.junit.BeforeClass;
    import org.junit.Test;
    import ccd.jee.domain.blog.BlogEntry;
    import ccd.jee.session.beans.blog.Blog;

    public class BlogTest {

        private static final String BLOG_ENTRY_DATA = "Blogeintrag";
        private static final String BLOG_ENTRY_TITEL = "Titel";  
        private static final int FIND_ID = 1;
        private static final String JNDI_NAME = "ejb/BlogService";
        private static Context context;
        private static Blog blog;
      
        @BeforeClass
        public static void setUp() throws NamingException {
          
            context = new InitialContext();      
            blog = (Blog) context.lookup(JNDI_NAME);              
        }
      
        @Test
        public void testStore() {
                  
            blog.storeBlogEntry(getBlogEntry());
        }

        @Test
        public void testDelete() {
      
            final BlogEntry entry = getBlogEntry();
            blog.storeBlogEntry(entry);
            blog.removeBlogEntry(entry);
        }
      
        @Test
        public void testUpdate() {
      
            final BlogEntry entry = getBlogEntry();
            blog.storeBlogEntry(entry);
          
            entry.setTitle(BLOG_ENTRY_TITEL);
            blog.updateBlogEntry(entry);
        }
      
        @Test
        public void testFind() {
      
            final BlogEntry firstEntry = getBlogEntry();
            blog.storeBlogEntry(firstEntry);
          
            final BlogEntry secondEntry = blog.findBlogEntry(BlogEntry.class, new Integer(FIND_ID));
          
            assertEquals(BLOG_ENTRY_TITEL, secondEntry.getTitle());
            assertEquals(BLOG_ENTRY_DATA, secondEntry.getEntry());
        }
      
        @Test
        public void testFindAll() {
          
            final BlogEntry entry = getBlogEntry();
            blog.storeBlogEntry(entry);
              
            final List<BlogEntry> list = blog.getAllBlogEntries();      
            for(BlogEntry blogEntry : list) {
              
                assertEquals(BLOG_ENTRY_TITEL, blogEntry.getTitle());
                assertEquals(BLOG_ENTRY_DATA, blogEntry.getEntry());
            }
        }
      
        private BlogEntry getBlogEntry() {
                  
            return getBlogEntry(BLOG_ENTRY_TITEL, BLOG_ENTRY_DATA);
        }
      
        private BlogEntry getBlogEntry(String title, String entry) {
          
            final BlogEntry blogEntry = new BlogEntry();
            blogEntry.setTitle(title);
            blogEntry.setEntry(entry);
          
            return blogEntry;
        }
    }

    Das Szenario veranschaulicht, wie einfach die Programmierung eines allgemeinen Datenbankservices mit den Boardmitteln von Java EE 5 ist. Der Datenbankservice ist noch nicht vollständig ausprogrammiert, sodass beispielsweise keine Abfragen mit Parametern und Löschoperationen per eindeutigem Identifier (UID) unterstützt werden. Die Erweiterung um diese Funktionalitäten ist allerdings unproblematisch. Mit dem Datenbankservice sind komplexere Entitäten und deren Relationen speicherbar. Die Relationen zwischen den Entitäten basieren auf dem Domainmodell und sind transparent  für den Datenbankservice.

    Reflektiert man, wie aufwendig die Programmierung von Entity Beans im EJB 2 Standard ist und wieviel mehr Quellcode (Boilerplate Code) sowie Deployment Deskriptoren erstellt werden müssen, ist die JPA eine deutliche Erleichterung bei der Programmierung von Datenbankanbindungen.

    Der Datenbankservice ist ein gutes Beispiel für die Anwendung des DRY-Prinzips. Würde man in jedem EJB den EntityManager zum Speichern von Daten anwenden, hätte man sehr viel mehr redundanten datenbankspezifischen Quellcode zu erstellen. Generische Programmierung ist deshalb geeignet, um das DRY-Prinzip einzuhalten. Abzuraten ist allerdings von einer generischen Programmierung mit der Basisklasse "Object", weil dabei die Typsicherheit verloren geht. Die Basisklasse "Object" sollte deshalb nur gezielt in geringer Dosis ihre Anwendung finden.

    stay tuned


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


    Sonntag, 12. September 2010

    Java EE 6 introduces the Enterprise Singleton

    Java EE 6 hat einen neuen Session Bean Typ das Singleton im Portfolio. Für ein Singelton typisch, läuft im Applikationsserver nur eine Instanz des EJBs. Das Singleton eignet sich als Cache für andere EJBs, weil sein Status über Methodenaufrufe hinweg erhalten bleibt. Das Singleton ist ein Hybrid aus einer Stateless und einer Stateful Session Bean. Stateless Session Beans sind einem Client nicht fest zugeordnet und  verwalten deswegen auch keinen clientspezifischen Zustand. Ein Stateful Session Bean hingegen, ist einem Client fest zugeordnet und hält statusspezifische Daten für einen Client vor.

    Das Singleton ist als gewöhnliches Session Bean implementierbar, sodass Entitäten ansprechbar sind. Eine Cache-Implementierung könnte beispielsweise eine JPA-Abfrage nutzen, um den Cache des Singeltons in einer Lifecycle-Methode zu initialisieren. Vorsicht ist dennoch geboten. Das Singleton ist kein vollwertiger transaktionaler Cache für den Einsatz in einem Cluster. Ein im Cluster einsetzbarer transaktionaler Cache, der seine Daten im Cluster repliziert, ist der JBoss Cache. Der JBoss Cache ist für komplexe, transaktionale und hochverfügbare Cache-Szenarien geeigneter als eine Eigenentwicklung mit einem Singleton.

    Singletons berücksichtigen die Nebenläufigkeit. Der Zugriff auf statusspezifische Datenstrukturen des Singletons findet in der Standardkonfiguration (Container-Managed Concurrency) synchronisiert statt. Die Ausführung der Methoden des Singletons wird dabei serialisiert, sodass zu einem gegebenen Zeitpunkt nur ein Client auf das Singleton zugreifen kann. Man bedenke, ein Singleton wird häufig parallel von mehreren Clients verwendet. Ein Client ist im Kontext des Singletons ein anderes EJB.

    In dem folgenden Szenario wird veranschaulicht, wie ein Singleton parallel von zwei EJBs und mehreren Clients angesprochen wird.
    Szenario: Enterprise Singleton

    Das SingletonBean verwaltet eine Map. Die Map fungiert quasi als Cache. Das ManagementBean und das ConfigurationBean fügen Einträge in den Cache ein bzw. lesen Einträge aus dem Cache. Mehrere Clients nutzen das ManagementBean und das ConfigurationBean in nebenläufiger Umgebung.

    Implementierung des Singleton Beans:

    import java.util.HashMap;
    import java.util.Map;
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    import javax.ejb.Singleton;

    @Singleton
    public class SingletonBean<T> {
     
        private Map<T, T> cache;

        @PostConstruct
        void createCache() {
        
            cache = new HashMap<T, T>();
        }

        @PreDestroy
        void destroyCache() {
        
            cache.clear();
            cache = null;
        }

        public T get(T key) {
        
            return cache.get(key);
        }

        public void add(T key, T value) {
        
            cache.put(key, value);
        }
    }

    Schnittstelle des Management Beans:

    public interface Management<T> {

        public void addCacheEntry(T key, T value);
        public T getCacheEntry(T key);
    }

    Schnittstelle des Configuration Beans:

    public interface Configuration<T> {

        public void addCacheEntry(T key, T value);
        public T getCacheEntry(T key);
    }

    Implementierung des Management Beans:

    import javax.ejb.EJB;
    import javax.ejb.Remote;
    import javax.ejb.Stateless;

    @Stateless
    @Remote(Management.class)
    public class ManagementBean<T> implements Management<T> {

        @EJB
        private SingletonBean<T> singleton;
      
        public T getCacheEntry(T key) {
          
            return singleton.get(key);
        }

        public void addCacheEntry(T key, T value) {
          
            singleton.add(key, value);
        }
    }

    Implementierung des Configuration Beans:

    import javax.ejb.EJB;
    import javax.ejb.Remote;
    import javax.ejb.Stateless;

    @Stateless
    @Remote(Configuration.class)
    public class ConfigurationBean<T> implements Configuration<T> {

        @EJB
        private SingletonBean<T> singleton;

        public T getCacheEntry(T key) {
       
            return singleton.get(key);
        }

        public void addCacheEntry(T key, T value) {
           
            singleton.add(key, value);
        }
    }

    Client-Konsolenprogramm:

    import javax.naming.Context;
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    import org.apache.log4j.Logger;

    public class TestSingleton {

        private static Logger logger = Logger.getRootLogger();

        private static final int MAX_LOOP_CYCLES = 100;
        private static final int MAX_THREADS = 50;
        private static final String KEY = "key-";
        private static final String VALUE = "value-";

        private static final String MANAGEMENT_JNDI_NAME = "SingletonEAR/ManagementBean/remote";
        private static final String CONFIGURATION_JNDI_NAME = "SingletonEAR/ConfigurationBean/remote";

        private static Context context;
        private static Configuration<String> configurationBean;
        private static Management<String> managementBean;

        public static void main(String[] args) throws NamingException {

            lookupAndInitialize();

            final Thread configurationAddThread[] = getThreadPool(new Runnable() {

                @Override
                public void run() {

                    for (int i = 0; i < MAX_LOOP_CYCLES; i++) {

                        configurationBean.addCacheEntry(KEY + i, VALUE + i);
                    }
                }
            });

            final Thread configurationGetThread[] = getThreadPool(new Runnable() {

                @Override
                public void run() {

                    for (int i = 0; i < MAX_LOOP_CYCLES; i++) {

                        TestSingleton.sleep();
                        logger.info(configurationBean.getCacheEntry(KEY + i));
                    }
                }
            });

            final Thread managementAddThread[] = getThreadPool(new Runnable() {

                @Override
                public void run() {

                    for (int i = 0; i < MAX_LOOP_CYCLES; i++) {

                        managementBean.addCacheEntry(KEY + i, VALUE + i);
                    }
                }
            });

            final Thread[] managementGetThread = getThreadPool(new Runnable() {

                @Override
                public void run() {

                    for (int i = 0; i < MAX_LOOP_CYCLES; i++) {

                        TestSingleton.sleep();
                        logger.info(managementBean.getCacheEntry(KEY + i));
                    }
                }
            });

            start(configurationAddThread, configurationGetThread,
                    managementGetThread, managementAddThread);
        }

        private static void lookupAndInitialize() throws NamingException {

            context = new InitialContext();
            configurationBean = (Configuration<String>) context
                    .lookup(CONFIGURATION_JNDI_NAME);
            managementBean = (Management<String>) context
                    .lookup(MANAGEMENT_JNDI_NAME);
        }

        private static Thread[] getThreadPool(final Runnable runnable) {

            final Thread threadPool[] = new Thread[MAX_THREADS];
            for (int i = 0; i < MAX_THREADS; i++) {

                threadPool[i] = new Thread(runnable);
            }

            return threadPool;
        }

        private static void start(final Thread[] configurationAddThread,
                                         final Thread[] configurationGetThread,
                                         final Thread[] managementGetThread,
                                         final Thread[] managementAddThread) {

            for (int i = 0; i < MAX_THREADS; i++) {

                configurationAddThread[i].start();
                configurationGetThread[i].start();
                managementGetThread[i].start();
                managementAddThread[i].start();
            }
        }

        private static void sleep() {

            try {

                Thread.sleep(5);

            } catch (InterruptedException ex) {

                ex.printStackTrace();
            }
        }
    }

    Das Client-Programm ist kein klassischer Unit-Test, weil über einen Thread-Pool mehrere Threads gestartet werden, die das ManagementBean und das ConfigurationBean ansprechen. Die Anwendung eines Unit-Tests ist in Bezug auf die Nebenläufigkeit im vorliegenden Szenario problematisch.

    Das ManagementBean und das ConfigurationBean fügen Cache-Einträge hinzu, um die Einträge anschliessend wieder zu lesen. Beide Beans beinhalten die gleiche Funktionalität. In einem reellen Szenario würden weitere unterschiedliche Funktionalitäten in den EJBs implementiert sein. Aufgrund des stark vereinfachten Szenarios ist in den Beans keine Rücksicht auf das DRY-Prinzip genommen worden.

    Der parallele Zugriff auf das SingletonBean über zwei EJBs und mehrere Clients erweist sich in den Tests als stabil. Es konnte in den Testläufen kein Problem entdeckt werden, das aufgrund der Nebenläufigkeit entstanden ist.

    Das Szenario zeigt darüber hinaus, dass EJBs leichtgewichtige Plain Old Java Objects (POJOs) sind, die generisch programmiert werden können. Durch generische Programmierung sind allgemeine typsichere EJBs erstellbar. Ein weiterer Vorteil ist, dass EJBs in Unit-Tests mit Mock-Objekten (Fake it till you make it) gestestet werden können, ohne dabei im Applikationsserver als "managed" EJBs zu laufen.

    Java EE 5 und nachfolgende Versionen sind eine gute Basis für die Programmierung von Enterprise Anwendungen. Der Java EE Standard ist gereift und erleichtert deutlich die Programmierung von serverseitigen Java  Anwendungen.

    Once again, stay tuned


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

    Freitag, 10. September 2010

    Favor Composition over Inheritance thoughts

    Vererbung ist ein mächtiges Mittel um wiederverwendbare Software zu schreiben. Objektorientierte Programmiersprachen nutzen Vererbung, Polymorphie, Überschreiben und Überladen, um Quellcoderedundanzen (DRY) zu minimieren und das Open Closed Principle (OCP) einzuhalten.

    Nachteilig bei der Vererbung ist die enge Kopplung in Klassenhierarchien und die einzuhaltenden Regeln, die das liskovische Substitutionsprinzip vorgibt. Vererbungshierarchien sind bei fehlerhaften Designentscheidungen und nicht bedachten Randbedingungen häufig fragil. Die Fragilität äußert sich durch die nicht Ersetzbarkeit von Klassen einer konkreten Klassenhierarchie und unvorhersehbaren Verhaltensweisen bei der Verwendung einer Klassenbibliothek. Erfahrene Entwickler schlagen deshalb vor, anstelle von abstrakten Basisklassen vorzugsweise Schnittstellen zu verwenden. Diese Vorgehensweise ist allerdings nicht in allen Fällen geeignet, weil abstrakte Klassen auch Implementierungen beinhalten können. Der Mix aus konkreten Methodenimplementierungen und abstrakten Methoden gleicht den Nachteil durch Vererbung Fragilität zu erzeugen wieder aus.

    Die Kapselung als wesentlicher Vorteil moderner Programmiersprachen kann bei fehlerhaftem Design in einer Klassenhierachie durch Vererbung gebrochen werden. Beim Klassendesign sollte deshalb genau überlegt werden, ob eine Klasse mit final als Endpunkt einer Vererbungshierarchie deklariert wird oder ob weitere Ableitungen der Klasse geduldet werden. Der Zugriffsmodifizierer "protected" ist auf Feld- und Methodenebene dediziert zur Regelung der Sichtbarkeit nutzbar. Methoden, die mit final deklariert wurden können nicht mehr überschrieben werden und markieren somit auf der Methodenebene den Endpunkt der Vererbungshierarchie. Mittel zur Kontrolle der Vererbung von Feldern und Methoden sind in der Programmiersprache Java vorhanden und fallweise zu nutzen.

    Zur Auflockerung etwas Theorie:

    In der klassischen objektorientierten Lehre werden Klassenentwürfe und die einhergehenden Ableitungen als IS-A Beziehungen bezeichnet. An dieser Stelle wird die Relation zur Typbildung deutlich. Die Komposition hingegen nutzt die Delegierung und die HAS-A Beziehung als Verweis zu einem anderen Objekt. IS-A und HAS-A Beziehungen sind fest verankerte Konzepte objektorientierter Programmiersprachen.

    Java EE Patterns (Komposition und Delegierung):

    Im Java EE Umfeld wird clientseitig häufig das Business Delegate eingesetzt. Das Business Delegate kümmert sich um das Lookup von Enterprise JavaBeans (EJBs) und bietet  darüber hinaus weitere clientspezifische Services zur Fehlerbehandlung und Transaktionssteuerung an. Der Client in einer Java EE Anwendung nutzt das Business Delegate wie eine lokale Komponente, ohne sich mit den entfernten Methodenaufrufen beschäftigen zu müssen. 

    Das serverseitige Gegenstück zum Business Delegate ist die Facade. Die Facade ist eine vereinfachte Schnittstelle zu einem komplexen Subsystem. Facaden bieten Dienste für Clients an und sind Coarse Grained designed. Transaktionen können beim Client oder an der Grenze einer Facade beginnen. Die Transaktionssteuerung ist applikationsabhängig. Wesentlich für die Facade ist, dass durch die Bündelung von Funktionalitäten weniger Netzwerkverkehr erzeugt wird. In der Praxis können Monster Facaden entstehen, die unterschiedliche fachliche Funktionalitäten bündeln. Ratsam ist in solchen Fällen nach dem Prinzip Separation of Concerns vorzugehen und je nach fachlicher Funktionalität eine zugeordnete Facaden zu erstellen. Coarse Grained bezieht sich definitiv nicht auf die Masse der Methoden in einer Facade, sondern auf die Bündelung von Methodenaufrufen in den Methoden der Facade.

    Implementierungsbeispiel:

    Im Blogbeitrag soll anhand einer einfachen Counter Implementierung die Fragilität  der Vererbung gezeigt werden. Zusätzlich zur Lösung durch Vererbung wird eine Komposition veranschaulicht, die im konkreten Fall der bessere Implementierungsansatz ist. Die Counter Implementierung beinhaltet ein CounterBean mit zwei nicht synchronisierten Methoden, die für die Verwendung in nebenläufigen Umgebungen erweitert werden sollen.

    Schnittstelle des Counters:

    public interface Counter {

        public long getCounter();
        public long increment();
    }

    Implementierung des Counters:

    public class CounterBean implements Counter {
       
        private long counter = 0;
       
        @Override
        public long getCounter() {
           
            return counter;
        }

        @Override
        public long increment() {

            if(Long.MAX_VALUE == counter) {
                throw new IllegalStateException("counter overflow");
            }
           
            return(++counter);
        }
    }

    Ableitung des Counters:

    public class CounterSubBean extends CounterBean {

        @Override
        public synchronized long getCounter() {
          
            return super.getCounter();
        }
      
        @Override
        public synchronized long increment() {
          
            return super.increment();
        }
    }

    Die Anforderung, dass der Counter in nebenläufigen Umgebungen einsetzbar ist, wird durch synchronisierte Methoden in der abgeleiteten Klasse erfüllt. Die Implementierung ist dennoch nicht stabil, weil das Gesetz der Ersetzbarkeit gebrochen wurde. Verwendet ein Programmierer die Basisklasse anstelle der Ableitung, würde er mit Recht erwarten, dass die Methoden der Basisklasse ebenfalls synchronisiert sind. Diese Annahme kann bei unzweckmäßigem Einsatz der Basisklasse in nebenläufigen Umgebungen zu schwierig findbaren Laufzeitfehlern führen.

    Die im vorliegenden Fall geeignete Implementierung ist der Decorator. Der Decorator zeigt sofort an, dass die Funktionalität der Basisklasse nicht durch Vererbung erweitert wird. Der Programmierer weiß in diesem Kontext, dass sich die Basisklasse anders als die Decorator Implementierung verhält. Zweck des Decorators ist es nämlich, eine Klasse zu erweitern, ohne den Vertrag den die Basisklasse erfüllt zu verletzen.

    Decorator Implementierung des Counters:

    public class CounterBeanDecorator implements Counter {

        private final Counter counter;
               
        private CounterBeanDecorator(Counter counter) {
           
            this.counter = counter;
        }

        @Override
        public synchronized long getCounter() {
           
            return (counter.getCounter());
        }

        @Override
        public synchronized long increment() {
           
            return(counter.increment());
        }
    }


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