Sustainable Service Design – Teil 2

Implementierung nachhaltiger Services mit Java EE

Wolfgang Pleus

© Shutterstock/Sergey Nivens

Nachhaltigkeit ist ein wichtiges Thema in vielen Bereichen unseres täglichen Lebens. In der durch ständige Innovationen getriebenen Softwarebranche wird ihr jedoch wenig Beachtung geschenkt. Diese Artikelreihe beschreibt einen Ansatz für das Design und die Implementierung nachhaltiger Software Services. Der erste Teil [1] beschäftigte sich mit den Grundlagen. Der vorliegende zweite Teil zeigt eine konkrete Implementierung nachhaltiger Services mit Java EE.

Der erste Teil dieser Artikelreihe beschäftigte sich mit den Grundlagen und Prinzipien des Sustainable Service Designs (SSD). Dieser Teil wendet sich nun einer konkreten Implementierung zu. Das Beispiel verwendet Java EE 7 und JBoss WildFly 8 für die Umsetzung. Das vollständige Beispiel steht zum Download zur Verfügung.

Gezeigt wird das einfache Beispiel eines Rechners (Calculator), der die vier Grundrechenarten Addition, Subtraktion, Multiplikation und Division unterstützt. Obwohl natürlich trivial, handelt es sich um ein Konzept mit hohem Wiederverwendungspotenzial und folgt somit den ersten beiden Regeln (Konzept und Wiederverwendbarkeit) eines nachhaltigen Servicekontrakts [1].

Der Kontrakt

Der Servicekontrakt beschreibt die Aufrufschnittstelle des Service in Form eines Java-Interface. Listing 1 zeigt das API des Calculator-Service.

public interface Calculator{
  public PerformCalculationResponse 
    performCalculation(PerformCalculationRequest request)
    throws CalculatorException;
}

Um ein einheitliches Aufrufmodell über alle Bindungen zu erreichen, werden die eingehenden Parameter in einem Request-Objekt und die ausgehenden Parameter in einem Response-Objekt gekapselt. Diese Objekte werden in allen Bindungen konsequent wiederverwendet. Eine maximale technische Wiederverwendbarkeit wird erreicht, indem die Objekte mit XML Schema (XSD) definiert werden. Über das Maven-Plug-in org.jvnet.jaxb2.maven2:maven.jaxb2-plugin werden daraus Java-Klassen generiert. Das Projekt JAXB2-Commons stellt zahlreiche Plug-ins zur Verfügung, um die Generierung zu beeinflussen. So können automatisch Methoden wie toString, equals, hashCode oder auch Wertkonstruktoren oder Fluent-APIs generiert werden. Der Implementierungsaufwand wird so reduziert und die Einheitlichkeit erhöht. Listing 2 zeigt die Definition der Nachrichten PerformCalculationRequest und PerformCalculationResponse mit XML Schema.

<!--Request inklusive Dokumentatiom -->
<xs:element name="PerformCalculationRequest">
  <xs:annotation><xs:documentation>
  This request performs a calculation
  </xs:documentation></xs:annotation>
  <xs:complexType><xs:sequence>
  <xs:element name="operation" type="tns:Operation" minOccurs="1" maxOccurs="1">
    <xs:annotation><xs:documentation>
    Operation to perform
    </xs:documentation></xs:annotation>
  </xs:element>
      <xs:element name="inputs" type="tns:ArrayOfInt" minOccurs="1" maxOccurs="1">
    <xs:annotation><xs:documentation>
     Input values for the operation
    </xs:documentation></xs:annotation>
  </xs:element>
    </xs:sequence></xs:complexType>
</xs:element>

<!-- Response: Dokumentation zur besseren Lesbarkeit entfernt -->
<xs:element name="PerformCalculationResponse">
  <xs:complexType>
    <xs:sequence>
  <xs:element name="result" type="xs:decimal" minOccurs="1" maxOccurs="1"/>
</xs:sequence>
  </xs:complexType>
</xs:element>

<!-- Error: Dokumentation zur besseren Lesbarkeit entfernt -->
<xs:element name="CalculatorError">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="message" type="xs:string" minOccurs="1" maxOccurs="1"/>
      <xs:element name="code" type="xs:int" minOccurs="1" maxOccurs="1"/>
    </xs:sequence>
  </xs:complexType>
