Por:
Tomas Restrepo
Es evidente para
cualquier que los Web Services se han vuelto ya algo mucho m�s com�n; son no
solo una soluci�n tecnol�gicamente interesante a muchos problemas, sino que es
adem�s uno de esos buzz-words que los
clientes mismos solicitan con frecuencia. Este art�culo trata de tocar sobre
una falacia particular del desarrollo de soluciones basadas en Web Services en
la que muchas veces caemos por desconocimiento de esta tecnolog�a.
Una frase com�n
que se oye cuando se habla del desarrollo de WebServices es la solicitud com�n
de"Un servicio que retorne un
documento XML". Aparentemente simple; al fin y al cabo, un documento XML
no es m�s que una cadena de texto con un formato particular, no?
Bueno, pues
si.... y tambi�n no. Lo que deber�a resultar aparente (y muchas veces no lo es)
es que la frase "un servicio que retorne un documento XML" es
redundante (lo que los gringos llaman un Oxymoron).
Olvidamos acaso que el nombre original de los Web Services era "XML Web
Services"?
Hablar de un
servicio Web que retorne un XML es redundante precisamente porque el retorno[1]
de XML es impl�cito en la
definici�n misma de servicio Web! Y si es as�, y la
presencia de XML est� intr�nseca a los servicios Web, porque nos vemos en la
penosa necesidad de hablar de cadenas de texto (strings) para retornar y
obtener fragmentos arbitrarios como argumentos o retornos de las operaciones en
un Servicio?
El mundo de los
Web Services, como muchos otros mundos en el universo del desarrollo de
software, se visualiza para el desarrollador desde dos grandes perspectivas: El
modelo real, y el modelo de programaci�n.
A lo que hace
referencia esta divisi�n es simple: El modelo real es como funcionan realmente las cosas, mientras que el modelo
de programaci�n no es m�s que una
abstracci�n creada por las herramientas y lenguajes de desarrollo. El modelo
real hace referencia al tiempo de ejecuci�n, mientras que el modelo de
programaci�n hace referencia al tiempo de dise�o.
Como observamos
anteriormente, la frase "un servicio que retorne un documento XML" no
tiene sentido si la miramos desde la perspectiva del modelo real; pero parece
tener m�s sentido si la miramos desde el punto de vista del modelo de
programaci�n.
La manera en que
la mayor parte de la gente traducir�a esta frase en un servicio funcional
ser�a, como se mencion� anteriormente, definir un servicio con una operaci�n
que retorne un string que contenga el texto correspondiente a un documento XML.
Consideremos esta definici�n en C#/ASP.NET:
[ WebService(Namespace="urn:schemas-winterdom-com:servicio") ]
public class Servicio : WebService
{
[ WebMethod() ]
public string ObtenerXml()
{
return
"<texto>Hola Mundo</texto>";
}
// ...
}
Podemos decir que
la operaci�n ObtenerXml() devuelve un documento XML? Si lo miramos desde el
punto de vista del c�digo fuente (el modelo de programaci�n), parecer�a
evidente que la respuesta es si. Al fin y al cabo, retorna un string que
contiene el texto "<texto>Hola Mundo</texto>", el cual
evidentemente es un fragmento XML bien formado.
Consideremos
ahora una segunda operaci�n:
[ WebMethod() ]
public Cliente ObtenerCliente(int id)
{
return
new Cliente(id);
}
Podemos decir que
la operaci�n ObtenerCliente() devuelve un documento XML? Bajo la vista
�nicamente del modelo de programaci�n, la respuesta obvia parecer�a ser que no.
Parece ser evidente que el servicio retorna un objeto de tipo Cliente, no un
documento XML!
Retomemos ahora
el servicio anterior desde la perspectiva del modelo real. Bajo la perspectiva
real, sabemos, al menos en forma te�rica, sino pr�ctica, que los servicios Web
son basados en SOAP, y que SOAP es en si mismo un documento XML. Veamos como se
ver�a un mensaje SOAP de respuesta de cada una de estos dos operaciones.
Examinemos
primero el mensaje SOAP de respuesta de la operaci�n ObtenerXml():
<?xmlversion="1.0"encoding="utf-8"?>
<soap:Envelopexmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<
soap:Body
>
<
ObtenerXmlResponse
xmlns
="urn:schemas-winterdom-com:servicio">
<ObtenerXmlResult><texto>Hola
Mundo</texto></ObtenerXmlResult>
</
ObtenerXmlResponse
>
</
soap:Body
>
</soap:Envelope>
Si miramos
el contenido del <soap:Body/> no encontramos el esperado
"<texto>Hola Mundo</texto>", sino
"<texto>Hola Mundo</texto>". Humm....
ciertamente eso no parece como un fragmento bien formado de XML....
Miremos
ahora el mensaje SOAP de respuesta para la operaci�n de ObtenerCliente():
<?xmlversion="1.0"encoding="utf-8"?>
<soap:Envelopexmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<
ObtenerClienteResponse
xmlns
="urn:schemas-winterdom-com:servicio">
<ObtenerClienteResult>
<ID>12</ID>
<Nombre>John</Nombre>
<Apellidos>Doe</Apellidos>
</
ObtenerClienteResult
>
</
ObtenerClienteResponse
>
</
soap:Body
>
</soap:Envelope>
Sorpresa!
Si miramos el contenido del <soap:Body/>, podemos observar �nada m�s ni
nada menos que un documento XML bien formado! No es un documento con una
estructura muy definida, ni la m�s apropiada, pero sigue siendo un documento
XML. (Victoria p�rrica, pero victoria al fin y al cabo).
Qu� es lo
que sucede? Lo que estamos viendo es el efecto del modelo de programaci�n: No
es que los Web Services retornen objetos o clases (como parecer�a ser el caso
de ObtenerCliente()), sino que el modelo de programaci�n (en este caso el de
ASP.NET) representa el XML y sus esquemas como objetos en el entorno de
desarrollo. La mensajer�a sigue siendo XML.
Cuando
decimos que el Servicio retorna un string (como en el caso de ObtenerXml()),
pues nuevamente est� presente el modelo de programaci�n, que presume que
simplemente se quiere retornar una cadena de texto cualquiera. Es decir, el
modelo de programaci�n no tiene forma de saber telep�ticamente que el
desarrollador esperaba enviar solo cadenas que representaran fragmentos bien
formados de XML; por lo tanto, el entorno de ejecuci�n escapa la cadena para
asegurar que pueda ser almacenada dentro del verdadero documento XML formado
por el SOAP Envelope.
Miremos
ahora las diferencias en estas dos operaciones desde la perspectiva del
contrato del servicio (es decir, el WSDL). No examinaremos el WSDL completo ya
que ser�a demasiado largo, pero podemos concentrarnos en los elementos m�s
relevantes. Examinemos en primer lugar la definici�n del portType del servicio:
<wsdl:portTypename="ServicioSoap">
<
wsdl:operation
name
="ObtenerXml">
<
wsdl:input
message
="tns:ObtenerXmlSoapIn"
/>
<
wsdl:output
message
="tns:ObtenerXmlSoapOut"
/>
</
wsdl:operation
>
<
wsdl:operation
name
="ObtenerCliente">
<
wsdl:input
message
="tns:ObtenerClienteSoapIn"
/>
<
wsdl:output
message
="tns:ObtenerClienteSoapOut"
/>
</wsdl:operation>
</wsdl:portType>
Hasta aqu�,
todo es igual: Tenemos el servicio con dos operaciones, cada una con un mensaje
de entrada y de salida. Examinemos los mensajes de salida de ambas operaciones:
<
wsdl:message
name
="ObtenerXmlSoapOut">
<
wsdl:part
name
="parameters"
element
="tns:ObtenerXmlResponse"
/>
</
wsdl:message
>
<
wsdl:message
name
="ObtenerClienteSoapOut">
<
wsdl:part
name
="parameters"
element
="tns:ObtenerClienteResponse"
/>
</
wsdl:message
>
Nuevamente,
hasta aqu� igual. Miremos ahora la descripci�n en el XSD de cada uno de estos
dos mensajes, empezando por la de ObtenerXml():
<s:elementname="ObtenerXmlResponse">
<
s:complexType
>
<
s:sequence
>
<
s:element
minOccurs
="0"
maxOccurs
="1"
name="ObtenerXmlResult"type="s:string"/>
</
s:sequence
>
</
s:complexType
>
</s:element>
Como
podemos observar, la definici�n del mensaje de respuesta de la operaci�n
ObtenerXml() es un elemento "ObtenerXmlResult" que contiene un
string. No m�s y no menos. Por lo tanto, es claro que el WSDL no revela la
estructura interna (el schema) del XML contenido en dicho string.
Por el
contrario, la definici�n correspondiente a ObtenerCliente() es mucho m�s rica:
<s:elementname="ObtenerClienteResponse">
<
s:complexType
>
<
s:sequence
>
<
s:element
minOccurs
="0"
maxOccurs
="1"
name="ObtenerClienteResult"type="tns:Cliente"/>
</
s:sequence
>
</
s:complexType
>
</s:element>
<s:complexTypename="Cliente">
<
s:sequence
>
<
s:element
minOccurs
="1"
maxOccurs
="1"
name
="ID"
type
="s:int"
/>
<
s:element
minOccurs
="0"
maxOccurs
="1"
name
="Nombre"
type
="s:string"
/>
<
s:element
minOccurs
="0"
maxOccurs
="1"
name
="Apellidos"
type
="s:string"
/>
</s:sequence>
</s:complexType>
Como
podemos ver, esta descripci�n claramente especifica que la operaci�n retorna un
fragmento XML que contiene un elemento llamado ObtenerClienteResult que
contiene tres sub-elementos correspondientes al ID, Nombre y Apellidos. Mucho
mejor que ObtenerXml(), no? Cual de las dos operaciones creen que ser�a m�s
f�cil de consumir?
Para este punto
deber�a resultar entonces evidente que si NO se debe crear servicios Web que
retornen fragmentos XML como operaciones que retornen cadenas de texto. Las
desventajas son claras:
Sin
embargo, hay algunos casos en los que realmente no es posible crear servicios
cuya estructura sea realmente definida previamente; es decir, algunos en los
que la informaci�n de entrada o de salida pueden ser documentos XML
arbitrarios, y no documentos que se ajusten a una estructura fija.
En estos
casos, lo que se requiere es poder crear un servicio cuyas operaciones manejen
fragmentos bien formados de XML arbitrarios, pero esto no quiere decir que la
manera de hacerlo sea manejando strings que los contengan. WSDL, y en
particular XSD, ya tienen una forma espec�fica de denotar esto en la definici�n
de la estructura un mensaje mediante el uso de <xs:any/>[2]
.
A nivel del
modelo real, es decir, de la representaci�n final de dicha informaci�n, un
documento arbitrario XML se representa tal como es, es decir, como XML, sin
necesidad de escaparlo. A nivel del modelo de programaci�n, ASP.NET expone esta
funcionalidad mediante el uso de argumentos y valores de retorno de tipo
XmlNode/XmlElement/XmlDocument.
En realidad, esta funcionalidad es soportada directamente por el XmlSerializer, no por ASP.NET. Por lo tanto, se puede usar a diferentes niveles, como por ejemplo dentro de otro XML cuyo esquema si sea conocido.
Un ejemplo
de esta caracter�stica ser�a la siguiente operaci�n:
[ WebMethod() ]
[
SoapDocumentMethod(ParameterStyle=SoapParameterStyle.Bare) ]
[ return: XmlElement("Documento") ]
public
XmlElement ObtenerFragmentoXml()
{
XmlDocument doc = new XmlDocument();
doc.LoadXml("<texto>Hola
Mundo</texto>");
return
doc.DocumentElement;
}
Si
observ�ramos la descripci�n generada en el WSDL para esta operaci�n, ver�amos
que tendr�a la siguiente forma:
<!-- La Operacion en el portType-->
<wsdl:operationname="ObtenerFragmentoXml">
<
wsdl:input
message
="tns:ObtenerFragmentoXmlSoapIn"
/>
<
wsdl:output
message
="tns:ObtenerFragmentoXmlSoapOut"
/>
</wsdl:operation>
<!-- Los Mensajes -->
<wsdl:messagename="ObtenerFragmentoXmlSoapIn"/>
<wsdl:messagename="ObtenerFragmentoXmlSoapOut">
<
wsdl:part
name
="ObtenerFragmentoXmlResult"
element="tns:ObtenerFragmentoXmlResult"/>
</wsdl:message>
<!-- Los Tipos -->
<s:elementname="Documento">
<s:complexType>
<
s:sequence
>
<
s:any
/>
</
s:sequence
>
</
s:complexType
>
</s:element>
Como
podemos observar, en el �ltimo fragmento de la descripci�n aparece el
<xs:any/> indicando que el contenido de esa parte del mensaje es un
fragmento arbitrario de XML anidado dentro de un elemento llamado
"Documento". Miremos como se ver�a el mensaje de respuesta en la red:
<?xmlversion="1.0"encoding="utf-8"?>
<soap:Envelopexmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<
Documento
xmlns
="urn:schemas-winterdom-com:servicio">
<textoxmlns="">Hola Mundo</texto>
</Documento>
</
soap:Body
>
</soap:Envelope>
En el
mensaje de respuesta se puede observar claramente como el XML retornado es
tratado como XML realmente (incluyendo el manejo correcto de namespaces) y forma parte integral del
mensaje, pero permitiendo que tenga una estructura arbitraria.
La
perspectiva de Contract-First Development
busca es que los Web Services se definan sea partiendo del contrato, es decir,
del WSDL que los define y la mensajer�a XML que maneja el servicio, y no desde
el c�digo (Code-First Development). Por analog�a, Contract-First es equivalente de cierta
manera a aproximarse al dise�o del servicio desde la perspectiva del modelo
real, y no como Code-First que la
aproxima desde el modelo de programaci�n (como hace ASP.NET y VS.NET por
defecto).
El tema
completo de Contract-First es demasiado extenso para tratarlo como parte de
este art�culo, pero es importante mencionarlo ya que problemas como los aqu�
presentados se tornan evidentes desde el principio cuando se trabaja por
Contract-First.
Estos son
algunos recursos que pueden resultar �tiles para investigar sobre este tema:
http://www.thinktecture.com/Resources/Software/WSContractFirst/default.html
http://www.iona.com/blogs/newcomer/archives/000087.html
http://pluralsight.com/blogs/aaron/archive/2004/11/11/3440.aspx
http://msdn.microsoft.com/vstudio/java/interop/websphereinterop/default.aspx
El objetivo
de este art�culo era presentar porque no se debe hacer uso de servicios Web que
utilicen strings para el transporte de fragmentos de XML, bien sea como datos
de entrada o de salida, dado que existen mejores (y m�s apropiadas) opciones.
Adicionalmente,
se busc� dar claridad sobre como los modelos de programaci�n que presentan las
herramientas de desarrollo en la plataforma .NET (aunque es similar en otras
plataformas) pueden no siempre guiarnos hacia la mejor soluci�n; hay que
conocer como funcionan los modelos realmente para poder aproximar el problema
desde el mejor �ngulo.
[1]
Lo mismo vale para la informaci�n
de entrada (solicitud) del Web Service, pero se menciona solo el retorno para
mantener el texto m�s simple.
[2]
La definici�n completa del uso de
<xs:any/> puede encontrarse en la secci�n 3.10 "Wildcards" de
la parte 1 de la especificaci�n de XSD (http://www.w3c.org/TR/xmlschema-1/#Wildcards)