Introduzione

Nei precedenti articoli abbiamo parlato di SOA sia dal punto di vista architetturale (vedere [MOKASOAI]) che delle metodologie di analisi e di design (vedere [MOKASOAII]).

Un'azienda che decide di evolvere il proprio sistema informativo verso una architettura SOA deve fare diverse scelte e fronteggiare numerose sfide.

In [MOKASOAIII] e [MOKASOAIV] abbiamo visto come sia indispensabile capire il livello di maturità dell'organizzazione e definire in base a questa una roadmap che definisca i passi aziendali (organizzativi, tecnici, metodologici e culturali) per definire un processo evolutivo ed incrementale con l'obiettivo di una architettura SOA matura.

In [MOKASOAV] abbiamo parlato di Service Oriented Integration (SOI): i principi Service Oriented applicati alle problematiche d'integrazione.

In [MOKASOAVII] si sono introdotte le caratteristiche principali di un Enterprise service BUS (ESB). I servizi SOA devono essere in grado di comunicare tra di loro attraverso un canale di comunicazione: il SOA bus. Da un punto di vista architetturale il SOA bus è un layer che deve mettere a disposizione uno strato di comunicazione tra i servizi: in [MOKASOAVIII] si sono presentati alcuni ESB open-source.

In [MOKA_VIII] si sono introdotte alcune possibili strategie d'adozione di SOA ed in particolare si è parlato dell'importanza di un SOA Pilot.

In [MOKASOAIX] abbiamo presentato uno scenario d'esempio di analisi e design di un processo, utilizzando WSDL e BPEL.

In questo articolo, l'ultimo della serie dedicata a SOA, concludiamo la presentazione dell'esempio pratico con cui stiamo "ripassando" tutto quello che è stato presentato nei precedenti articoli.

Architettura implementata

Il caso d'uso d'esempio che avevamo preso in considerazione è quello della gestione di un ordine di acquisto che viene considerato valido se l'acquirente presenta una email e una carta di credito validi.

Figura 1 - Il Processo BPEL d'esempio
Figura 1 - Il Processo BPEL d'esempio

L'esempio proposto è stato sviluppato con ServiceMix 3.0 (vedere [SERVICEMIX]).

ServiceMix è ad oggi l'ESB JBI-compliant open-source che, a nostro parere, è il più maturo tra quelli disponibili open-source.

Nel nostro esempio ci siamo attenuti alle specifiche JBI: dobbiamo quindi definire l'applicazione come Composite Application. Coerentemente con JBI, i servizi saranno esposti al BUS in WSDL da Service Engine (SE) e accedibili dall'esterno tramite Binding Component (BC) HTTP.