</xs:element>

<!-- Enum Typ: Dokumentation zur besseren Lesbarkeit entfernt -->
<xs:simpleType name="Operation">
  <xs:restriction base="xs:string">
    <xs:enumeration value="ADD"/>
    <xs:enumeration value="SUBTRACT"/>
    <xs:enumeration value="MULTIPLY"/>
<xs:enumeration value="DIVIDE"/>
  </xs:restriction>
</xs:simpleType>

<!—Komplexer Listentyp: Dokumentation zur besseren Lesbarkeit entfernt -->
<xs:complexType name="ArrayOfInt">
  <xs:sequence>
    <xs:element name="value" type="xs:int" minOccurs="1" maxOccurs="unbounded"/>
  </xs:sequence>
</xs:complexType>

Das Schema folgt dem Venetian-Blind-Prinzip. Nachrichten werden als Elemente definiert. Jeder Parameter wird als Element innerhalb eines ComplexType definiert, wobei sich der Name, die Kardinalität und der Typ festlegen lassen. Typen, die in verschiedenen Nachrichten wiederverwendet werden sollen, werden als globale ComplexType definiert (z. B. ArrayOfInt). Als Typen kommen alle XSD-Standardtypen wie string, int, boolean sowie eigene komplexe Datenstrukturen zum Einsatz. Fehler werden ebenfalls als Element definiert (CalculatorError). Das Fehlerobjekt wird in der CalculatorException gekapselt, die sich wie das Interface im api-Modul befindet. XSD ist eine mächtige Beschreibungssprache, mit der sich beispielsweise auch Vererbungen und Enumerationen (z. B. Operation) definieren lassen. Durch Imports lassen sich Typdefinitionen wiederverwenden. Die Portabilität der Schnittstelle ist automatisch gegeben, da ausschließlich XML-Schema-konforme Typen eingesetzt werden. Den größten Teil der Definition macht die Dokumentation aus. Die XSD-Dateien sollten vollständig und klar mittels <xs:annotation><xs:documentation> dokumentiert werden und erfüllen so Regel 9 (Dokumentation) eines nachhaltigen Servicekontrakts [1]. Listing 2 zeigt dies exemplarisch am Request-Typ. Das Schema wird durch eine XSLT-Transformation in eine lesbare HTML-Dokumentation überführt [1]. Erreicht wird dies durch das Maven-Plug-in org.jvnet.jaxb2.maven2:maven-jaxb2-plugin. Durch die Bereitstellung von xjb-Dateien kann die Codegenerierung weitgehend beeinflusst werden. Beispielsweise können Java-Typen für bestimmte xsd-Typen definiert werden. So kann ein xsd:dateTime durch java.util.Calendar oder seit Java 8 durch java.time.LocalDateTime abgebildet werden. Das Schema dient als zentrales Artefakt für Dokumentation und Codegenerierung. Die Dokumentation sollte in einem Repository, zum Beispiel einem Wiki, abgelegt werden. Dadurch wird der Service für andere Entwickler auffindbar und die Wiederverwendbarkeit wird erhöht. Der Kontrakt wird in einem eigenen Maven-Modul mit Namen api abgelegt. So kann er in allen anderen Projekten verwendet werden.

Die Serviceimplementierung

Die Serviceimplementierung erfolgt im Maven-Modul mit Namen impl. Dieses Modul enthält im einfachsten Fall nur eine Abhängigkeit zum api-Modul. Listing 3 zeigt die Implementierungsklasse. Die generierten Nachrichten aus dem api-Modul werden in der Signatur verwendet.

public class CalculatorImpl implements Calculator{
    @Override
public PerformCalculationResponse performCalculation(
       PerformCalculationRequest request) throws CalculatorException {
      // Durchführen der Berechnung
      double result = ...
      return new PerformCalculationResponse().withResult(result);
    }
  }

