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.