Moviendo datos en WCF

Siempre que planteamos una arquitectura, se nos plantea la misma duda ¿cómo movemos los datos entre capas de la aplicación?. Pues bien, estoy liado con la arquitectura de dos aplicaciones en cierto modo similares y una vez más me enfrento a esta cuestión. Solo que esta vez, además, esta WCF de por medio, tecnología nueva en la que no cuento con la experiencia que atesoro con DCOM, Remoting o Web Servcies ‘normales’.


Así que esta vez es un poco más arduo encontrar la respuesta.


A mi siempre me a parecido una excelente opción para mover datos usar los objetos contenedores de datos que proporcionan los ‘primos’ de la familia ADO: ADO COM del de toda la vida, con su Recordset y ADO.Net con sus DataSets y sus DataTables. Son una solución comoda, rápida de implementar y simple para solucionar el problema de mover datos entre capas de una aplicación. Los Recordset del viejo ADO y su ‘serialización’ en DCOM tenian un excelente redimiento además.


Los más puristas os estareís llevando las manos a la cabeza, diciendo, ¡pero que aberración, donde estén las colecciones de objetos!. Sin duda es una opción mucho más OOP y mucho más ‘cool’, pero ¿son la solución más comoda y simple?. En mi opinión no. Tienen un problema que hacen que no sean, en la mayoría de los casos, mi opción preferida: cada vez que añades un campo, tienes que modificar varias capas de código, no vale con simplemente cambiar la SELECT. Además las colecciones no se vinculan tan bien a componentes de interfaz de usuario (aunque esto a mejorado mucho). Las ventajas son código más elegante y un pelín más simple y autodescriptivo. Los DataSet tipados son una aproximación intermedia, pero basicamente, tener que crear un nuevo contendor de datos siempre que variamos los campos que devolvemos puede llegar a ser extremadamente tedioso y poco práctico.


Con WCF los partidarios de las colecciones tienen además otro argumento a su favor: Devolver o recibir DataSets viola el principio de SOA que dice que ‘los servicios comparten esquema y contrato, no clases’. Es evidente que cuando devolvemos o recibimos DataSet estamos obligando a que los clientes de nuestro servicio sepan que es un DataSet. Pero la reglas están para romperlas, cuando hay un motivo de peso para ello. No quiero decir que este principio no sea importante, pero si estamos seguros de que nuestros servicios solo serán consumidos desde clientes .Net, devolver DataSet puede ser muy comodo.


Ahora se presentan una serie de incognitas:


¿Mejor DataSet o DataTables?


En principio, la lógica dice, que un DataTable es un objeto más ligero que un DataSet, luego parece que una buena opción es devolver DataTables en lugar de DataSet. Pero ocurre una cosa curiosa, no podemos devolver un DataTable no tipado desde un servicio. Al menos yo no he logrado que se cree correctamente el proxy de lado cliente. No he encontrado la explicación a esta cuestión, no se porque ocurre esto. En principio el DataTable es tan serializable como el DataSet en la versión 2.0 del Framework pero no lo he logrado crear un cliente que reciba un DataTable no tipado. Especulando, he llegado a la conclusión de que el framework no tiene suficiente información para serilizar un DataTable. En concreto en este artículo dicen que debemos proporcionar un esquema para los DataTable no tipados, lo que no nos separa en nada de devolver un DataTable no tipado, cada vez que añadamos un campo tendremos que modificar el esquema devuelto.


Con los DataTables tipados no hay problema, la cuestión es que no hay ganancia ninguna, pues un DataTable tipado, se serializa exactamente igual que el DataSet tipado que lo contiene. Vamos, que los bytes que se mueven son los mismos. Para averiguar esto, simplemente he enchufado el Ethereal, un sniffer de red, para ver cuantos datos se movian.


¿Cómo son de eficientes moviendo datos los DataSet en WCF?


Se puede reformular esta pregunta a ¿Cómo de eficiente es la serialización de los DataSet en WCF?. La verdad es que no mucho. Este es el aspecto de un DataSet construido sobre la tabla Address de la base de datos AdventureWorks, cuando se devuelve desde un servicio que usa netTCPBinding, y por tanto el enconding binario.


…… net.tcp://pc065:8383/DataService…
.
…Q/http://tempuri.org/IDataService/GetTypedDataSet net.tcp://pc065:8383/DataServiceV…s…a.V.D
…..D..M…t.^J…>.5.aD,D*…D…….V…xsi)http://www.w3.org/2001/XMLSchema-instance..xsd http://www.w3.org/2001/XMLSchema@.GetTypedDataSet..http://tempuri.org/…
…..z7http://tempuri.org/IDataService/GetTypedDataSetResponse.GetTypedDataSetResponse.http://tempuri.org/.GetTypedDataSetResultV…s…a.V.D
…..D..M…t.^J…>.5.aD…….V.B.
.B.A.xs.schema..id..AddressDataSet..targetNamespace.%http://tempuri.org/AddressDataSet.xsd..attributeFormDefault..qualified..elementFormDefault..qualified..xs http://www.w3.org/2001/XMLSchema..mstns%http://tempuri.org/AddressDataSet.xsd.%http://tempuri.org/AddressDataSet.xsd..msdata$urn:schemas-microsoft-com:xml-msdataA.xs.element..name..AddressDataSet..msdata.IsDataSet..true..msdata.UseCurrentLocale..trueA.xs.complexTypeA.xs.choice..minOccurs…maxOccurs..unboundedA.xs.element..name..AddressA.xs.complexTypeA.xs.sequenceA.xs.element..name..AddressID..msdata.ReadOnly..true..msdata
AutoIncrement..true..type..xs:int.A.xs.element..name..AddressLine1A.xs
simpleTypeA.xs.restriction..base..xs:stringA.xs.maxLength..value..60….A.xs.element..name..AddressLine2..minOccurs.A.xs
simpleTypeA.xs.restriction..base..xs:stringA.xs.maxLength..value..
60….A.xs.element..name..CityA.xs
simpleTypeA.xs.restriction..base..xs:stringA.xs.maxLength..value..30….A.xs.element..name.
PostalCode..minOccurs.A.xs
simpleTypeA.xs.restriction..base..xs:stringA.xs.maxLength..value..15….A.xs.element..name..Phone..minOccurs.A.xs
simpleTypeA.xs.restriction..base..xs:stringA.xs.maxLength..value..25….A.xs.element..name..StateProvinceID..type..xs:int..minOccurs..A.xs.element..name..CountryRegionC
ode..minOccurs.A.xs
simpleTypeA.xs.restriction..base..xs:stringA.xs.maxLength..value..3….A.xs.element..name..ModifiedDate..type..xs:dateTime.A.xs.element..name..rowguid..msdata.DataType.XSystem.Guid, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089..type..xs:string……A.xs.unique..name..Constraint1..msdata
PrimaryKey..trueA.xs.selector..xpath…//mstns:Address.A.xs.field..xpath..mstns:AddressID….A.diffgr.diffgram..diffgr)urn:schemas-microsoft-com:xml-diffgram-v1..msdata$urn:schemas-microsoft-com:xml-msdata@.AddressDataSet.%http://tempuri.org/AddressDataSet.xsd@.Address..diffgr.id..Address1..msdata.rowOrder.@.AddressID.@.AddressLine1.
1970 Napa Ct.@.City..Bothell@
PostalCode..36903@.Phone..819-555-0175@.StateProvinceID..79@.CountryRegionCode..US @.ModifiedDate..2002-12-12T11:37:20.187+01:00@.rowguid.$9aadcb0d-36cf-483f-84d8-585c2d4ec6e9.@.Address..diffgr.id..Address2..msdata.rowOrder.@.AddressID..2@.AddressLine1..9833 Mt. Dias
….Resto de DataSet hasta 6,2 MB…..

Llama la atención que se incluye una gran cantidad de metadatos. Lo primero que se pasa por la cabeza es quitar esos metadatos, para disminuir el tamaño de los datos que se ponen en la red, estableciendo la propiedad SchemaSerializationMode del DataSet a ExclueSchema, pero esto para mi sorpresa ¡no tienen ningún efecto sobre la serialización del DataSet en WCF!. En Remoting y Web Services esta propiedad tenía un efecto radical sobre el rendimiento.

El problema se recrudece cuando usamos un binding basado en Http, entonces el enconding es XML y por lo tanto tenemos un tamaño de los datos serializados mucho mayor, unos 11Mb frente a los 6,2Mb que teniamos antes. Podeís ver como aparecen un motón de etiquetas XML con metadatos en la captura adjunta.

 

Resumiento los DataSet son muy comodos, pero no son lo más eficiente de mundo, sobre todo cuando se usa encoding XML. La situación es más complicada por la incapacidad de retirar los metadatos. En cualquier caso la comodidad y la sencilled pueden a veces tener más peso que el rendimiento sobre todo si este es aceptable. Devolver 20000 filas y cargarlas en un grid, tarda menos de 2 segundos en una red local.


De todos modos que casi 20000 filas, ocupen 6,2Mb en el mejor de los casos, solo deben hacernos pensar una cosa: es una pésima idea devolver 20000 registros desde un servicio, en un DataSet o en una colección de objetos o por SMS.


Por cierto, un artículo muy recomendable sobre la serialización en WCF: Serialization in Windows Communication Foundation

Recibir errores y trazas del Framework 3.0 en Inglés

 A pesar de lo que nos esforzamos desde Geeks.ms, es un hecho que hay mucha más información sobre Windows Workflow, Windows Presentation Foundation, Windows Communication Foundation y CardSpace en inglés que en español. En especial en lo que se refiere a errores.


Un problemilla que tenemos los desarrolladores que tenemos Windows instalado en español es que vemos todos los errores y trazas de WW, WPF y WCF en castellano. Esto es un problema porque, a día de hoy, es mucho más facil encontrar información sobre los errores en inglés que en castellano.


Pues bien la solución es simple, desinstalar el Paquete de idioma para español de Microsoft .Net Framwork 3.0, esto es simple, desde agregar o quitar pogramas:



Pero… en muchas ocasiones no aparece el elemento que vemos en el pantallazo de arriba. Esto es lo que me ha ocurrido a mi en varios equipos, aunque desconozco el motivo. En esos casos podemos desinstalar ejecutando el archivo Setup.exe que está en el directorio C:WindowsMicrosoft.NETFrameworkv3.0Microsoft Framework 3.0 Spanish Language Pack, tal y como se muestra en la siguiente pantalla:



El resulado será el mismo en los dos casos: 



Una vez completada la operación, tendremos los mensajes y trazas que lanza el Framework en inglés, lo que nos facitilitará bastante la vida a la hora de ‘Googlear’ en busca de la solución a nuestros problemas.

Excelente documento sobre System.Transactions

He encontrado un excelente documento en el que Juval Lowy le pega un repaso más que interesante a las posibilidades que abre el namespace System.Transactions a la hora de utilizar transacciones en nuestras aplicaciones.


El documento se llama Introducing System.Transactions in the Microsoft .NET Framework version 2.0, pero es bastante más que una introducción. Ya tiene unos mesecitos, pero no por eso pierde interés.


Hace una comparación con el enfoque tradicional y explica algunos detalles relacionados con el escalado de transacciones al DTC y otras interioridades que todo desarrollador debería conocer para poder sacar el mayor partido a las posibilidades del excelente soporte transaccional que tiene el framework de .Net desde la versión 2.0.


De muy recomendable lectura en cualquier caso y de imprescindible lectura si usamos System.Transactions en la arquitectura de nuestra aplicación.

Estimación y planificación detallada

Un error habitual en gestión de proyectos es creer que la mejor manera de estimar el coste o la duración de un proyecto es realizar una división del trabajo en tareas. Veía este error hace pocos días entre los comentarios al post titulado Tengo un plan: ser ágil. Es curioso cómo en varios comentarios se ligaba estimación con planificación detallada. El argumento era más o menos el siguiente: puesto que las metodologías ágiles no realizan una estimación detallada más que en el momento de abordar una iteración o sprint ¿cómo podemos estimar?.


Lo primero, me gustaría explicar porque las metodologías ágiles no tratan de realizar una planificación detallada: es una simple cuestión de economía. Realizar una planificación detallada es un esfuerzo grande. Además todos sabemos los volátiles que tienden a ser las planificaciones. Cuando más detalladas más volátiles. Según una aproximación ágil, el coste de realizar una planificación detallada no merece la pena.


Lo segundo, no realizar una planificación detallada, no significa no realizar una planificación en absoluto. Es posible realizar una planificación suficiente para guiar el proyecto a medio y largo plazo sin necesidad de de que esta sea demasiado detallada. Los riesgos de una planificación excesiva siempre amenazan a los proyectos y en ocasiones son más peligrosos que los de una planificación escasa. Incluso existe un patrón que describe esta situación ‘Death by planning’, su nombre lo dice todo.


Bueno una vez dejado claro que las metodologías ágiles no propugnan no hacer planificación sino hacer la que es imprescindible, volvamos al tema que nos ocupa. ¿es necesaria una planificación detallada para poder estimar?. No, y lo voy a ‘demostrar’ con un pequeño ejercicio de estimación que os pido realicéis, y me hagáis llegar vuestros resultados en los comentarios. No pongaís por favor cual debería ser el resultado obtimo, cualquiera lo puede averiguar. Solo contadme cual fue vuestra desviación en cada una de las estimaciones del ejercicio.


El ejercicio es el siguiente: Debéis estimar la distancia entre Bilbao y Ámsterdam. No vale usar MapPoint ¿eh?. Seguro que estáis pensado que es algo difícil, que no tenéis información suficiente, pero eso es precisamente lo que ocurre la mayor de las veces en un proyecto de software. Debéis estimar con poca información o lo que es peor en base a información incompleta o sesgada.
Bueno venga… ¿tenéis ya una estimación?. Apuntadla.


Ahora os propongo hacer el equivalente a detallar más el plan de proyecto y estimar en base a la descomposición del viaje en etapas, actuación en cierto modo equivalente a estimar un proyecto en base a la subdivisión en tareas. Os diré por qué ciudades tendréis que pasar, así podréis estimar tramos más pequeños, y en teoría vuestra estimación debería ser más exacta. ¿Lo será?.


El itinerario pasa por: Bilbao – Bayona – Burdeos – Orleans – Paris – Amberes – Amsterdam
Debéis estimar estos tramos y sumar el resultado.


Ahora podéis comparar vuestras dos estimaciones con el valor real. ¿A mejorado en algo el tener un itinerario más detallado vuestra estimación? Seguro que no.


Podriamos refinar más aun el itinerario, e incluir más tramos, pero ¿creeís que esto mejoraría la estimación?.


La conclusión de este pequeño ejercicio: Una planificación detallada no mejora en nada nuestra capacidad de estimar.


Entonces ¿qué podemos hacer para mejorar nuestras estimaciones?… en el próximo post os lo cuento ;).

¿Cómo llamar a servicios web desde C/C++?

Como últimamente me han realizado esta pregunta en varias ocasiones la voy a contestar en este post, seguro que a alguien le vendrá bien la respuesta.

Los servicios web se han extendido espectacularmente en los últimos años. Ya casi no existe ninguna aplicación que no exponga un API de integración basada en una fachada de servicios web. Una de las grandes ventajas de los servicios web es que se pueden llamar desde cualquier lenguaje de programación ya que lo único necesario es ser capaz de contruir un mensaje SOAP (que no es más que una cadena de texto) y poder realizar un POST (que en esencia es enviar datos por un socket). Aunque desde cualquier lenguaje se pueden realizar estas tareas a menudo es necesario utilizar un proxy de lado cliente que nos abstraiga de la dificultades de componer el mensaje SOAP y realizar el POST.

En C++ contamos con numerosas posibilidades a la hora de llamar a servicios web, aunque ninguna es evidente a primera vista.

En Visual Studio, formando parte parte de ATL Server contamos con una herramienta poco conocida, llamada sproxy, que es capaz de generarnos el proxy necesario para invocar a un servicio web a partir del WSDL del servicio. Sproxy genera un archivo .h que contiene las clases necesarias para llamar al servicio web. Sproxy tiene dos pegas: es una solución que solo funciona con el compilador de Microsoft y que depende de MSXML (tendremos que distribuirlo con nuestra aplicación) y de las librerias de ATL (por lo tanto no se puede compilar en la versión Express de Visual C++).

Otra opción es utilizar el Microsoft SOAP Toolkit, que aun se puede descargar pero que no ya no tiene soporte por parte de Microsoft.  Salvo que se trate de mejorar una aplicación que ya usa este toolkit es casi mejor olvidar esta opción.

En Visual C++, contamos con la opción de utilizar C++/CLI y toda la potencia de las clases del framework de .Net para llamar a los servicios web. Dada la posibilidad de mezclar código manejado y código no manejado en aplicación este puede ser el camino más sencillo siempre que no nos importe exigir que el framework de .net esté instalado en la máquina cliente.  Es la aproximación más sencilla pues solo es necesario agregar una referencia al servicio web para que se genere el proxy manejado necesario para llamar al servicio. El proceso es exactamente el mismo que cuando llamamos a un servicio web desde C#.

Por último, existe otra opción que tiene como principales ventajas que es mutiplataforma y que funciona con cualquier compilador de C o C++. Se trata de gSOAP, un proyecto libre que proporciona las herramientas necesarias para generar el proxy que nos permita llamar a un servicio web desde casi cualquier plataforma usando C o C++. No es tan facil de usar como las anteriores opciones pero funciona muy bien. Si la necesidad de usar Linux, Windows, Windows CE o Mac como cliente del servico es clave, gSoap es la elección clara. En la web de la herramienta hay documentación más que suficiente sobre como utilizar esta herramienta. Yo la he usado recientemente con bastante exito para llamar a un servicio de integración que expone una aplicación en la que estado trabajando los ultimos años. Os dejo un poco de ese código, para que veaís que una vez generardo el proxy la llamada es más o menos sencilla:

/Incluimos los archivos generados por gSOAP

//http://www.cs.fsu.edu/~engelen/soap.html

//que nos generan las clases proxy necesaria para llamar a servicio web

//Para que los proxys compilen correctamente, es necesario incluir en la

//compilación los archivos soapC.cpp, soapClient.cpp y stdsoap2.cpp

//además de las cabeceras necesarias, generadas por gSOAP

#include «soapproxy/soapSaveDataSoapProxy.h»

#include «soapproxy/savedatasoap.nsmap»

 

//Url en la que se encuenta el servicio de Prisma 3

//Es necesario establecerla correctamente al servidor concreto

const char endpoint[] = http://servidorprisma/prismaweb/webservices/public/savedata.asmx;

 

//Función que pemite llamar al servicio de carga de datosde Prisma 3

string SaveTableData(string& user, string& company, string& tableDataNode)

{

    //Proxy SOAP generado con gSOAP

    SaveDataSoap p;

    p.endpoint = endpoint;

 

    //Clase del proxy que contiene los parametros del servicio

    _ns1__SaveTableData2 params;

 

    params.user = &user;

    params.company = &company;

    params.tableDataNode = &tableDataNode;

 

    //Clase del proxy que contiene la respuesta del servicio

    _ns1__SaveTableData2Response response;

 

    //Llamada al servicio

    p.__ns1__SaveTableData2(&params, &response);

 

    return *response.SaveTableData2Result;

}

Cabe reseñar que cuanto más simples y comunes sean los tipos que toman como parámetros los servicios web, más facil nos será llamarlos desde múltiples lenguajes o plataformas. Cualquier lenguaje sabe lo que es un string, pero solo los lenguajes .Net saben lo que es un Dataset.

Resaltar tambien que gSoap, permite generar el esqueleto de la parte servidora de un servicio web, algo muy util si necesitamos exponer servicios web desde C/C++. Pero de todos modos, en plataforma windows, para exponer servicios web desde Visual C++ es mucho más simple y potente usar ATL Server.

Da gusto ver que el C/C++ es como andar en bicicleta, una vez bien aprendido no se olvida y resulta útil cuando uno menos lo espera…