Das POJO CalculatorImpl stellt die Serviceimplementierung bereit. Etwaige Aufrufprotokolle wie SOAP oder Datenformate wie JSON sind hier nicht relevant. Der Service wird allein durch das implementierte Konzept und nicht durch die Technologie definiert. Listing 3 verwendet in Form der Methode withResult das aus dem XML Schema durch JAXB generierte Fluent-API. Besonders bei komplexen Datenstrukturen wird dadurch die Lesbarkeit des Codes deutlich erhöht. Serviceimplementierungen weisen in der Regel Abhängigkeiten zu anderen Libraries oder Frameworks, beispielsweise für die Persistenz, auf. Hierbei ist auf die Unabhängigkeit zu achten. Es ist abzuwägen, ob beispielsweise die Kopplung an einen Applikationsserver via JPA tolerierbar ist oder stattdessen beispielsweise Hibernate innerhalb der Implementierung verwendet werden soll. Je geringer die Abhängigkeit von einer Laufzeitumgebung ist, umso universeller einsetzbar wird ein Service sein.

Durch die Einbindung des api– und impl-Moduls kann der Service innerhalb einer einfachen Java-VM verwendet werden. Diese Art der Verwendung wird auch als Native Binding bezeichnet.

EJB-Bindung

Eventuell soll der Service über Maschinengrenzen hinaus erreichbar sein. Dies kann durch einen Remote-EJB-Aufruf erreicht werden. Voraussetzung dafür ist ein EJB-Container. In diesem Beispiel also JBoss WildFly. Weitere Gründe für die Realisierung einer EJB-Bindung können auch Container Managed Transactions (CMT), Bean-Pools oder deklarative Sicherheit sein.

@Stateless
@Named("ejb")
@Remote(Calculator.class)
public class CalculatorEJBBinding implements Calculator{

  private Calculator impl = new CalculatorImpl();

  @Override
  public PerformCalculationResponse
   performCalculation(PerformCalculationRequest request) throws 
   CalculatorException {
    return impl.performCalculation(request);
  }
}

Es ist erkennbar, dass das EJB Binding lediglich einen Wrapper um die Serviceimplementierung darstellt. Dieser Wrapper enthält alle EJB-spezifischen Annotationen. Die Aufrufe werden direkt an die Serviceimplementierung delegiert, und der Code wird auf ein Minimum reduziert. CalculaturImpl wird in der Klasse instanziiert. Alternativ könnte er via Contexts and Dependency Injection (CDI) injiziert werden. Da WildFly CDI unterstützt, wäre das eine Option, würde aber die technische Abhängigkeit der Serviceimplementierung erhöhen. Technische Kopplungen sollten stets mit Bedacht und nur dann eingebaut werden, wenn sie einen konkreten Nutzen bringen. Die Nachrichten aus dem api-Modul werden auch hier direkt weiterverwendet. Somit verfügt die EJB-Bindung über die gleiche Aufrufschnittstelle wie die native Serviceimplementierung.

Natürlich ließe sich die Serviceimplementierung auch direkt in der EJB vornehmen. Damit wäre jedoch in jedem Fall ein EJB-Container als Laufzeitumgebung erforderlich, um den Service zu betreiben. Im Sinne einer nachhaltigen Entwicklung ist das zu vermeiden. Die EJB-Bindung wird im Modul binding-ejb implementiert. Dieses Modul enthält die Maven-Abhängigkeiten api und impl. Die Serviceimplementierung selbst hat keine Kenntnis von der EJB-Bindung. EJB stellt neben anderen nur eine weitere Möglichkeit des Aufrufs dar. Die EJB-Bindung enthält keine Servicelogik und kann bei Bedarf leicht gegen alternative Implementierungen ausgetauscht werden. Es kann sinnvoll sein, bindungsspezifischen Merkmale, wie beispielsweise die Transaktionssteuerung, innerhalb der Serviceimplementierung zu realisieren, da diese sonst beim Wechsel der Bindung verloren gehen würden. Hier stehen die Anforderungen Einfachheit der Realisierung und Unabhängigkeit der Serviceimplementierung in einem Spannungsfeld, das individuell aufzulösen ist.

SOAP-Bindung

Die SOAP-Bindung stellt den Service über SOAP/HTTP bereit. Metadaten von SOAP-Services werden durch eine WSDL-Datei beschrieben. Im Sinne eines konsequenten Contract-First-Ansatzes sollten diese explizit kodiert und nicht generiert werden. Im Rahmen des SSD stellt die WSDL-Datei einen bindungsspezifischen Sekundärkontrakt dar. Er basiert auf den XML-Schema-Dateien und verwendet diese durch einen Import unverändert weiter.