Lo scenario che verrà implementato è il seguente:

  • Un client (Java e C#) invoca il WSDL del BPEL accedendo al bus service mix mediante un JBI Binding Component SOAP
  • L'invocazione, arrivata al BC, viene inoltrata (routing) al SE BPEL che orchestra i seguenti servizi:
  • il servizio di controllo della carta di credito (CheckCdC)
  • il servizio di controllo email (CheckMail)
  • Il servizio, sulla base del risultati dei servizi invocati, restituisce la risposta al client
Figura 2 - L'esempio proposto
Figura 2 - L'esempio proposto

Configurazione ServiceMix e JBI

ServiceMix mette a disposizione una serie di componenti JBI già pronti all'uso, che devono essere installati per essere utilizzati. Questi componenti sono presenti nella directory "components" di ServiceMix, sotto forma di archivio (.zip). Per effettuare l'installazione, è sufficiente copiare l'archivio relativo al componente che si vuole installare nella directory "install".

Per fare funzionare il nostro esempio, installeremo i seguenti componenti:

  • servicemix-jsr181: SE per i WS
  • servicemix-bpe: SE per il processo BPEL
  • servicemix-http: BC per l'esposizione al di fuori del bus del processo BPEL
Figura 3 - Le directory di installazione e deploy
Figura 3 - Le directory di installazione e deploy

Questi componenti serviranno per effettuare il deploy delle nostre Service Unit (SU), sia Binding Component (BC) che Service Engine (SE). L'insieme delle SU assemblate in un Service Assembly produce la composite application che vogliamo costruire. La composite application non è altro che un archivio contenente i .zip delle SU e il jbi.xml del SA. L'archivio della SA va copiato nella directory "deploy" perché ServiceMix poi proceda all'installazione.

Quindi, coerentemente con le specifiche JBI, costruiremo una composite application formata da tre SU:

  • SE JSR181 per la pubblicazione dei servizi CheckMail e CheckCdC sul BUS
  • SE BPE per l'orchestrazione dei servizi
  • BC HTTP per l'esposizione del servizio all'esterno

Il Service Assembly descriptor (il file jbi.xml della SA) dell'applicazione demo dell'articolo prevede quindi:

<service-assembly>
  <identification>
    <name>MokaDemoSA</name>
    <description>Moka Demo service assembly</description>
  </identification>

La definizione della Service Unit del SE JSR1818

  <service-unit>
    <identification>
      <name>jsr181su</name>
      <description>jsr181 service unit</description>
    </identification>
    <target>
      <artifacts-zip>jsr181.zip</artifacts-zip>
      <component-name>servicemix-jsr181</component-name>
    </target>
  </service-unit>

La Service Unit del SE BPEL

  <service-unit>
    <identification>
      <name>bpesu</name>
      <description>bpe service unit</description>
    </identification>
    <target>
      <artifacts-zip>bpe.zip</artifacts-zip>
      <component-name>servicemix-bpe</component-name>
    </target>
  </service-unit>

La Service Unit del Binding Component HTTP

  <service-unit>
    <identification>
      <name>httpsu</name>
      <description>Contains the binding of the bpe process on SOAP-HTTP</description>
    </identification>
    <target>
      <artifacts-zip>http.zip</artifacts-zip>
      <component-name>servicemix-http</component-name>
    </target>
  </service-unit>

  <connections>
  </connections>
</service-assembly>
</jbi>
Figura 4 - Contenuto dell'archivio del Service Assembly
Figura 4 - Contenuto dell'archivio del Service Assembly

A questo punto occorre specificare il contenuto delle SU. I Web Services

Le annotazioni sono una delle funzionalità più recenti del linguaggio Java versione 5 e permettono di aggiungere marcatori al codice Java che indicano al container di seguire un determinato comportamento. Le annotazioni non sono codice, ma marcano il codice in modo che il container alteri il proprio comportamento di conseguenza. Questo meccanismo è stato utilizzato, fra le altre cose, per semplificare la definizione di oggetti e componenti, evitando la complessità di complicate configurazioni tramite file XML. Per quanto riguarda i WS, sono state definite appositamente le specifiche JSR 181 (vedere [JSR181SPEC]).

Di seguito si riporta un semplice esempio di annotazione per la definizione di un semplicissimo Web Service "Hello World".

@WebService
public class HelloWorldService {
  @WebMethod
  public String helloWorld() {
    return "Hello World!";
  }
}

Caricando questa classe in un container apposito, che ne riconosca il significato, si ottiene un servizio Web già pronto, senza la necessità di codificare parti di codice che supportino SOAP.

ServiceMix supporta lo standard JSR181 tramite il SE servicemix-jsr181. Noi comunque abbiamo utilizzato questo SE senza annotare la classe, dato che questo componente supporta la possibilità di specificare l'endpoint del servizio sulla configurazione della SU.

L'immagine che segue riporta il semplice esempio di Web Service CheckEmailService costituito dalla classe Java CheckEmailService e dal file xbean.xml (per la configurazione della SU).

Figura 5 - La classe Java ed il descriptor della SU
Figura 5 - La classe Java ed il descriptor della SU

Il BPEL

L'endpoint del processo per l'accesso "esterno" è definito nel file xbean.xml di configurazione della SU HTTP:

<?xml version="1.0"?>
<beans xmlns:http="http://servicemix.apache.org/http/1.0"
       xmlns:im="http://it.imolinfo.jbi4corba.test.webservice.generator">

  <http:endpoint service="im:TestProcessService"
                 endpoint="testprocess"
                 defaultOperation="processExecution"
                 role="consumer"
                 locationURI="http://localhost:8192/Service/TestProcess"
                 defaultMep="http://www.w3.org/2004/08/wsdl/in-out"
                 soap="true"/>
</beans>

La SU BPEL invece contiene i .wsdl del processo che stiamo definendo e il .bpel che esprime la logica del processo stesso.

La dichiarazione del servizio è:

<!--  Processo -->
<wsdl:types>
  <xsd:schema targetNamespace="http://wsdl.test.mokabyte.it"
              xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsd:element name="processRequest">
      <xsd:complexType>
        <xsd:sequence>
          <xsd:element maxOccurs="1" minOccurs="1"
                       name="cdc" nillable="true" type="xsd:string" />
          <xsd:element maxOccurs="1" minOccurs="1"
                       name="email" nillable="true" type="xsd:string" />
        </xsd:sequence>
      </xsd:complexType>
    </xsd:element>
    <xsd:element name="processResponse">
      <xsd:complexType>
        <xsd:sequence>
          <xsd:element maxOccurs="1" minOccurs="1"
                       name="result" nillable="true" type="xsd:string" />
        </xsd:sequence>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
</wsdl:types>

<wsdl:message name="processRequest">
  <wsdl:part name="payload" type="typens:processRequest"/>
</wsdl:message>
<wsdl:message name="processResponse">
  <wsdl:part name="payload" type="typens:processResponse"/>
</wsdl:message>

<wsdl:portType name="TestProcess">
  <wsdl:operation name="processExecution">
    <wsdl:input message="tns:processRequest" />
    <wsdl:output message="tns:processResponse" />
  </wsdl:operation>
</wsdl:portType>

<wsdl:binding name="TestProcess" type="tns:TestProcess">
  <wsdl:operation name="processExecution"></wsdl:operation>
</wsdl:binding>

<wsdl:service name="TestProcessService">
  <wsdl:port name="testprocess" binding="tns:TestProcess" />
</wsdl:service>

All'interno dello stesso file WSDL sono state inserite (ma non riportate nell'articolo) anche le descrizioni WSDL dei servizi utilizzati (CheckEmailService e CheckCdCService).

Il BPEL corrispondente al servizio dichiara le variabili utilizzate:

<bpel:variables>
  <bpel:variable messageType="ns1:processRequest" name="processRequest"/>
  <bpel:variable messageType="ns1:processResponse" name="processResponse"/>
  <bpel:variable messageType="ns2:checkCdC" name="checkCdC"/>
  <bpel:variable messageType="ns2:checkCdCResponse" name="checkCdCResponse"/>
  <bpel:variable messageType="ns2:checkEmail" name="checkEmail"/>
  <bpel:variable messageType="ns2:checkEmailResponse" name="checkEmailResponse"/>
</bpel:variables>

Riceve il messaggio da elaborare:

<bpel:receive createInstance="yes"
              name="ReceiveRequest"
              operation="processExecution"
              partnerLink="TestProcessRequest"
              portType="tns:TestProcess" variable="processRequest"/>

Legge i valori che vengono utilizzati per chiamare i servizi

<bpel:assign name="ReadRequestToResponse">
  <bpel:copy>
    <bpel:from variable="processRequest" part="payload"
                query="/ns1:processRequest/ns1:cdc"/>
    <bpel:to variable="checkCdC" part="payload" query="/ns2:checkCdC/ns2:input"/>
  </bpel:copy>
  <bpel:copy>
    <bpel:from variable="processRequest" part="payload"
                query="/ns1:processRequest/ns1:email"/>
    <bpel:to variable="checkEmail" part="payload" query="/ns2:checkEmail/ns2:input"/>
  </bpel:copy>
</bpel:assign>

Esegue la chiamata ai servizi:

<bpel:flow>
  <!-- Chiamo il servizio checkCdC -->
  <bpel:invoke name="CheckCdCService"
               operation="checkCdC"
               inputVariable="checkCdC"
               outputVariable="checkCdCResponse"
               partnerLink="CheckCdC"
               portType="tns:CheckCdCServicePortType"/>

  <!-- Chiamo il servizio checkEmail -->
  <bpel:invoke name="CheckEmailService"
               operation="checkEmail"
               inputVariable="checkEmail"
               outputVariable="checkEmailResponse"
               partnerLink="CheckEmail"
               portType="tns:CheckEmailServicePortType"/>
</bpel:flow>

Decide se restituire true o false

<bpel:switch>
  <bpel:case condition="(getVariableData('checkCdCResponse', 'payload', '/ns2:checkCdCResponse/ns2:out') = 'true') and (getVariableData('checkEmailResponse', 'payload', '/ns2:checkEmailResponse/ns2:out') = 'true')">
    <!-- true -->
    <bpel:assign name="ResponseTrue">
      <bpel:copy>
        <bpel:from expression="true()"/>
        <bpel:to variable="processResponse" part="payload" query="/ns1:processResponse/ns1:result"/>
      </bpel:copy>
    </bpel:assign>
  </bpel:case>

  <bpel:otherwise>
    <!-- false -->
    <bpel:assign name="ResponseFalse">
      <bpel:copy>
        <bpel:from expression="false()"/>
        <bpel:to variable="processResponse" part="payload" query="/ns1:processResponse/ns1:result"/>
      </bpel:copy>
    </bpel:assign>
  </bpel:otherwise>
</bpel:switch>

Ritorna la risposta

<bpel:reply name="ReplyResponse"
            partnerLink="TestProcessResponse"
            portType="tns:TestProcess"
            operation="processExecution"
            variable="processResponse"/>
Figura 6 - Il processo espresso in BPEL
Figura 6 - Il processo espresso in BPEL

JMX Console

ServiceMix espone i servizi interni ed i componenti attraverso JMX come da specifica JBI. L'MBeanServer del JBIContainer è raggiungibile mediante un JMXConnector che permette la connessione remota via protocollo RMI. Tramite JMX è quindi possibile ispezionare gli oggetti installati in ServiceMix.

String jndiPath = "jmxrmi";

JMXServiceURL url = new JMXServiceURL(
  "service:jmx:rmi:///jndi/rmi://<namingHost>:<namingPort>/" + jndiPath);

JMXConnector connector = JMXConnectorFactory.connect(url);

I parametri di default per la versione 3 di ServiceMix sono:

  • namingPort: 1099
  • container name: jmxrmi
  • JMX Service URL: service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi

Con il JDK 1.5 si ha a disposizione il tool Console JMX che permette di monitorare applicazioni JMX-compliant.

Per mandarla in esecuzione per il monitoring ed il management di ServiceMix utilizzare il seguente comando:

%JAVA_HOME%\bin\jconsole service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi
Figura 7 - Console JMX del JDK 5
Figura 7 - Console JMX del JDK 5

Una volta collegati è possibile visualizzare ed interagire con gli MBean selezionando la voce org.apache.servicemix.

Figura 8 - Gli MBean di Service Mix
Figura 8 - Gli MBean di Service Mix

Client

Avendo a disposizione l'interfaccia di un servizio definita in WSDL, il modo più semplice per chiamare un WS è generare gli stub mediante opportuni tool di conversione da WSDL al linguaggio d'interesse.

Ad esempio con Axis, partendo dal contratto WSDL, è possibile generare le classi da usare lato client utilizzando il tool WSDL2Java (java org.apache.axis.wsdl.WSDL2Java). Il tool WSDL2Java è quindi utile poiché permette di semplificare notevolmente lo sviluppo di un client Java riducendo la quantità di codice da scrivere e senza fare riferimento diretto alle API Axis o JAX-RPC.

Il codice del client Java che ci permette di invocare il nostro WebService diventa così semplice ed intuitivo ed è il seguente:

public class ProcessDemoTest {
  public static void main(String[] args) {
    try {
      TestProcess process = new TestProcessLocator();

      ProcessRequest correctRequest = new ProcessRequest();
      correctRequest.setCdC("123456");
      correctRequest.setEmail("email@email.it");

      ProcessResponse response = process.getTestProcessJBIPort().processExecution(correctRequest);

      System.out.println("Risultato:" + response.getResult());
      // ...
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
}

Con .NET è disponibile il programma analogo wsdl.exe che, dato il WSDL, genera la corrispondente classe Proxy C#.

Il codice client C# è di seguito riportato:

public class TestClient {
  public static void Main(string[] args) {
    new TestClient().test(args[0], args[1]);
  }

  public void test(string word1, String word2) {
    TestProcess service = new TestProcess();

    processRequest datoInput = new processRequest();
    datoInput.CdCId = word1;
    datoInput.email = word2;

    processResponse datoOutput = service.processExecution(datoInput);

    Console.WriteLine("Risultato:" + datoOutput.result);
  }
}

Mandando in esecuzione il client il processo ritorna false se la email o la carta di credito non sono valide e true nel caso in cui entrambi i dati siano corretti.

Nella nostra implementazione (semplificata) del processo, il Web Service CheckEmailService restituisce true nel caso in cui nella email sia presente la @ mentre il Web Service CheckCdCService restituisce true nel caso in cui il numero della Carta di Credito sia in formato numerico.

Lanciando quindi il processo con carta di credito "123456" ed email "aaa@bbb.it" il processo restituisce true. Nel caso in cui si invochi il servizio con email senza @ o con numero della carta di credito non numerica il processo restituisce false.

Figura 9 - Lo scenario d'esempio implementato
Figura 9 - Lo scenario d'esempio implementato

Conclusioni

Con questo articolo finisce questa lunga serie di articoli dedicati a SOA. Il percorso che abbiamo affrontato è stato impegnativo e ha toccato alcuni degli aspetti per noi importanti, ma che (purtroppo) spesso non sono trattati con sufficiente chiarezza (anche per colpa di molti articoli "nebulosi" che si trovano in rete).

Abbiamo cercato di affrontare l'argomento da molti punti di vista (architetturale, metodologico, organizzativo, tecnologico, pratico) in modo da offrire una visione il più possibile completa di questo tema complesso ma che oggi è sempre più strategico nel mondo IT.

Bibliografia

  • [MOKA_SOA_I] M. Piraccini, S. Rossini: SOA: Introduzione - MokaByte 100 - Ottobre 2005
  • [MOKA_SOA_II] M. Piraccini, S. Rossini: SOA: Metodologia - MokaByte 101 - Novembre 2005
  • [MOKA_SOA_III] M. Piraccini, S. Rossini: SOA (III): Il Maturity Model - MokaByte 102 - Dicembre 2005
  • [MOKA_SOA_IV] M. Piraccini, S. Rossini: SOA (IV) Roadmap - MokaByte N.103 - Gennaio 2006
  • [MOKA_SOA_V] M. Piraccini, S. Rossini: SOA (V) SOI - MokaByte N.104 - Febbraio 2006
  • [MOKA_SOA_VI] M. Piraccini, S. Rossini: SOA (VI) ESB (I) - MokaByte N.105 - Marzo 2006
  • [MOKA_SOA_VII] M. Piraccini, S. Rossini: SOA (VII) ESB (II) - MokaByte N.106 - Aprile 2006
  • [MOKA_SOA_VIII] M. Piraccini, S. Rossini: SOA (VIII) - MokaByte N.107 – Maggio 2006
  • [MOKA_SOA_IX] M. Piraccini, S. Rossini: SOA (IX) Esempio (I) - MokaByte N.108 - Giugno 2006
  • [SERVICEMIX] http://servicemix.org
  • [JSR208_SPEC] R. Ten-Hove, P. Walker: Java Business Integration (JBI) 1.0 Final Release May 24, 2005 - http://www.jcp.org/en/jsr/detail?id=208
  • [JBI_HOME] http://java.sun.com/integration/
  • [JSR181SPEC] JSR 181: Web Services Metadata for the Java Platform - http://www.jcp.org/en/jsr/detail?id=181
  • [WSBPEL] http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=wsbpel