In articoli e guide precedenti è stato introdotto il documento WSDL (versione 1.1) il cui ruolo è descrivere l'interfaccia pubblica di un Web service. E' possibile partire dalla definizione dell'interfaccia e si ha un approccio top-down, oppure partire dal codice (ad esempio scritto in Java) per poi generare con appositi tool il WSDL e si ha l'approccio bottom-up.
Nel documento WSDL si descrive cosa può essere utilizzato (le operazioni), come viene utilizzato (protocollo di comunicazione e formato dei messaggi scambiati), e dove utilizzare il servizio (endpoint del servizio). Nel "come" abbiamo la possibilità di specificare diversi aspetti, tra i quali stile e codifica dei messaggi scambiati. Questi aspetti vengono definiti esplicitamente agendo su alcuni parametri del documento WSDL. Vi è un'ulteriore possibilità, quella di basarsi su un pattern "wrapped".
In questo articolo verrà fornita una descrizione degli stili e delle codifiche mostrandone gli aspetti salienti, le opportunità di utilizzo e i punti di forza o di debolezza; verrà inoltre approfondita la questione wrapped/non wrapped. Al contrario di quanto avviene nella definizione di stile e codifica, non vi sono parametri espliciti per specificare che il documento WSDL si basa su pattern wrapped. Devono essere rispettate invece una serie di regole sulle quali c'è però un po' di confusione. Seguiranno pertanto degli esempi dimostrativi per verificarle e facilitarne la memorizzazione.
L'articolo è costituito dalle seguenti sezioni:
- Precondizioni
- Stile: RPC vs document
- Codifica: encoded vs literal
- Wrapped o non wrapped, regole ed esempi
- Le combinazioni possibili, punti di forza e debolezze
- Conclusioni
Precondizioni
Assicurarsi di avere correttamente installato sul computer il Java Development Kit (JDK 6, Update 24 o successiva). Si assume che sia stata correttamente impostata la variabile d'ambiente JAVA_HOME
e aggiornato il Path di sistema.
E' inoltre utile avere conoscenze basilari sull'argomento, quindi occorre aver già affrontato il discorso WSDL, consigliamo la lettura della guida Web Service. Nell'articolo si utilizza inoltre il tool wsimport, appartenente alle API dello stack JAX-WS, adottato per generare artefatti a partire dal documento WSDL secondo un approccio top-down. E' possibile leggere un'introduzione all'argomento nell'articolo Java Web Service: L'approccio Top-Down in JBoss.
Stile: RPC vs document
Per prima cosa si specifica che lo stile non ha niente a che fare con il modello di programmazione, serve solo a indicare come tradurre un WSDL binding in un messaggio SOAP.
Può essere specificato a livello di binding:
<soap:binding
oppure a livello operativo, nel qual caso sovrascrive il livello di binding:
<soap:operation
Di default è document.
RPC
Adottando uno stile RPC si specifica che il <soap:body>
contiene un elemento con il nome del metodo remoto invocato, elemento che contiene a sua volta un tag per ogni parametro d'ingresso e per il valore di ritorno del metodo. Questo stile riflette pertanto le modalità con le quali vengono definite le interfacce in Java. Segue un esempio di richiesta che segue questo stile:
<soap:envelope>
<soap:body>
<presentazione> <!-- nome del metodo -->
<nome>Pippo</nome> <!-- parametro -->
<cognome>De Pippis</cognome> <!-- parametro -->
</presentazione>
</soap:body>
</soap:envelope>
Document
Con lo stile Document il <soap:body>
contiene direttamente le parti del messaggio, senza necessità di regole di formattazione. Il server è responsabile di mappare gli oggetti serventi (parametri, metodi, ...) e i valori dei documenti XML trasmessi. Segue un esempio di richiesta basato su questo stile:
<soap:envelope>
<soap:body>
<!-- XML arbitrario -->
<presentazioni xmlns=”http://www.presentazioni.lista.com/”>
<presentazione>
<nome>Pippo</nome>
<cognome>De Pippis</cognome>
</presentazione>
<presentazione>
<nome>Pluto</nome>
<cognome>De Pippis</cognome>
</presentazione>
</presentazioni>
</soap:body>
</soap:envelope>
Confronto
Lo stile RPC è strettamente legato al codice dell'applicazione. Ciò implica che anche qualora si volesse cambiare l'ordine o il tipo dei parametri, ciò implicherebbe la necessità di dovere modificare il documento WSDL stesso.
Lo stile document invece è accoppiato solo in modo lasco con l'implementazione, in quanto le informazioni necessarie per l'elaborazione dei messaggi ricevuti sono ottenute dalla serializzazione e successiva deserializzazione (marshalling/unmarshalling) dei dati. E' possibile pertanto utilizzare un int
al posto di un double
per rappresentare un parametro, visto che alla fin fine verranno comunque convertiti in un campo XML. Altro vantaggio per lo stile document è quello di poter validare il messaggio di risposta SOAP.
Lo svantaggio principale dello stile document è l'impossibilità di determinare in modo standard il metodo remoto del Web service che verrà invocato, è il codice dell'applicazione a ricavare l'informazione (questo limite verrà superato con l'approccio wrapped, come vedremo in seguito).
Un punto controverso per lo stile document riguarda l'assenza di regole per la formattazione del body; ciò da un lato offre grande libertà espressiva (consentendo pertanto di rappresentare ArrayList, HashMap o BitSet, al contrario dello stile RPC che consente di passare solo tipi semplici come String o int), dall’altro implica un overhead dovuto alle operazioni di marshalling/unmarshalling.
Codifica: encoded vs literal
Analogamente a quanto già detto circa lo stile, i termini encoded o literal sono significativi solo per il mapping WSDL à SOAP.
Il parametro da specificare è il body|fault|header|headerfault use
, ad esempio:
<soap:body use="literal|encoded">
E’ necessario specificarlo per ogni input/output di ogni operazione.
Encoded
Nella codifica encoded ogni parte del messaggio fa riferimento a un tipo astratto utilizzando l'attributo type
piuttosto che basarsi su uno schema XML. Detto altrimenti, il body non segue uno schema ma segue uno specifico formato che ci si aspetta il client conosca. Da notare che il document/encoded di fatto non è molto utilizzato (e conseguentemente non riportato) in quanto, oltre a non essere WS-I compliant, lo stile document consente una libertà espressiva che mal si concilia con le informazioni da scambiare richieste dalla codifica encoded.
- RPC/encoded, messaggio SOAP:
<soap:envelope>
<soap:body>
<presentazione>
<nome xsi:type="xsd:string">Pippo</nome>
<cognome xsi:type="xsd:string">De Pippis</cognome>
</presentazione>
</soap:body>
</soap:envelope>
Literal
Con questa codifica ogni parte del messaggio fa riferimento a uno schema XML. I dati che viaggiano sono pertanto serializzati in accordo a uno specifico schema. Se il client ha accesso al WSDL ha la possibilità di sapere con precisione come è formattato il messaggio.
- RPC/literal, messaggio SOAP:
<soap:envelope>
<soap:body>
<presentazione>
<nome>Pippo</nome>
<cognome>De Pippis</cognome>
</presentazione>
</soap:body>
</soap:envelope>
- Document/literal, messaggio SOAP:
<soap:envelope>
<soap:body>
<nome>Pippo</nome>
<cognome>De Pippis</cognome>
</soap:body>
</soap:envelope>
Confronto
L'encoded non è approvato dal WS-I standard in quanto possono esservi lievi differenze nel modo in cui i vari linguaggi di programmazione e framework dei Web service sono in grado di interpretare le regole di formattazione, con conseguente rischio di incompatibilità. Inoltre, data la richiesta di descrivere il tipo dei dati che viaggiano (con conseguente ridondanza di informazioni trasmesse) di fatto è utilizzato solo con lo stile RPC che come abbiamo visto si limita allo scambio di tipi semplici.
Il Literal invece dà la possibilità di validare il messaggio rispetto a uno schema, evitando il rischio di incompatibilità e agevolando lo scambio di dati anche complessi, riducendo le informazioni da trasmettere.
Wrapped o non wrapped, regole ed esempi
C'è un’ulteriore classificazione che suddivide il pattern document/literal in wrapped e non wrapped (ossia bare). In primo luogo occorre specificare che lo stile wrapped non preveda dichiarazioni esplicite. Ha avuto origine in Microsoft, in modo da permettere di ricavare semplicemente il nome dell'operazione remota da invocare migliorando così le impostazioni rispetto a un normale document/literal. Pertanto, di fatto non ha senso proporlo per l’RPC che, come abbiamo visto, già si porta dietro il nome del metodo da invocare. L'uso di questo stile consente ad esempio l'interoperabilità con implementazioni Microsoft.
Ma come si fa a specificare che stiamo adottando questo stile? Seguendo una serie di regole, a volte non propriamente chiare, che consentono ai tool automatici di stabilire che i requisiti siano soddisfatti. In questa sezione si descriveranno le regole mostrando nel contempo degli esempi che consentano di verificare la validità delle affermazioni e di memorizzarle meglio.
Mostriamo innanzi tutto un esempio di messaggio SOAP che segue tale stile:
<soap:envelope>
<soap:body>
<presentazione>
<nome>Pippo</nome>
<cognome>De Pippis</cognome>
</presentazione>
</soap:body>
</soap:envelope>
Tralasciando l'assenza di dettagli sui parametri che viaggiano, il messaggio somiglia a quello scambiato con l'RPC/encoded. La differenza significativa è che al body non sta seguendo il nome dell'operazione da invocare ma il nome del wrapper, dove il wrapper è l'elemento, necessariamente singolo, che ingloba gli altri elementi del messaggio. Approfondiremo successivamente questi aspetti. Seguono il documento WSDL e lo schema XML utilizzati negli esempi successivi(dopo ogni esempio occorre riportare schema XML e/o documento WSDL alla forma originaria):
"documento.wsdl":
<?xml version="1.0" encoding="ISO-8859-1" ?>
<definitions name="WrappedNonWrapped"
targetNamespace="http://www.prova.documento.com/"
xmlns:tns="http://www.prova.documento.com/"
xmlns:schema="http://www.prova.schema.com/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<types>
<xsd:schema targetNamespace="http://www.prova.documento.com/">
<xsd:import namespace="http://www.prova.schema.com/" schemaLocation="schema.xsd" />
</xsd:schema>
</types>
<message name="PresentazioneReq">
<part name="parameter" element="schema:Presentazione" />
</message>
<message name="PresentazioneResp">
<part name="parameter" element="schema:PresentazioneRisposta" />
</message>
<portType name="ServizioPresentazioneIF">
<operation name="Presentazione">
<input message="tns:PresentazioneReq"/>
<output message="tns:PresentazioneResp"/>
</operation>
</portType>
<binding name="ServizioPresentazioneImplPortBinding" type="tns:ServizioPresentazioneIF">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="Presentazione">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="PresentazioneService">
<port name="ServizioPresentazioneImpl" binding="tns:ServizioPresentazioneImplPortBinding">
<soap:address location="http://localhost:8080/Presentazione/PresentazioneService/ServizioPresentazioneIF"/>
</port>
</service>
</definitions>
"schema.xsd":
<?xml version="1.0" encoding="ISO-8859-1" ?>
<xs:schema
xmlns:schema="http://www.prova.schema.com/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.prova.schema.com/"
>
<xs:import namespace="http://www.w3.org/2001/XMLSchema" />
<xs:element name="Presentazione">
<xs:complexType>
<xs:sequence>
<xs:element name="Nome" type="xs:string" />
<xs:element name="Cognome" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Titolo">
<xs:complexType>
<xs:sequence>
<xs:element name="NomeTitolo" type="xs:string" />
<xs:element name="AnnoTitolo" type="xs:int" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="PresentazioneRisposta">
<xs:complexType>
<xs:sequence>
<xs:element name="Risposta" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Document/literal, un caso particolare
Lo stile wrapped è una caso particolare di document/literal, pertanto si dovranno rispettare le regole di base dello stile document e della codifica literal. Lo stile come abbiamo visto può non essere specificato in quanto è document di default, invece è necessario specificare use="literal"
per ogni definizione soap:body
. Ciò implica che:
- Ci può essere al massimo un body part nel messaggio di input e di output.
- Ogni part fa riferimento necessariamente a un elemento e non a un tipo, elemento che deve essere definito, importato o incluso nel documento. Questi elementi costituiscono gli elementi "wrapper", elementi che a loro volta devono rispettare alcune regole.
Utilizziamo ora il tool wsimport dello stack JAX-WS (jdk 6) seguendo un approccio tipicamente top-down. Per i nostri esempi utilizziamo due opzioni del tool che ci consentono di aumentare la verbosità, ossia la quantità di informazioni che il tool ci restituisce (visualizzando pertanto i sorgenti generati), e di evitare di compilare gli schemi (aspetto al quale non siamo ora interessati). Il comando completo sarà pertanto:
wsimport -verbose -Xnocompile documento.wsdl
Esso rodurrà il seguente risultato:
Documento WSDL e schema XML soddisfano i requisiti e pertanto di default si utilizzerà il pattern wrapped. Essendo questo il comportamento di default, nelle classi generate non troviamo nessuna informazione a proposito. Nell'interfaccia ServizioPresentazioneIF
troveremo le seguenti annotazioni:
- @WebService
- @XmlSeeAlso
Se invece le regole non sono soddisfatte nella stessa interfaccia troveremo anche l’annotazione:
- @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
Se proviamo a non rispettare i due punti precedenti otterremo però dei messaggi di errore in quanto stiamo violando le regole di base del document/literal.
Nel primo caso, proviamo a introdurre nel body una secondo tag <part>:
<message name="PresentazioneReq">
<part name="parameter" element="schema:Presentazione" />
<part name="newelement" element="schema:Titolo" />
</message>
Otterremo in risposta un messaggio di errore
[ERROR] operation "Presentazione": more than one part bound to body
Analogamente, se tentiamo di utilizzare un tipo base
<message name="PresentazioneReq">
<part name="parameter" type="xsd:string" />
</message>
Il tool ci informerà che questo comportamento non si accorda con quanto abbiamo dichiarato esplicitamente:
[ERROR] Invalid wsdl:operation "Presentazione": its a document-literal operation, message part must refer to a schema element declaration
I messaggi di errore sono leggermente diversi. Nel secondo caso si informa di una violazione delle regole del document/literal, mentre nel primo no. Ciò è dovuto al fatto che la presenza di un solo part è necessaria per avere messeggi SOAP WS-I compliant, ma almeno in teoria non è del tutto esclusa dal document/literal (alcuni tool potrebbero consentire lo stesso la compilazione rinunciando alla portabilità/interoperabilità).
Document/literal wrapped, regole specifiche
Passiamo ora alla presentazione delle regole che caratterizzano il wrapped.
Il nome dell'operazione deve essere lo stesso dell’input wrapper. Nel nostro caso l'input wrapper è l'elemento Presentazione, pertanto l'operazione è stata definita come Presentazione.
Si è già introdotto che ciò consente di aumentare l'efficienza dei Web service in quanto è più semplice individuare la giusta operazione da invocare. Ora possiamo provare a violare questa regola per osservarne l'effetto. Proviamo pertanto a modificare il nome dell'operazione nel documento WSDL, utilizzeremo il nome del messaggio d'input, PresentazioneReq
.
E' necessario ricordarsi che occorre modificare il nome dell'operazione sia nel portType che nel binding.
Compilando non otterremo messaggi d'errore, però andando a consultare l'interfaccia ServizioPresentazioneIF
noteremo che è stata aggiunta l'annotazione @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
Detto altrimenti abbiamo ottenuto un document/literal not wrapped (bare).
Gli elementi wrapper devono essere definiti come complextype.
Proviamo a modificare nello schema l'elemento PresentazioneRisposta
, modificandolo in un elemento semplice.
<xs:element name="PresentazioneRisposta" type="xs:string" />
Ancora una volta compilando verrà aggiunta l'annotazione
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
Gli elementi costitutivi di un wrapper devono essere innestati in un tag sequence
o choice
, non all
. Detto altrimenti, alcuni sub-elementi possono essere nulli ma se ne deve rispettare l'ordine. Per verificarlo è possibile modificare il tag Order Indicator
nello schema XML per l'elemento Presentazione:
<xs:element name="Presentazione">
<xs:complexType>
<xs:sequence|choice|all>
<xs:element name="Nome" type="xs:string" />
<xs:element name="Cognome" type="xs:string" />
</xs:sequence|choice|all>
</xs:complexType>
</xs:element>
solo se si modifica in <all>
verrà introdotta l'annotazione @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
.
Se i sotto elementi sono costituiti a loro volta da elementi complessi non ci sono restrizioni di sorta sull'Order indicator
impostabile. Per averne un esempio, è sufficiente effettuare la seguente modifica agli elementi Presentazione e Titolo e verificare che continui ad essere considerato wrapped:
<xs:element name="Presentazione">
<xs:complexType>
<xs:sequence>
<xs:element name="Nome" type="xs:string" />
<xs:element name="Cognome" type="xs:string" />
<xs:element ref="schema:Titolo" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Titolo">
<xs:complexType>
<xs:all>
<xs:element name="NomeTitolo" type="xs:string" />
<xs:element name="AnnoTitolo" type="xs:int" />
</xs:all>
</xs:complexType>
</xs:element>
Una regola supplementare
C'è un'ulteriore regola che prevede di chiamare l'elemento wrapper di output con il nome del wrapper di input più Response
. Pertanto, nel nostro caso il wrapper d'output si sarebbe dovuto chiamare PresentazioneResponse
. Tale regola però non è obbligatoria, anche se qualche volta può capitare di leggere il contrario.
Le combinazioni possibili, punti di forza e di debolezza
In questa sezione si riassumono vantaggi e svantaggi dei possibili pattern adottabili, mostrando che la scelta della soluzione da adottare è soggetta a diverse considerazioni.
RPC/encoded
- Vantaggi
- Struttura semplice.
- Il nome dell'operazione compare nel mesaggio.
- Svantaggi
- La codifica rende ridondanti i messaggi
- Non è WS-I compliant
- Difficile da validare
RPC/literal
- Vantaggi
- Struttura semplice
- Il nome dell’operazione compare nel mesaggio
- WS-I compliant
- Svantaggi
- Difficile da validare
Document/encoded
- Svantaggi
- La codifica rende ridondanti i messaggi
- Non è WS-I compliant
Document/literal (bare)
- Vantaggi
- Validabile
- Svantaggi
- La mancanza del nome dell’operazione rallenta il dispatching
- Non necessariamente WS-I compliant
Document/literal wrapped
- Vantaggi
- Validabile
- Dispatching semplificato dalla presenza del nome dell’operazione nel messaggio
- WS-I compliant
- Standard de-facto
- Svantaggi
- WSDL più complesso
- Messaggi più verbosi
- Overloading delle operazioni non realizzabile
Quando usare chi
Da quanto detto finora il pattern document/literal wrapped sembra il compromesso ottimo tra efficienza ed efficacia. La quantità minima di informazioni in più presente nei messaggi SOAP è giustificata dal minor carico computazionale richiesto al SOAP engine per l'individuazione del metodo corretto da invocare.
A ciò si aggiunge l'aspetto non secondario che sia diventato uno standard de-facto, per cui è lecito pensare che le nuove applicazioni verranno sviluppate seguendo questo pattern.
Il problema principale dipende però dalle regole cui sono soggetti i wrapper, regole che non necessariamente sono state seguite nel passato. Non è raro che non si possano apportare modifiche agli schemi o agli stessi documenti WSDL, pertanto non è difficile che applicazioni legacy debbano seguire altri pattern.
Ci sono inoltre dei casi particolari per cui può risultare tuttora conveniente utilizzare uno stile RPC. Un esempio può venire dalla necessità di inviare i dati di un grafo, ad esempio di un albero binario. Il metodo standard di invio si basa sul tag href
, che è parte del pattern RPC/encoded. Ma con la codifica literal il tag href
non è disponibile. In questo caso è possibile utilizzare diversi sistemi per trasmettere le stesse informazioni con la codifica literal, ma nessuno di questi modi è standard, per cui potrebbero risultare non interoperabili e/o soggetti a ridondanza.
Infine, un'altra problematica riguarda l'overloading delle operazioni. Abbiamo visto che il nome dell'operazione corrisponde al nome del wrapper di input. Ma non possono esservi nell'XML due elementi con lo stesso nome, pertanto se si necessita di effettuare l'overloading delle operazioni si dovrà ripiegare sul document/literal bare o su uno stile RPC. In teoria si potrebbero utilizzare gli schemi XML per avere nomi uguali ma con namespace differente, ma nei Web service in generale l'overloading è sconsigliato e il WSDL 2.0 non lo supporta. Pertanto in alcuni casi questa limitazione non viene nemmeno annoverata tra gli svantaggi del document/literal wrapped.
Conclusioni
Nel corso dell'articolo si sono approfonditi i principali stili e codifiche dei documenti WSDL. Si sono descritte le quattro combinazioni di base e l'introduzione di una quinta, document/literal wrapped, per ottenerne una che racchiudesse i principali vantaggi al minor prezzo possibile. Di norma l'obiettivo è raggiunto ed è possibile utilizzare quest'ultimo pattern garantendosi portabilità ed interoperabilità. Ma in alcuni casi, applicazioni specifiche o legacy, tuttora può rivelarsi opportuno/necessario dover utilizzare un'altra combinazione.