<xsd:import namespace="http://www.pleus.net/services/calculator/api/model" schemaLocation="xsd/calculator.xsd"/>

Das Schema calculator.xsd wird durch das Maven-Plug-in org.apache.maven.plugins:maven-dependency-plugin aus dem api-Modul übernommen. Somit reduziert sich der Inhalt der WSDL auf ein Minimum und es entstehen keine Redundanzen. Abbildung 1 zeigt die WSDL-Datei.

Abb. 1: WSDL-Datei „SOAP-Bindung“

Abb. 1: WSDL-Datei „SOAP-Bindung“

Die Nachrichten und Fehler aus dem XML Schema dienen zur Definition des WSDL-PortType. Durch das Maven-Plug-in org.jvnet.jax-ws-commons:jaxws-maven-plugin wird ein Interface für den PortType mit den erforderlichen Annotationen generiert. Die SOAP-Bindung implementiert dieses Interface und delegiert den Aufruf an die Serviceimplementierung. Statt CalculatorPortType kann auch Calculator implementiert werden. Somit wird die Schnittstelle in allen Bindungen wiederverwendet. Je nach Qualitätsmerkmalen kann an die EJB-Bindung oder die native Implementierung delegiert werden. Listing 5 zeigt die Implementierung.

@WebServlet("/soap")
@WebService(targetNamespace="http://www.pleus.net/services/calculator",
            serviceName="CalculatorService",
            portName="CalculatorPort",
            wsdlLocation="WEB-INF/wsdl/calculator.wsdl")
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public class CalculatorSOAPBinding implements CalculatorPortType {

  @Inject  @Named("ejb")
  private Calculator service;
  
    @WebMethod(action = "urn:performCalculation")
    @WebResult(name = "PerformCalculationResponse", targetNamespace = "...", 
               partName = "parameters")
    public PerformCalculationResponse performCalculation(@WebParam(...) 
           PerformCalculationRequest parameters)
           throws CalculatorSOAPException{
      try {
          return service.performCalculation(parameters);
       } catch (CalculatorException e) {
          throw new CalculatorSOAPException(e.getMessage(),e.getError());
       }
    }
}

Wie bei allen Bindungen sehen wir auch hier keine Servicelogik, sondern eine einfache Delegation an die Serviceimplementierung. Durch die Kapselung des Fehlerobjekts in der CalculatorSOAPException wird es im Fehlerfall als SOAP Fault an den Aufrufer zurückgegeben. Die Exception dient dem Transport, der eigentliche Fehler befindet sich in CalculatorError. Die XML-Schema-Dateien und die generierten Nachrichten werden wiederverwendet, wodurch sich auch hier der Implementierungsaufwand auf ein Minimum reduziert.

XML-/JSON-Bindung

Die XML-/JSON-Bindung stellt den Service via HTTP im XML- oder JSON-Format zur Verfügung. Insbesondere die JSON-Variante ist hierbei für in JavaScript entwickelte Webclients interessant, da somit eine einfache Integrationsmöglichkeit zur Verfügung steht. Die XML Schemas des Servicekontrakts beschreiben ebenso die JSON-Schnittstelle. Dies ist ein wichtiger Aspekt, da durch den SSD-Contract-First-Ansatz eine vollständig dokumentierte JSON-Schnittstelle bereitgestellt werden kann. Wie bei der SOAP-Bindung lassen sich die Schema-Dateien in den Subkontrakt in Form der WADL-Datei integrieren und so unverändert wiederverwenden wie in Listing 6 gezeigt. Die Implementierung der XML-/JSON-Bindung erfolgt mit JAX-RS, wie in Listing 7 gezeigt.

<?xml version="1.0"?> 
 <application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:api="http://www.pleus.net/services/calculator/api" 
  xmlns="http://wadl.dev.java.net/2009/02"
  xsi:schemaLocation="http://wadl.dev.java.net/2009/02 wadl.xsd 
  http://www.pleus.net/services/calculator/api calculator.xsd"> 

   <grammars> 
     <include href="xsd/calculator.xsd"/>
   </grammars> 
   <resources base="http://localhost:8080/services/calculator/rest"> 
     <resource path="performcalculation"> 
       <method name="POST" id="performcalculation"> 
         <request>
    <representation element="api:PerformCalculationRequest"
                           mediaType="application/json" /> 
    <representation element="api:PerformCalculationRequest"
                           mediaType="application/xml" /> 
         </request> 
         <response status="200"> 
           <representation element="api:PerformCalculationResponse"
                           mediaType="application/xml" /> 
         </response> 
       </method> 
     </resource>
   </resources> 
 </application>
