Routing Service: Novedades en WCF 4.0

Código del ejemplo en VS2010 Beta 1

En una entrada anterior, hicimos una introducción acerca de la creación de un sistema de Router o Forwarding en WCF 3.X, fíjese que comenzamos diciendo que podría ser algo habitual en arquitecturas orientadas a servicios, note el patrón “Intermediate Routing”.

La siguiente versión de WCF, conocida como WCF 4.0 de entre las novedades que trae consigo, algunas de las cuales ya las hemos visto aquí y aquí, puede encontrarse la implementación de un servicio de router ‘out of box’ capaz de resolvernos este patrón sin necesidad de implementar ni una sola línea de código y soportando muchos de los esquemas de mensajes habituales, Request Replay, Fire And Forget, Duplex.

A lo largo de esta entrada trataremos de implementar el mismo ejemplo que teníamos en la entrada anterior  con el nuevo sistema de routing. Empezemos por el principio, que consiste en definir el servicio que será alojado en distintos procesos HOST (presumiblemente en distintas máquinas) y que desamos balancear.

 

 

Una vez alojados estos servicios, cosa que no vamos a comentar y que damos por sabida, por ejemplo en las direcciones http://localhost:2020/GeekService/ y http://localhost:2030/GeekService/IGeekService llega la hora de hacer la implementación de nuestro router. El nuevo espacio de nombres System.ServiceModel.Routing pone a nuestra disposición un servicio por defecto denominado RoutingService el cual realizará por nosotros las tareas de enrutado sin necesidad de que tengamos tanto conocimiento de las cabeceras de los sobres SOAP To y Action como necesitábamos para la versión anterior.

Las siguientes líneas de código reflejan lo necesario para alojar nuestro servicio de Router, en nuestro ejemplo en una aplicación de consola.

 

 

A la hora de configurar este servicio de router nos tendremos que fijar en distintos aspectos. Por un lado el enlace que vamos a seleccionar, que por supuesto tendrá que ser perfectamente compatible con los de los servicios y cliente, es decir, si el cliente y los servicios se comunican con el enlace ws2007HttpBinding y con un determinado mecanismo de seguridad, el router tendrá que tener este mismo enlace con la configuración adecuada.

Con respecto al contrato que implementa este Routing Service, tendremos distintas opciones a elegir, dependiendo del tipo de esquema de mensajería que se utilice. Otra vez el espacio de nombres System.ServiceModel.Routing pone a nuestra disposición cinco posibles contratos de servicio para nuestro router para que seleccionemos el adecuado para nuestras necesidades:

· System.ServiceModel.Routing.IDuplexRouterCallback

· System.ServiceModel.Routing.IDuplexSessionRouter

· System.ServiceModel.Routing.IRequestReplyRouter

· System.ServiceModel.Routing.ISimplexDatagramRouter

· System.ServiceModel.Routing.ISimplexSessionRouter

Como en nuestro ejemplo, teníamos un sistema de envío y respuesta tradicional, seleccionaremos IRequestReplyRouter. En este punto ya tendremos la configuración de nuestro router como sigue:

 

 

Llegados a este momento solamente falta ver como especificamos los filtros que permitan indicarle al router como redirigir los mensajes entrantes hacia los distintos procesos de alojamiento de los servicios. Para ello, lo primero que haremos será agregar un nuevo comportamiento  llamado routing, mediante el cual podremos indicar la tabla de filtros de enrutado.

 

Nota: El comportamiento de routing permite indicar si solamente se realizaran filtros por las cabeceras ( filtersOnHeadersOnly ), algo muy rápido, o bien examinando el cuerpo de los mensajes.

Estamos ya en la parte más interesante del sistema, la parte con la que podemos especificar los filtros que nos permitan balancear las peticiones. En realidad, un filtro, para un router nos es nada más que una clase heradada de MessageFilter, por lo tanto la creación de filtros personalizados es relativamente sencillo. Por defecto, dentro de WCF 4.0 ya tendremos una serie de filtros OOB, los cuales nos permitirán balancear en función del Action, Address, Endpoint y/o un filtro XPath sobre el contenido del mensaje.

La sección de filtros para enrutado es un nueva subsección de <system.serviceModel> y que tendrá un aspecto similar a la siguiente, que pasaremos a desgranar seguidamente.

La primera parte de la nueva sección , namespaceTable, nos permite establecer prefijos para los distintos namespaces que pueda tener un sobre SOAP, como en nuestro caso, al igual que en la entrada para routers en WCF 3.X, establecíamos una cabecera con un determinado namespace, pasamos a establecer este nuevo prefijo. Por supuesto, algunos prefijos son por defecto ya conocidos como s12 para Soap 1.2 o s11 para Soap 1.1.

Una vez especificados los prefijos, llega la hora de establecer la lista de filtros que podrán usarse dentro de las tablas. En el caso de nuestra sección incluímos dos filtros basados en una consulta XPath sobre el mensaje. Cómo podrá observar el primer filtro se corresponderá con los mensajes que contengan una Header llamada Host y con valor HostA, el segundo filtro será equivalente pero en este caso para el valor HostB.

Nota: Por supuesto, dentro de esta colección se podrá hacer uso tanto de los filtros por defecto como de los filtros personalizados que deseemos crearnos.

Ya completadas las secciones de prefijos y filtros solamente falta la creación de las tablas, para nuestro ejemplo disponemos de una tabla con nombre ‘RoutingTableA’, previamente configurada en el comportamiento de servicio, la cual hace uso de los filtros con nombre FilterHostA y FilterHostB.

Ya está todo amigos, no ha hecho falta escribir una sola línea de código para completar el ejemplo, solamente configuración de un servicio que tenemos por defecto en WCF 4.

Ahora, solamente haría falta comunicarle al cliente la dirección del router para que la establezca como dirección de los servicios, sin preocuparnos de que el header Address no coincida, sin aplicar un ViaBehavior etc….

Espero que esta nueva entrada sobre las novedades de WCF os haya parecido interesante, si os interesa investigar o leer más sobre este tema a continuación os dejo algunos enlaces interesantes.

http://blogs.msdn.com/endpoint/archive/2009/05/07/the-road-to-4-wcf-changes-between-beta-1-and-ctp.aspx

http://blogs.thinktecture.com/cweyer/archive/2009/05/08/415335.aspx

http://www.aspnetpro.com/articles/2009/05/asp200905mb_f/asp200905mb_f.asp

 

Saludos

Unai Zorrilla

Routers en WCF 3.X

Codigo del ejemplo

Aunque suene en principio algo extraño, la posibilidad de crear un servicio de routers o forwarding es un elemento no tan raro dentro de nuestros desarrollos de aplicaciones distribuídas. Si bien, hoy tenemos sistemas de balanceo de carga por software, NLB, o por hardware como F5 por citar algunos, estos sistemas podrían no cubrir ciertas necesidades. Piense por ejemplo en enrutar o balancear determinados mensajes de servicio en función del valor de una cabecera o bien en función de la operación que se desea realizar. Como se imaginará, los sistemas citados anteriormente puede que no sean suficientes para estos propósitos.

El enrutamiento de mensajes ya era posible para los que tuvimos la suerte desgracia de trabajar con WSS 3.0, y por supuesto, es posible desde las primeras versiones de WCF, que a partir de ahora conoceremos o llamaremos como WCF 3.X.

A lo largo del siguiente post trataremos de ver un ejemplo de una posible implementación de un sistema de routers tal y como podemos hacer hoy en día con WCF 3.X, en otro post siguiente veremos cómo hacerlo en .NET 4.0 con su nuevo servicio dedicado explícitamente a esta funcionalidad, pero paso a paso..

NOTA: Dentro de la implementación de routers podrían distinguirse varios tipos además de implementaciones para resolver los distintos estilos de mensajes, simplex, dúplex, con sesión, seguridad etc.. para no complicar el tema trataremos de realizar un ejemplo simple sin pararnos a manejar temas como sesión o seguridad.

La primera pregunta que uno se hace, o por lo menos esta es la que yo me hice en su dia, cuando se pone a pensar en una posible implementación, es cómo manejar la cabecera Action, cabecera que todos los sobres SOAP contienen.

NOTA: La cabecera Action permite a la infraestructura o dispatcher de mensajes decidir que método del contrato de servicio se va a ejecutar. Si un contrato de servicio dispone de un método, llamemos A, cuando se invoca a esta operación el sobre SOAP contendrá una cabecera Action con un valor equivalente a la siguiente expresión: [Service Namespace]/[Contract Name]/OperationName, a lo menos que se especifique el atributo Action del decorador OperationContract. Por dar una nota explicativa más sobre este tema, para la implementación de REST en WCF precisamente uno de los cambios consistió en establecer la versión del sobre SOAP a None y cambiar el procesamiento de los mensajes para que la invocación del método del contrato de servicio no se basara en la cabecera Action sino en la Uri de invocación.

El porqué de esta pregunta es muy sencillo, si ponemos un intermediario entre el cliente y el servicio, esto es, básicamente un router o forwarder, un método del intermediario solamente sería llamado si su valor de Action se corresponde con la cabecera Action del sobre SOAP que se reciba. Por lo tanto, ¿Cómo hacemos para que un método de un intermediario pudiera procesar un sobre con un Action distinto al suyo?.

La respuesta a esta pregunta son ‘los métodos ociosos’, es decir, los métodos de contrato de servicio que tienen establecido su Action igual a *. Este valor de Action permite indicarle a la infraestructura que cuando llegue un mensaje al servicio cuyo valor de cabecera Action no se corresponda con ninguno de los elementos del contrato de servicio ejecute este método.

Llegados a este punto ya sabemos que podríamos tener por ejemplo un método ForwardMessage decorado con OperationContract y su valor de Action=* capaz de procesar cualquier mensaje que recibiera. Pero, los mensajes a procesar son distintos y variados, unos toman un parámetro de entrada otros varios, unos no devuelven resultados, otros un tipo simple y otros algún tipo complejo. En WCF 3.X siempre hemos tenido la posibilidad de trabajar de forma no tipada con nuestros servicios, asumiendo como un Message cualquier elemento de entrada y cualquier resultado de operación de servicio, aunque esta sea no devolver ningún resultado, por lo tanto, llegados ya ha este punto podríamos deducir que un contrato válido para un router de servicio podría ser algo similar a lo siguiente.

Con este sencillo contrato ya tenemos decidido cómo será nuestro intermediario o router, ahora, solamente queda ver cómo realizar una implementación. Para ello, empezaremos por definir el servicio que enrutaremos entre diferentes equipos, con el fin de no complicar el ejemplo con elementos innecesarios partiremos de un contrato de servicio tan simple como el siguiente:

 

Una vez decidido el contrato de nuestro servicio pasaremos a realizar una implementación del mismo y  alojarlo en diferentes procesos Host, que podrían estar en diferentes máquinas, para nuestro ejemplo tendremos estos servicios alojados en las siguientes direcciones http://localhost:2020/GeekService y http://localhost:2030/GeekService.

Fíjese como llegados hasta aquí puede surgir otra pregunta similar a la que nos hacíamos con respecto a la cabecera Action, pero esta vez con respecto a la cabecera To. WCF tiene para cada uno de los extremos, endpoints, asociadas dos direcciones, una lógica y otra física, cuyo sentido es el mismo que en WSS con los valores de las cabeceras To y Via. To se corresponde con la dirección lógica a la que se envían los mensajes miestras que Via se corresponde con la dirección física de red dónde se está realizando la escucha de los mismos. En WCF 3.X la dirección lógica se establece con el parámetro Address y la dirección física con ListenUri, aunque por regla general solamente se suele establecer el valor de Address la dirección física se infiere automáticamente de esta.

Si en nuestro ejemplo anterior configuramos nuestro servicio GeekService con dos direcciones distintas, los valores que la cabecera To tendrá en los mensajes a uno y otro serán diferentes, y por lo tanto los mensajes se podrán rechazar si llegan a un servicio de escucha con un valor de dirección lógica distinto al que el mensaje contiene en sus cabeceras. ¿Cómo resolvemos ahora este problema?

 

NOTA: Puede obtener más información acerca del direccionamiento de WCF en este gran artículo de Aaron Skonnard.

En realidad, tenemos varias alternativas, aunque todas ellas pasan de una forma u otra por configurar los valores de ListenUri o establecer un behavior de tipo clientVia que como puede imaginarse se corresponde con el valor de Via o dirección física a las que nos comuniquemos.

Para nuestro ejemplo, creamos dos proyectos de host a los cuales agregamos la referencia a nuestro contrato de servicio y los configuramos tal y como se muestra a continuación:

Fíjese como, para la configuración anterior hemos establecido su dirección lógica a urn:GeekService y hemos establecido su dirección física en http://localhost:2030/GeekService, para el otro host hacemos una configuración similar, aunque como indicamos antes, escuchando en la dirección física http://localhost/2020/GeekService.

Realizaremos ahora la implementación de nuestro router, tal y como se ve en el siguiente fragmento:

 

 

En esta implementación, lo único que se realiza es la verificación del mensaje para obtener una cabecer que nos permita enrutar las llamadas de servicio. Una vez obtenida esa cabecera creamos un cliente apropiado del host al que nos dirigimos, por supuesto, esto forma parte de la configuración de nuestro router y la podemos ver en la siguiente figura:

 

 

Preste atención a la configuración de los clientes del router y como a estos se les ha agregado el comportamiento clientVia, esto es necesario para que el valor de la cabecera To en respuesta a las peticiones del cliente no se viera modificada.

Ahora, ya solamente nos queda realizar la implementación del cliente, para ello simplemente agregamos el siguiente código de llamada, dónde además se agrega una cabecera personalizada, la que necesita el router para enrutar.

NOTA: El envío de esta cabecera se podría realizar automáticamente mediante la implementación de IClientMessageInspector, pero, esto formaría parte de otra entrada.

 

La configuración del cliente, es similar a la del router, y la podemos ver a continuación:

 

 

Por supuesto, este es un ejemplo muy sencillo, y no es la única forma de realizar esta tarea, pero espero que os anime a saber más sobre las posibilidades de enrutado en WCF y las posibilidades que se abren para hacer sistemas de balanceo en función de los parametros de los mensajes o cualquier otro elemento que podamos programar. En un futuro post, no demasiado lejano, veremos cómo esta tarea, que lleva una buena cantidad de código se simplifica con ciertas novedades introducidas en WCF 4.0.

WS-Discovery (II):Novedades en WCF 4.0

En el post anterior empezamos a ver la especificación de WS-Discovery por medio de los mensajes PROBE y PROBE MATCH. A lo largo de esta entrada trataremos de ver los anunciós HELLO y BYE y como hacer que nuestros servicios los realicen de forma automática cada vez que los mismos se abren o cierran los canales de escucha.

Si para el descubrimiento de los servicios a los extremos de nuestro servicio agregábamos un elemento de tipo udpDiscoveryEndpoint para facilitar su descubrimiento junto al comportamiento discoveryBehavior. Para los anuncios solamente tendremos que incluir un nuevo extremo, udpAnnouncementEndpoint, esta vez, dentro de la sección de anuncios del comportamiento discoveryBehavior tal y como se muestra a continuación.

 

Con este sencillo paso, automáticamente nuestro servicio es capaz de realizar los envíos de los anuncios. La pregunta que os estaréis haciendo ahora es ¿Cómo se subscribe uno a estos anuncios? En definitiva, estos anuncios no son más que mensajes enviados a una dirección de multicast, ya comentada en el artículo anterior, que un determinado servicios puede estar escuchando. La implementación de este servicio de escucha es muy sencilla basándose en una clase ya incluída en WCF 4.0 que se llama AnnouncementService y cuyo ejemplo de uso podemos ver a continuación.

 

WS-Discovery (I): Novedades en WCF 4.0

NOTA: En esta entrada solamente se tratará el modo AdHoc, es decir, basándose en descubrimiento de servicios bajo una misma subred. Se deja para otros futuros post la creación de un proxy de descubrimiento.

WS-Discovery es una de las ‘pocas’ novedades que trae WCF 4.0, es decir, la versión de Windows Communication Foundation en Visual Studio 2010. Aunque es ya un estandard de OASIS  que tiene su tiempo, hasta esta nueva versión no teníamos implementación directa.

Antes de empezar a mostrar ejemplos sobre su uso en Visual Studio 2010 haremos un breve repaso del porqué y cómo funciona WS-Discovery. La idea es muy simple, y se basa en el descubrimiento automático de extremos de servicio dentro de una red, algo muy demandado y necesario en Bus de Servicios Empresariales ( ESB ), dónde el direccionamiento puede modificarse con relativa frecuencia. La especificación de este estándar es relativamente pequeña y fácil de entender tal y como se puede ver resumida en los siguientes puntos.

· Cuando un servicio se une a una red, es decir, cuando se pone a escuchar en una determinada dirección este envía un anuncio denominado HELLO para notificar su entrada a la misma.

· Cuando un servicio se quita de una red, es decir, cuando se cierra y deja de escuchar en una dirección este envía un anuncio denominado BYE para notificar su salida.

· Un cliente puede descubrir servicios existentes en un red bajo un criterio determinado enviando un mensaje denominado PROBE, si existe algún servicio este responde con otro mensaje denominado PROBE MATCH el cual contiene la información de su ServiceEndPoint.

· Un cliente puede descubrir servicios existentes en la red mediante su nombre, para ello puede enviar un mensaje denominado RESOLVE, si el servicio está en la red responde con otro mensaje denominado RESOLVE MATCH con la información del mismo.

Sencillo verdad? Le suena esté mecanismo? En realidad es el mismo que Windows utiliza para el descubrimiento de dispositivos hardware puesto que también se basa en una implementación de WS Discovery.

Sobre este pequeño resumen de especificación caben unas cuantas preguntas, la primera es ¿dónde se envían estos mensajes y/o anuncios? La respuesta es mediante multicast a una dirección determinada, que por regla general y en el caso de WCF 4.0 es soap.udp://239.255.255.250:3702, valores que podremos modificar dentro de la nueva sección de configuración “Standard Endpoints” que WCF Configuration Editor pone a nuestra disposición. Al final, como podrá comprobar, WS-Discovery no es más que la implementación de un determinado contrato de servicio, que en caso de WCF 4.0 se corresponde con la versión de Abril 2005 y que tiene la siguiente firma.

Ahora que más o menos entendemos WS-Discovery, veámos como configurar un servicio para que acepte la petición Probe y por lo tanto pueda ser descubierto por los clientes del mismo ( entendiéndose como clientes también a otros servicios).

El primer paso es agregar a nuestros servicios un Endpoint especial, llamado updDiscoveryEndpoint, de forma similar a como agregamos extremos de servicio hasta la fecha.

 

NOTA: El atributo Kind es nuevo dentro de la configuración de Windows Communication Foundation y nos permite especificar que este extremo se corresponde con alguno de los extremos ‘estándar’ que tenemos configurados. Algunos ejemplos adicionales de estos extremos son timerServiceNotificationExpiredEndpoint,timerServiceEndpoint o announcementEndpoint.

Una vez que hemos agregado nuestro nuevo extremo al servicio solamente tendremos que agregar al mismo un nuevo comportamiento, denominado ServiceDiscovery.

 

Con estos dos sencillos pasos nuestro servicio ya será capaz de aceptar mensajes de tipo PROBE y responder con la información de los extremos en los que está escuchando.

NOTA: El documento de especificación de WS-Discovery contiene una completa documentación acerca de los formatos de mensajes PROBE y PROBE MATCH que le recomiendo revisar.

 

Llegados a este momento en el que ya tenemos configurado nuestro servicio para aceptar peticiones PROBE veremos cómo obtener y/o descubrir la información de los mismos de forma AdHoc. Para ello nos serviremos de la clase DiscoveryClient situada en el espacio de nombres System.ServiceModel.Discovery, tal y como vemos en el siguiente ejemplo de código:

 

 

 

Fíjese que en la especificación de la clase FindCriteria podemos establecer el contrato que los servicios que queremos buscar tienen que cumplir. Como elementos adicionales, FindCriteria también nos permite establecer un máximo de duración en la búsqueda o los Scopes de servicio que se tienen que cumplir.