Interoperabilità e Web Service. In questo articolo (terzo della serie) si parlerà di integrazione e interoperabilità dal punto di vista pratico. Una volta definiti gli standard architetturali e tecnologici, in che modo due applicazioni eterogenee possono comunicare? Con quali mezzi? Saranno in grado di capirsi e condividere informazioni? La discussione partirà dalla descrizione di due software eterogenei che si fondono fino a creare un‘architettura ibrida, generata dall‘utilizzo di un paradigma a servizi con framework WSAS e Web Service Framework for PHP. Prove pratiche di comunicazione, insomma.
Java e PHP: Un esempio di architettura ibrida
Iniziamo con un paio di supposizioni. Supponiamo che un team di sviluppatori lavori per un’azienda con business core in sviluppo software per strutture ricettive (TuriCoorp). E supponiamo che, a un certo punto della propria esistenza, l’azienda decida di acquistare tale FidHotel, impresa concorrente.
Ora, la suite software della prima impresa è scritta interamente in Java/Swing, mentre la seconda azienda aveva deciso a suo tempo di aggredire il mercato con un prodotto Web Based interamente scritto in PHP/Ajax, spendendo parecchie risorse in studi sull’usabilità del sistema.
La TuriCoorp si trova quindi a dover scegliere quali parti dell’infrastruttura acquistata riciclare all’interno della propria e quali non considerare nell’integrazione o di cui rinviare l’utilizzo a un momento successivo, valutando naturalmente il numero di sviluppatori con competenze PHP da destinare al supporto della nuova architettura e le potenzialità di entrambi i linguaggi nella gestione di risorse differenti. Da un’attenta analisi del framework appena acquistato, infatti, emerge una particolare predisposizione della piattaforma PHP/Ajax a gestire le problematiche di interazione con l’utente finale, mentre poca attenzione sembra essere stata dedicata alla progettazione della base dati ed alla gestione delle problematiche di prenotazione, core del sistema e punta di diamante della piattaforma TuriCoorp.
Si opta, quindi, per un’architettura ibrida, in modo da utilizzare database e logica di business del software TuriCoorp e interfaccia grafica del framework FidHotel. A patto, naturalmente, di riuscire a rendere i due sistemi interoperabili.
In figura 1 sono illustrate le architetture dei due software prima e dopo la fusione. In principio la piattaforma FidHotel utilizza un back-end scritto in PHP, ospitato su Web Server e accessible via browser, laddove TuriCoorp fa uso di un DBMS lato server mentre tutta la logica applicativa è affidata a client che adoperano Java/Swing come vista. D’altro canto nel framework ibrido si decide di conservare parte del codice PHP e gran parte del front-end Ajax, grosso valore aggiunto dell’architettura FidHotel appena acquistata. Lato TuriCoorp, invece, i client vengono depauperati della logica di business, che adesso sarà racchiusa in servizi i quali vengono esposti in forma di Web Service. La figura chiarisce meglio quanto esposto.
Figura 1 – Fusione delle Architetture.
Ora, compito di questo articolo non sarà certo quello di dire quale sia il design “ottimo” per una situazione del genere, ne’ quello di interferire con le scelte del team TuriCoorp, seppur opinabili. Date le scelte progettuali poco sopra descritte, però, un’implementazione e una successiva esposizione dei servizi all’esterno dovrebbe permettere ai due applicativi eterogenei (PHP e Java) di parlarsi e consentirne un certo grado di integrazione.
Degli innumerevoli servizi che la TuriCoorp ha intenzione di esporre, prendiamo a titolo di esempio il BookingService, che si occupa delle prenotazioni: è un oggetto il cui compito è fornire informazioni sullo stato e sulla disponibilità delle camere degli alberghi, insieme alla possibilità di prenotare le stesse e alcuni servizi accessori. La figura 2 illustra un semplice diagramma delle classi di tale servizio e di alcune delle risorse ad esso connesse, considerando che per l’esempio in oggetto sono stati presi in esame soltanto tre metodi. Il BookingService, infatti, contiene null’altro che i metodi da esporre ed utilizza un Data Access Object (BookingServiceDAO) per l’accesso alla base dati. La classe Camera, invece, anch’essa in versione light, ripulita cioè da tutti i fronzoli, contiene informazioni sulla camera quali il numero, il tipo (doppia, singola, etc…) ed un booleano che indica la presenza di televisore. Anche qui le informazioni sono tutt’altro che esaustive, ma sufficienti per le prove di comunicazione.
Figura 2 – BookingService: diagramma delle classi.
Naturalmente il BookingServiceDAO non fa nient’altro che restituire dati predefiniti (non esiste una base dati in questo esempio) e il BookingService poggia sul BookingServiceDAO proprio per l’accesso ai dati (che nella realtà ovviamente esisteranno). Entrambi, poi, utilizzano la classe Camera. In questo caso sono state utilizzate soltanto classi concrete: ma, nella realtà, scrivere del codice per interfacce e non per classi avrebbe senz’altro rappresentato la soluzione ideale. Per l’implementazione delle classi dare un’occhiata al codice che segue.
public class BookingService { public int getNumeroCamereLibere() { BookingServiceDAO bookingServiceDAO = new BookingServiceDAO(); int camereLibere = bookingServiceDAO.getNumeroLibere(); return camereLibere; } public int getNumeroCamereTotali() { BookingServiceDAO bookingServiceDAO = new BookingServiceDAO(); int camereTotali = bookingServiceDAO.getNumeroTotali(); return camereTotali; } public Camera getCamera(int numCamera) { BookingServiceDAO bookingServiceDAO = new BookingServiceDAO(); Camera camera = bookingServiceDAO.getCamera(numCamera); return camera; } }
public class BookingServiceDAO { public int getNumeroLibere() { int numeroLibere = 10; return numeroLibere; } public int getNumeroTotali() { int numeroCamere = 90; return numeroCamere; } public Camera getCamera(int numeroCamera) { Camera camera = new Camera(numeroCamera,"Doppia",false); return camera; } }
public class Camera { private int numero; private String tipo; private boolean tv; public Camera(int numero, String tipo, boolean tv) { super(); this.numero = numero; this.tipo = tipo; this.tv = tv; } public int getNumero() { return numero; } public void setNumero(int numero) { this.numero = numero; } public String getTipo() { return tipo; } public void setTipo(String tipo) { this.tipo = tipo; } public boolean isTv() { return tv; } public void setTv(boolean tv) { this.tv = tv; } }
Esposizione dei servizi
Di WSAS abbiamo parlato nella parte II di questa serie di articoli (MokaByte 142 – Luglio/Agosto 2009). È un motore per Web Service che poggia su Apache Axis2, gira tranquillamente come applicativo standalone ed è in grado di creare Web Service utilizzando varie fonti, dalla classe POJO, al JAR, al Web Service Spring. In questo caso lo utilizzeremo come Web Service container per la gestione del servizio di prenotazione appena creato. Per i nostri scopi procediamo alla creazione di un jar (BookingService.jar) con le classi BookingService, BookingServiceDAO e Camera.
Una volta creato il .jar, da un Web browser (https://localhost:9443/carbon/ a meno che non abbiate cambiato qualcosa rispetto alle istruzioni fornite nel secondo articolo della serie) si accede a una pagina in cui sono presenti alcune funzioni di accesso pubblico e una finestra di login sulla destra con la solita richiesta di credenziali per l’accesso alla piattaforma. Quelle di default sono, come non è difficile immaginare, username: admin e password: admin.
Una volta autenticati, dal menu di gestione a sinistra attraverso la voce List è possible accedere alla lista dei Web Service attivi e, con la stessa facilità, dal menu Add è possibile aggiungere un Web Service, sia esso una classe POJO, un file .jar, un Web Service Axis2, JAX-WS, Spring. Nel nostro caso utilizziamo il JAR appena preparato.
Ora, utilizzando la voce Jar Service verrà chiesto il nome del gruppo di servizi al quale il servizio appartiene e di quale file .jar si ha la necessità di eseguire il deployment. Scegliamo il nostro BookingService.jar e subito dopo (pulsante Next) andiamo a indicare quale classe esporre come Web Service (nel nostro caso BookingService). Una volta scelta la classe (Next) passiamo ai metodi selezionandoli tutti e tre (getNumeroCamereLibere, getNumeroCamereTotali, getCamera) e dopo pochi secondi ci ritroveremo con il Web Service attivo (visibile nella lista).
Adesso è possible provare immediatamente il servizio (Try this service dalla lista dei Web Service attivi). Il metodo getNumeroCamereLibere dovrebbe ritornare il valore 10, il metodo getNumeroCamere dovrebbe ritornare il valore 90, mentre il metodo getCamere dovrebbe ritornare una camera doppia senza TV con numero corrispondente a quanto scelto come parametro di ingresso.
Una volta espletate le procedure di testing, è possibile dare uno sguardo alla struttura WSDL sia nel formato 1.1 che nel formato 2.0, in modo da avere un’idea sull’organizzazione del servizio. È proprio attraverso questo linguaggio di interfaccia basato su XML, infatti, che chi accede al servizio è in grado di capire quello che il servizio fa e come.
A operazioni ultimate il codice è esposto come servizio e pronto per essere interrogato. Vediamo come PHP sia in grado di farlo.
Accesso ai Servizi
Anche di WS02 Web Service Framewor for PHP abbiamo parlato nella parte II e non è nient’altro che un’estensione PHP che permette di produrre e consumare Web Service. In questo caso ci limitiamo al consumo.
A framework installato (dare uno sguardo alla parte II per dettagli) è necessario eseguire il deplyoment di quella parte dell’infrastruttura PHP che si occuperà dell’accesso ai servizi appena esposti attraverso la piattaforma WSAS (BookingService). Per creare tale codice (client) è possibile seguire due strade: la prima è quella di partire dalle API del framework e scrivere da zero il codice per interrogare il servizio BookingService; la seconda, invece, è quella di inferire il codice del client (o meglio parte del codice del client) partendo dall’interfaccia WSDL del servizio esposto.
Nel nostro caso optiamo per la seconda, salvo poi riservarci di analizzare il codice prodotto, e per la generazione del client utilizziamo lo script wsdl2php.php presente nella directory scripts della cartella dove abbiamo estratto il framework. Il comando sarà qualcosa del tipo
C:wso2-wsf-php-bin-2.0.0-win32scriptsphp wsdl2php.php http://server_ip_address:9763/services/BookingService?wsdl > booking_client.php
Il file booking_client.php è dunque lo skeleton del codice del client; adesso basta spostarlo sul Web Server, analizzarlo e testarlo.
Come si presenta dunque il codice appena generato? All’inizio del file vengono create le classi PHP corrispondenti ai tipi specificati all’interno del file wsdl, mentre i TODO successivi suggeriscono il da farsi. Subito dopo viene istanziato un oggetto client da cui si ricava un proxy per accedere ai metodi del servizio ed attraverso il proxy vengono richiamati tutti e tre i metodi esposti e ne vengono stampati i valori di ritorno. Si tenga presente che nello stampare il valore di ritorno va tenuto in considerazione il tipo del valore, definito in alto nel file. In ogni caso un confronto con l’interfaccia WSDL aiuta senz’altro a comprendere meglio i meccanismi di comunicazione. Il codice generato è pressapoco quello che segue, i commenti in inglese ed i TODO sono generati in maniera automatica dallo script wsdl2php.php, mentre il codice sotto i TODO dovrebbe rappresentare la logica di business (in questo caso vengono semplicemente stampati a video i valori di ritorno). Naturalmente è possibile scorporare il codice come meglio si crede in modo da soddisfare le proprie esigenze architetturali.
// PHP classes corresponding to the data types in defined in WSDL class getNumeroCamereLibereResponse { /** * @var int */ public $return; } class getNumeroCamereTotaliResponse { /** * @var int */ public $return; } class getCamera { /** * @var int */ public $numCamera; } class getCameraResponse { /** * @var (object)Camera */ public $return; } class Camera { /** * @var int */ public $numero; /** * @var string */ public $tipo; /** * @var boolean */ public $tv; } // define the class map $class_map = array( "getNumeroCamereLibereResponse" => "getNumeroCamereLibereResponse", "getNumeroCamereTotaliResponse" => "getNumeroCamereTotaliResponse", "getCamera" => "getCamera", "getCameraResponse" => "getCameraResponse", "Camera" => "Camera"); try { // create client in WSDL mode $client = new WSClient(array ( "wsdl" =>"http://172.16.10.249:9763/services/BookingService?wsdl", "classmap" => $class_map)); // get proxy object reference form client $proxy = $client->getProxy(); // create input object and set values //TODO: fill $input with (data type: anyType) data to match your business logic // call the operation $response = $proxy->getNumeroCamereLibere($input); //TODO: Implement business logic to consume $response, which is of type getNumeroCamereLibereResponse printf ("Camere Libere: %s ", $response->return); //TODO: fill $input with (data type: anyType) data to match your business logic // call the operation $response = $proxy->getNumeroCamereTotali($input); //TODO: Implement business logic to consume $response, which is of type getNumeroCamereTotaliResponse printf ("Camere Totali: %s ", $response->return); $input = new getCamera(); //TODO: fill in the class fields of $input to match your business logic $input->numCamera = 99; // call the operation $response = $proxy->getCamera($input); //TODO: Implement business logic to consume $response, which is of type getCameraResponse printf ("Numero Camera: %s ", $response->return->numero); printf ("Tipo Camera: %s ", $response->return->tipo); } catch (Exception $e) { // in case of an error, process the fault if ($e instanceof WSFault) { printf("Soap Fault: %s ", $e->Reason); } else { printf("Message = %s ", $e->getMessage()); } } ?>
Per analizzare meglio il processo request/response basta accedere (dalla console di WSAS) al SOAPTracer. Ogni volta che viene effettuara una request (da qualsiasi client quindi anche dal codice php appena preparato) viene tracciato lo scambio SOAP ed il risultato è quello mostrato sotto (in questo caso si riferisce all’invocazione del metodo getCamera(99)). Si faccia attenzione a come l’oggetto camera viene convertito per essere utilizzato dal richiedente. Sotto viene riportata prima la request del client e poi la response del Web Service.
http://172.16.10.249:9763/services /BookingService.BookingServiceHttpSoap11Endpoint/ urn:getCamera urn:uuid:01de478d-b541-4fc9-8e25-fe9a314bfa4a 99 urn:getCameraResponse urn:uuid:01de478d-b541-4fc9-8e25-fe9a314bfa4a xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance" xsi_type="ax21:Camera"> 99 Doppia false
Conclusioni
In questo articolo è stata affrontata la problematica dell’interoperabilità tra applicazioni eterogenee attraverso un esempio concreto. Diversi sono i motivi per cui due applicativi dissimili hanno necessità di parlarsi, come diverse solo le soluzioni a tali sfide progettuali. In questa sede abbiamo deciso di far dialogare PHP e Java: il primo uno dei linguaggi più diffusi in ambito Web, il secondo è uno dei linguaggi più amati in ambienti Enterprise. Le possibilità di dialogo, però, con un approccio orientato ai Web Service, sono pressochè illimitate. Utilizzando l’interfaccia del servizio, infatti, diversi linguaggi e piattaforme hanno la possibilità di accedervi in maniera trasparente.
Come negli altri articoli anche qui tendo a sottolineare che l’interazione descritta è meno di una tessera dell’intero puzzle, per la soluzione del quale potremmo star certi che di tempo non ne avremo mai abbastanza. Ma da qualche parte bisogna pur cominciare…
Riferimenti
[1] Thomas Erl, “SOA, Principles of Service Design”, Prentice Hall, 2008
[2] Deepal Jayasinghe, “Quickstart Apache Axis2”, Packt Publishing, 2008
[3] W3C, Web Service Activity
[4] WS02, The Developer Porta for SOA
[5] WS02, WSAS Project
http://wso2.org/projects/wsas/java
[6] WS02, WSF for PHP Project
http://wso2.org/projects/wsf/php
[7] WS02, WSAS Installation Guide
http://wso2.org/project/wsas/java/3.1.0/docs/installation_guide.html#Installing
[8] WS02, WSF for PHP Installation Guide
http://wso2.org/project/wsf/php/2.0.0/docs/manual.html