@Path("/")
@RequestScoped
public class CalculatorRESTBinding implements Calculator {

  @Inject  @Named("ejb")
  private Calculator service;
  
  @POST @Path("performcalculation")
  @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
  @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public PerformCalculationResponse
           performCalculation(PerformCalculationRequest request)
           throws CalculatorException{
      return service.performCalculation(request);
    }
}  

Auch hier werden das Calculator-Interface sowie PerformCalculationRequest und Response aus dem api-Modul verwendet und so der Code auf ein Minimum reduziert. Spätestens jetzt ist das Prinzip der Bindungen klar erkennbar. Nach dem gleichen Prinzip lassen sich bei Bedarf weitere Bindungen, beispielsweise für JMS, hinzufügen. Die XML-/JSON-Bindung präferiert Aufrufe über HTTP-POST, da hierbei die Request-/Response-Nachrichten unverändert verwendet werden können und die Einheitlichkeit der Schnittstelle automatisch gewährleistet ist. Falls erforderlich, ist ein Aufruf via HTTP-GET ebenfalls möglich. Dabei sollte jedoch darauf geachtet werden, dass die Schnittstelle vollständig auf die Query-Parameter abgebildet wird, da sonst die Einheitlichkeit verloren geht. Da die Bindungen immer gleich aufgebaut sind, lassen sie sich bei Bedarf auch leicht generieren. Auf Basis des api-Moduls und JAX-RS lässt sich ebenfalls ein Java-Client implementieren, der über die XML-/JSON-Bindung mit dem Service kommuniziert. Dadurch wird eine bequeme und typsichere Zugriffsmöglichkeit für Java-basierte Konsumenten geschaffen. Eine solche Implementierung ist Teil des Beispielcodes. Da Signaturänderungen immer zentral im XML Schema vorgenommen werden, stehen diese ohne zusätzlichen Aufwand in allen Bindungen zur Verfügung.

Module und Paketierung

Bis hier haben wir die wesentlichen Module kennengelernt. Dazu kommen noch die Module ear für die Paketierung des Services sowie das Modul test. Eine Übersicht über alle Module zeigt Tabelle 1.

Tabelle 1: Module

Tabelle 1: Module

Die HTML-Dokumentation kann in das Service-Repository übernommen werden. Alle Tests sind im test-Modul enthalten. Durch den Einsatz von Arquillian kann der Service inklusive aller Bindungen in einer realen Ausführungsumgebung automatisch getestet werden.

Regeln und Prinzipien

Aus didaktischen Gründen wurde bisher auf die Anwendungen einiger Regeln des nachhaltigen Servicekontrakts [1] verzichtet. Mengenoperationen (Regel 4) wurden nicht implementiert. Dies kann erreicht werden, indem statt PerformCalculation die Operation PerformCalculations implementiert wird. Kompensation (Regel 5) ist nicht erforderlich, da der Calculator keine Daten dauerhaft ändert. Korrelation (Regel 6) kann implementiert werden, indem dem Request/Response bzw. jeder Berechnung eine Korrelations-ID übergeben wird, mit der sich die Antwort einer Anfrage eindeutig zuordnen lässt. Somit wird der Service auch in asynchronen Szenarien, beispielsweise via JMS nutzbar. Darüber hinaus lässt sich die Nachhaltigkeit erhöhen, wenn Quellcode so geschrieben wird, dass er nicht nur von Maschinen, sondern auch von anderen Menschen verstanden und nachvollzogen werden kann. Die einheitliche Struktur der SSD-Services unterstützt dies.

Fazit

Dieser zweite Teil der Artikelserie beschreibt einen konkreten Design- und Entwicklungsansatz für die nachhaltige Entwicklung von Software Services. Gezeigt wird eine exemplarische Implementierung mit Java EE und WildFly. SSD ist nicht auf diese Konstellation beschränkt. So hat es sich seit 2007 in zahlreichen geschäftskritischen Projekten, beispielsweise in Kombination mit OSGi, Spring und .NET bewährt. SSD vereint viele Best Practices der Serviceorientierung in einem praktischen Ansatz. Es sollte allerdings nicht dogmatisch verstanden werden, sondern kann für den jeweiligen Kontext adaptiert werden. SSD bedarf keiner schwergewichtigen Produkte oder Laufzeitumgebungen. Also, einfach mal ausprobieren!

SOAP oder REST?
In Projekten hört man oft die Fragestellung, ob besser ein SOAP- oder ein REST-Ansatz verfolgt werden soll. Genau genommen hinkt dieser Vergleich, da es sich bei SOAP um ein Aufrufprotokoll und bei REST um einen Architekturansatz handelt. Meist verbergen sich hinter dieser Formulierung aber andere Fragen.
Frage 1: Soll XML oder JSON für die Kommunikation verwendet werden? JSON ist vermeintlich schneller und kompakter und ideal für JavaScript-Clients, während XML durch XML Schema klare typisierte Kontrakte ermöglicht und von Tools sehr gut verarbeitet werden kann. Im Rahmen von SSD ist diese Frage eigentlich obsolet, da JSON und XML über verschiedene Bindungen gleichzeitig unterstützt werden. Gleichzeitig erhalten wir durch den konsequenten Contract-First-Ansatz typisierte Kontrakte auch für die JSON-Bindung.
Frage 2: Soll ein ressourcenorientiertes oder operationsorientiertes Servicedesign verfolgt werden? Ein ressourcenorientierter Stil stellt, wie der Name schon sagt, die Ressource in den Mittelpunkt. Für eine Ressource können in der Regel CRUD-Operationen ausgeführt werden, die über HTTP-Verben (GET, POST, DELETE etc.) angesprochen werden. Ein operationsorientierter Stil (Remote Procedure Call, RPC) stellt die Operation in den Mittelpunkt. RPC wird beispielsweise im Bereich von SOAP Web Services eingesetzt. Auch SSD verfolgt einen operationsorientierten Stil. Ein Service stellt ein potenziell wiederverwendbares fachliches Konzept dar. Die Interaktion mit dem Service erfolgt über servicespezifische Operationen. Durch Services und Operationen lassen sich Konzepte der physischen Welt sehr gut abbilden, da diese meist durch Informationen (Daten) und beliebige Interaktionen (Operationen) charakterisiert sind.

 

SSD-Kritik
Für die Anwendung des Ansatzes ist die Akzeptanz der Entwickler essenziell. Nur wenn sie die beschriebenen Prinzipien anwenden und den Ansatz tragen, kann SSD erfolgreich eingesetzt werden. Für Entwickler kann ein konsequenter Contract-First-Ansatz unter Umständen ungewohnt sein und Einschränkung der Autonomie bedeuten. Typische Kritikpunkte sind daher:• Als Java-Entwickler bin ich mit XML Schema und WSDL/WADL nicht vertraut.
• Mit Code First kann ich schneller programmieren als mit Contract First.
• Die generierten Java-Klassen sind nicht so, wie ich sie selber programmieren würde.Es empfiehlt sich, diese und eventuell weitere Kritikpunkte vor dem Einsatz von SSD mit dem Entwicklerteam zu diskutieren. Erfahrungsgemäß verschwinden etwaige Einwände nach den ersten Serviceimplementierungen, nachdem mehr Erfahrung gesammelt wurde. Es kann erforderlich sein, Wissen in Bezug auf XML Schema, WSDL und WADL aufzubauen, um die Kontrakte effizient entwickeln zu können. Empfohlen wird außerdem die Erstellung eines anschaulichen Prototyps und Maven Archetypes zur Darstellung von Best Practices. Der Beispielcode [2] kann dabei helfen.

Aufmacherbild: Application icons in human hand von Shutterstock / Urheberrecht: Sergey Nivens

Verwandte Themen:

Geschrieben von
Wolfgang Pleus
Wolfgang Pleus
Wolfgang Pleus arbeitet als freier Technologieberater, Autor und Trainer im Bereich moderner Softwarearchitekturen. Seit zwanzig Jahren unterstützt er internationale Unternehmen bei der Realisierung komplexer Geschäftslösungen auf der Basis von Java EE und .NET. Seine Schwerpunkte liegen in den Bereichen SOA, BPM und Agile.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: