Una serie di articoli che spiegano come realizzare un Web Service. Prima di lanciarsi nello sviluppo (che sarà affrontato nel dettaglio nel corso di questo e dei prossimi articoli) è però necessaria qualche considerazione di tipo “metodologico” sulle strategie da seguire.
I diversi approcci alla realizzazione di un Web Service
Sono possibili approcci diversi allo sviluppo di un Web Service. Vediamoli di seguito, analizzandone le caratteristiche principali. Vedremo in seguito se propendere per l’una o l’altra soluzione.
Implementation first
Nessuno sviluppatore, dovendo realizzare un Web Service, inizierebbe a codificare il WSDL del contratto, in quanto trova più naturale la scrittura del codice di implementazione. In effetti tutti gli ambienti di sviluppo dispongono degli strumenti necessari alla pubblicazione di un Web Service a partire dalla logica applicativa, sia essa preesistente o di nuova realizzazione.
Anche se, a rigore, un simile approccio contraddice il modello di progettazione orientato agli oggetti, secondo il quale la definizione delle interfacce è propedeutica all’implementazione della logica applicativa, tuttavia questa tecnica di sviluppo ci permette di pubblicare agevolmente un Web Service. Il programmatore non si deve confrontare con la complessa realizzazione del WSDL, da cui gli ambienti di sviluppo sono in grado di derivare le classi di stub, endpoint e di trasformazione XML-Java. Ma la rapidità con cui è possibile realizzare prototipi a partire dall’implementazione crea un forte accoppiamento tra la gestione del payload XML e l’implementazione di stub ed endpoint, non permettendo una efficiente gestione del ciclo di vita del servizio. Inoltre la pubblicazione come Web Service di classi di implementazione già codificate non ci fornisce nessuna garanzia sul fatto che queste siano state progettate in un’ottica orientata ai servizi distribuiti tra piattaforme diverse.
Contract first
Con l’approccio “contract first” il progettista pensa il Web Service codificando il suo contratto di pubblicazione. Sebbene sia spesso difficile convincere gli sviluppatori a codificare in XML, in quanto trovano più naturale lo sviluppo nei linguaggi di programmazione, questo approccio aderisce alla caratteristica primaria dei Web Service, l’interoperabilità, che, come abbiamo visto, potrebbe non essere rispettata con l’approccio “implementation first”. Il progettista, applicando questo modello, ragiona, piuttosto che sul codice di implementazione, sul contratto che il servizio espone ai consumatori. Definisce quindi l’XML della request della response a partire dai tipi XML definiti dallo standard WS-I Basic Profile, sulla base dei quali potrà derivare schemi specifici di applicazione attraverso la definizione di tipi complessi e/o array. In questo modo sarà sempre sicuro di trovare la corrispondenza di questi nei tipi specifici di piattaforma. La codifica del WSDL viene completata dalla definizione del formato dei messaggi pubblicati, del binding e del service.
A partire dal WSDL gli ambienti di sviluppo saranno in grado di creare gli skeleton di implementazione del servizio, la classe di endpoint e di serializzazione e de-serializzazione del payload XML. Ovviamente questo approccio sottintende la conoscenza della codifica XML e le nozioni di progettazione WSDL, in misura maggiore nel caso in cui lo stile del Web Service sia RPC.
Quale implementazione scegliere?
Il dibattito sullo stile di implementazione più corretto è ampiamente trattato e per alcuni aspetti controverso. In maniera molto sintetica riportiamo alcuni argomenti favorevoli all’approccio “contract first”che abbiamo trovato ampiamente condivisi. Il lettore potrà approfondire questi temi anche a partire da alcuni link riportati nei riferimenti.
Portabilità dei tipi
L’interoperabilità, ossia il supporto di piattaforme diverse (Java, .NET etc.) rappresenta la più importante caratteristiche dei Web Service. La comunicazione tra le diverse piattaforme è garantita dall’XML che, trasferito sul protocollo di rete, viene convertito dal corrispondente runtime di supporto ai Web Services negli artefatti software proprietari.
Questa conversione vincola l’approccio “implementation first” all’utilizzo dei soli tipi cosiddetti “portabili”. Infatti con questo modello si corre il rischio di realizzare Web Services a partire di classi di implementazione che utilizzano tipi specifici di piattaforma. È tuttavia vero che gli ambienti di sviluppo, in aderenza alla specifica JAX-RPC 1.1, impediscono a design time la creazione di Web Service a partire da implementazioni con tipi non supportati. Se con Eclipse proviamo a pubblicare come Web Service un metodo come quello riportato di seguito, otteniamo come risultato la violazione della specifica JAX-RPC 1.1. In questo listato abbiamo una implementazione che viola la specifica JAX-RCP 1.1:
public Map getListaCodiceAvviamentoPostale() { TreeMap map = new TreeMap(); /* ...............*/ map.put("04013", "Sermoneta"); map.put("04016", "Sabaudia"); map.put("04017", "San Felice Circeo"); map.put("04019", "Terracina"); map.put("04020", "Ventotene"); map.put("04027", "Ponza"); map.put("04029", "Sperlonga"); /* ...............*/ return map; }
In questa maniera gli ambienti di sviluppo preservano il rischio di violare il principio di interoperabilità quando si cerca di creare Web Services a partire da tipi che non trovino corrispondenza alla specifica JAX-RPC 1.1.
Performance
La serializzazione automatica in XML di una classe, per esempio implementata in Java o in uno dei linguaggi della piattaforma .NET, potrebbe nascondere l’effettiva dimensione del payload trasferito sul protocollo SOAP. A questo si aggiunge l’overhead computazionale di conversione da e verso l’XML che diventa tanto più significativo quanto maggiore è la complessità degli oggetti. Approcciando la progettazione con la definizione anticipata dell’XML il progettista ha sempre il controllo delle dimensioni del payload che viene trasferito, e della complessità computazionale della trasformazione da e verso l’XML.
Riusabilità
La definizione di una “type inventory”, a cui attenersi nella definizione dei contratti pubblicati, introduce un indiscutibile grado di robustezza nella progettazione dei servizi. I progettisti, a partire dai tipi primitivi della specifica JAX-RPC 1.1, definiranno gli schema XML dei tipi di base del business che vogliono modellare. In questo modo all’interno del nostro service bus ci sarà un’unica definizione dei tipi di base che potranno essere riutilizzati, attraverso opportune direttive di import, nei diversi scenari applicativi. Più è ampio il progetto di sviluppo tanto più importante diventa questo aspetto di centralizzazione: la proliferazione di definizioni diverse dello stesso tipo potrebbe dare origine a disallineamenti noiosi da gestire. Riportiamo di seguito un esempio che definisce all’interno del service bus il codice di avviamento postale, un tipo complesso:
Gestione delle versioni
Per definizione, la validità di un contratto è strettamente legata al fatto di restare nel tempo il più possibile costante. Sappiamo bene che questo accade raramente e che ci si trova a dover modificare l’interfaccia del servizio con conseguente codifica di una nuova implementazione. L’approccio “contract first”, poiche’ definisce una scarso accoppiamento tra il contratto e la sua implementazione, ci permette di implementare una nuova versione del servizio nella stessa classe di implementazione di quello originario. Si può quindi utilizzare una trasformazione XSLT per la conversione del vecchio messaggio nel nuovo. Saremo così in grado di pubblicare ambedue le versioni del contratto dismettendo quello obsoleto nel momento in cui tutti i consumatori avranno aderito alla nuova interfaccia.
Implementazione con Spring-WS
Illustriamo l’implementazione di un Web Service seguendo un approccio “contract first” attraverso il framework Spring-WS. Questo framework media la complessità di questo approccio in quanto lo sviluppatore non dovrà codificare il WSDL, che sarà generato dal framework a partire dall’XML della response e della request definite per il contratto del servizio.
Il WSDL generato sarà in stile “document” e uso “literal”. Stiamo parlando dello stile di binding SOAP, che in un Web Service può essere document o RPC. In corrispondenza di questo valore il client capisce che dovrà far uso di schema XML piuttosto che utilizzare convenzioni di chiamate di procedure remote.
Vedremo quindi come la realizzazione del Web Service si articola nella definizione dell’XML del contratto, della configurazione dei file di configurazione di Spring-WS e nella codifica delle classi di implementazione dell’endpoint e del servizio.
Modello UML e interfaccia del servizio
La prima cosa da fare è definire l’XML che rappresenta la request, la response e l’interfaccia del servizio web che vogliamo realizzare. La realizzazione che presentiamo è quella di un banale servizio di acquisto dove la request è costituita da tre tipi complessi, che rappresentano le informazioni circa l’acquisto, l’indirizzo di spedizione della merce e le informazioni di pagamento attraverso carta di credito. La response del servizio è definita da un tipo complesso che rappresenta i dati relativi all’acquisto.
Riportiamo di seguito i corrispondenti class diagram e schema XSD.
Figura 3 – Class diagram della response.
A partire dai class diagram della request e response codifichiamo lo schema XSD che rappresenta il contratto del servizio:
<schema targetNamespace="http://www.luigibennardis.it/spring-ws/Ecomm/Acquisto/types" xmlns_tns="http://www.luigibennardis.it/spring-ws/Ecomm/Acquisto/types" elementFormDefault="qualified">
Avremmo potuto utilizzare la direttiva di import per ciascun tipo definito dalla request e dalla response:
schemaLocation=<"it.luigibennardis.spring-ws.datatypes.acquistotype.xsd"/> schemaLocation=<"it.luigibennardis.spring-ws.datatypes.indirizzotype.xsd"/>
In questo modo non solo si ottiene una maggiore leggibilità, ma sarà anche possibile definire tutti i contratti dei servizi che si vogliono pubblicare a partire dalla definizione centralizzata di uno schema inventory. Si evitano così dannose duplicazioni: tutti i progettisti referenzieranno sempre questo inventory da cui creeranno i contratti dei servizi che vogliono realizzare.
Figura 4 – Schema del servizio.
Definiamo quindi l’interfaccia e la classe di implementazione del servizio riportata nel diagramma UML di figura 5.
Figura 5 – Class diagram dell’implementazione del servizio.
Conclusioni
Per questa volta ci fermiamo qui, anche se resta ancora molto da fare per portare a termine lo sviluppo del nostro web service: abbiamo appena cominciato e continueremo a svilupparlo nel prossimo articolo… Era però importante fare una breve panoramica su alcuni aspetti che sono alla base di certe scelte. Ci interessa soprattutto aver messo in luce quali siano le ragioni alla base dell’approccio “contract first”.Vedremo il prossimo mese come Spring Web Services ben si presti alla realizzazione di web service e faciliti il compito allo sviluppatore facendo risparmiare tempo ed errori.
Riferimenti
[1] Blog “Shared Experience”
[2] Best practices for interoperable web service applications
www.frotscher.com/download/JUG-ContractFirst.pdf
[3] Apache Muse – Conflicting Version of WSDL4J in WebSphere Deployments
http://ws.apache.org/muse/docs/2.2.0/manual/troubleshooting/wsdl4j-version-on-websphere.html
[4] Web Services Description Language (WSDL 1.1)
[5] Web Services Description Language (WSDL 2.0)
http://www.w3.org/TR/2007/REC-wsdl20-primer-20070626/
[6] Metro Web Services for the Java Platform
http://java.sun.com/webservices/index.jsp
[7] The Web Services Interoperability Organization
[8] Spring Framework
http://static.springsource.org/spring-ws/sites/1.5/reference/html/tutorial.html
[9] IBM sulla scelta del WSDL
http://www.ibm.com/developerworks/webservices/library/ws-whichwsdl/
[10] IBM sui web service “document style”
http://www.ibm.com/developerworks/webservices/library/ws-docstyle.html
[11] IBM sul mapping da WSDL a SOAP
http://www.ibm.com/developerworks/webservices/library/ws-tip-namespace.html
[12] Arjen Poutsma, Rick Evans, Tareq Abed Rabbo, “Spring Web Services. Reference Documentation”