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.

Un comentario sobre “Routers en WCF 3.X”

  1. Hola, en los dos archivos de configuración de este ejemplo la direccion del listenUri apunta a «http://localhost:2030/GeekService», creo entender que uno de los host debe escuchar en 2030 y el otro en 2020.
    Saludos,

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *