Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Descubra un nuevo WCF con Discovery
Juval Lowy
Descargar el ejemplo de código
Todas las llamadas de Windows Communication Foundation (WCF) que son posibles con Microsoft .NET Framework 3.5 comparten dos restricciones. Primero, el puerto o canal asignado al servicio debe estar disponible. El administrador o el desarrollador de la aplicación literalmente tienen que adivinar o tener alguna forma de reservarlos. Segundo, el cliente debe conocer a priori la dirección de los extremos del servicio, tanto el número de puerto como el equipo de servicio, o el nombre del canal.
Sería ideal si el servicio pudiera usar cualquier dirección disponible. A su vez, el cliente necesitaría descubrir esa dirección en tiempo de ejecución. De hecho, existe una solución basada en estándares de la industria que estipula cómo se realiza esa detección. Esa solución, denominada simplemente detección (y sus mecanismos compatibles), es el tema de esta columna. También presenta varias herramientas útiles y clases auxiliares. El código fuente para estas está disponible en el sitio web code.msdn.
Detección de la dirección
La detección depende del Protocolo de datagramas de usuario (UDP). A diferencia del Protocolo de control de transmisión (TCP), UDP es un protocolo sin conexión, y no se requiere una conexión directa entre el remitente y el receptor de los paquetes. El cliente usa UDP para difundir solicitudes de detección de cualquier extremo que admita un tipo de contrato especificado. Estas solicitudes las reciben extremos de detección dedicados que los servicios admiten. La implementación del extremo de detección se responde al cliente con la dirección de los extremos del servicio que admiten el contrato especificado. Una vez que el cliente descubre los servicios, continúa invocándolos como llamadas WCF regulares. Esta secuencia se muestra en la figura 1.
Figura 1 Detección de dirección a través de UDP
De manera muy similar al extremo de intercambio de metadatos (MEX), WCF ofrece un extremo de detección estándar con el tipo UdpDiscoveryEndpoint:
public class DiscoveryEndpoint : ServiceEndpoint
{...}
public class UdpDiscoveryEndpoint : DiscoveryEndpoint
{...}
El servicio puede hacer que el host implemente ese extremo agregando ServiceDiscoveryBehavior a las colecciones de comportamientos que admite el servicio. Puede hacerlo de manera programática así:
ServiceHost host = new ServiceHost(...);
host.AddServiceEndpoint(new UdpDiscoveryEndpoint());
ServiceDiscoveryBehavior discovery = new ServiceDiscoveryBehavior();
host.Description.Behaviors.Add(discovery);
host.Open();
La figura 2 muestra cómo agregar el extremo de detección y el comportamiento de detección con el archivo de configuración del servicio.
Figura 2 Adición de extremo de detección en la configuración
<services>
<service name = "MyService">
<endpoint
kind = "udpDiscoveryEndpoint"
/>
...
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceDiscovery/>
</behavior>
</serviceBehaviors>
</behaviors>
Direcciones dinámicas
La detección es independiente de la manera exacta en que el host de servicio define sus extremos. Sin embargo, ¿qué pasa si se espera que el cliente use la detección para encontrar la dirección de servicio? En ese caso, el servicio tiene la libertad de configurar sus direcciones de extremos sobre la marcha, de manera dinámica, basándose en cualquier puerto o canal disponible.
Para automatizar el uso de direcciones dinámicas, escribí la clase auxiliar estática DiscoveryHelper con las dos propiedades AvailableIpcBaseAddress y AvailableTcpBaseAddress:
public static class DiscoveryHelper
{
public static Uri AvailableIpcBaseAddress
{get;}
public static Uri AvailableTcpBaseAddress
{get;}
}
Implementar AvailableIpcBaseAddress es sencillo, porque cualquier canal con nombre único lo hará, la propiedad usa un nuevo identificador global único (GUID) para nombrar el canal. La implementación de AvailableTcpBaseAddress se realiza al encontrar un puerto TCP disponible a través de la apertura del puerto cero.
La figura 3 muestra cómo usar AvailableTcpBaseAddress.
Figura 3 Uso de direcciones dinámicas
Uri baseAddress = DiscoveryHelper.AvailableTcpBaseAddress;
ServiceHost host = new ServiceHost(typeof(MyService),baseAddress);
host.AddDefaultEndpoints();
host.Open();
<service name = "MyService">
<endpoint
kind = "udpDiscoveryEndpoint"
/>
</service>
<serviceBehaviors>
<behavior>
<serviceDiscovery/>
</behavior>
</serviceBehaviors>
Si todo lo que desea es la dirección base dinámica para su servicio, el código de la figura 3 no es perfecto, porque sigue requiriendo que agregue la detección, ya sea en el archivo de configuración o de manera programática. Puede simplificar estos pasos con mi extensión de host EnableDiscovery, que se define así:
public static class DiscoveryHelper
{
public static void EnableDiscovery(this ServiceHost host,bool enableMEX = true);
}
Cuando se usa EnableDiscovery, no hay necesidad de pasos programáticos o de un archivo de configuración:
Uri baseAddress = DiscoveryHelper.AvailableTcpBaseAddress;
ServiceHost host = new ServiceHost(typeof(MyService),baseAddress);
host.EnableDiscovery();
host.Open();
Si el host todavía no tiene definidos extremos para el servicio, EnableDiscovery agregará los extremos predeterminados. EnableDiscovery también establecerá de manera predeterminada agregar al servicio el extremo de MEX en sus direcciones base.
Pasos del lado cliente
El cliente usa la clase DiscoveryClient para detectar todas las direcciones de extremo de todos los servicios que admiten un contrato especificado:
public sealed class DiscoveryClient : ICommunicationObject
{
public DiscoveryClient();
public DiscoveryClient(string endpointName);
public DiscoveryClient(DiscoveryEndpoint discoveryEndpoint);
public FindResponse Find(FindCriteria criteria);
//More members
}
Lógicamente, DiscoveryCliente es un proxy al extremo de detección. Como todos los proxy, el cliente debe brindar al constructor del proxy la información acerca del extremo de destino. El cliente puede usar un archivo de configuración para especificar el extremo o de brindar programáticamente el extremo de detección de UDP estándar para ese fin, debido a que no se requieren detalles adicionales (como dirección o enlace). El cliente luego llama al método Find y le proporciona el tipo de contrato que se va a detectar a través de una instancia de FindCriteria:
public class FindCriteria
{
public FindCriteria(Type contractType);
//More members
}
Find devuelve una instancia de FindResponse, que contiene una colección de todos los extremos detectados:
public class FindResponse
{
public Collection<EndpointDiscoveryMetadata> Endpoints
{get;}
//More members
}
Cada extremo es representado por la clase EndpointDiscoveryMetadata:
public class EndpointDiscoveryMetadata
{
public EndpointAddress Address
{get;set;}
//More members
}
La principal propiedad de EndpointDiscoveryMetadata es Address, que finalmente contiene la dirección de extremo detectada. La figura 4 muestra cómo un cliente puede usar estos tipos en conjunto para detectar la dirección del extremo e invocar el servicio.
Figura 4 Detección e invocación de un extremo
DiscoveryClient discoveryClient =
new DiscoveryClient(new UdpDiscoveryEndpoint());
FindCriteria criteria = new FindCriteria(typeof(IMyContract));
FindResponse discovered = discoveryClient.Find(criteria);
discoveryClient.Close();
//Just grab the first found
EndpointAddress address = discovered.Endpoints[0].Address;
Binding binding = new NetTcpBinding();
IMyContract proxy =
ChannelFactory<IMyContract>.CreateChannel(binding,address);
proxy.MyMethod();
(proxy as ICommunicationObject).Close();
Existen varios problemas importantes con la figura 4.
A pesar de que el cliente puede detectar varios extremos que admiten el contrato deseado, no hay una lógica para resolver cuál invocar. Simplemente invoca el primero en la colección devuelta.
La detección sólo está dirigida a las direcciones. No hay información acerca del enlace que se usará para invocar el servicio. La figura 4 simplemente codifica el uso del enlace TCP. El cliente tendrá que repetir estos pasos una y otra vez cada vez que necesite detectar la dirección de servicio.
La detección lleva tiempo. De manera predeterminada, Find esperará 20 segundos a que los servicios respondan a la solicitud de detección de UDP. Dicho retardo hace que sea inadecuado usar la detección en muchas aplicaciones, ciertamente, en que la aplicación realiza un gran volumen de llamadas. A pesar de que puede acortar ese tiempo de espera, si lo hace, corre el riesgo de no detectar alguno de los servicios, o bien ninguno. DiscoveryClient sí ofrece una detección asincrónica, pero no es útil para un cliente que necesita invocar el servicio antes de continuar con su ejecución.
En esta columna verá varios enfoques para abordar estos problemas.
Ámbitos
El uso de la detección implica una relación algo flexible entre el cliente y el servicio o servicios que detecta. Esto presenta otro conjunto de problemas: ¿cómo puede saber el cliente que ha detectado el extremo correcto? Cuando se detectan varios extremos compatibles, ¿cuál debe invocar el cliente?
Claramente se necesita algún mecanismo que ayude al cliente a filtrar los resultados de la detección. Esto es exactamente para lo que son los ámbitos. Un ámbito es sólo una dirección URL válida asociada con el extremo. El servicio puede asociar un ámbito, o incluso varios ámbitos, con cada uno de sus extremos. Los ámbitos están conectados junto con las direcciones en la respuesta a la solicitud de detección. A su vez, el cliente puede filtrar las direcciones detectadas basándose en los ámbitos encontrados o, mejor aún, puede intentar encontrar sólo ámbitos relevantes en primer lugar.
Los ámbitos son inmensamente útiles para personalizar la detección y agregar comportamiento sofisticado a su aplicación, en especial cuando se escriben herramientas de administración o un marco. El uso clásico de los ámbitos es para permitir que el cliente distinga entre los servicios polimórficos desde diferentes aplicaciones. Sin embargo, esto no ocurre con frecuencia. Pienso que los ámbitos son útiles cuando se trata de distinguir entre tipos de extremos en la misma aplicación.
Por ejemplo, suponga que tiene varias implementaciones para un contrato determinado. Cuenta con el modo operativo usado en el entorno de producción y el modo de simulación usado en pruebas y diagnósticos. Con los ámbitos, el cliente puede elegir el tipo de implementación correcta necesario, y distintos clientes nunca entran en conflicto entre sí al consumir los servicios de los demás. También puede hacer que el cliente seleccione un extremo distinto basándose en el contexto de la invocación. Podría tener extremos para generar perfiles, depurar, realizar diagnósticos, pruebas, instrumentación, etc.
El host asigna ámbitos por cada extremo usando la clase EndpointDiscoveryBehavior. Por ejemplo, para aplicar en todos los extremos, use un comportamiento de extremo predeterminado:
<endpointBehaviors>
<behavior>
<endpointDiscovery>
<scopes>
<add scope = "net.tcp://MyApplication"/>
</scopes>
</endpointDiscovery>
</behavior>
</endpointBehaviors>
Aplica los ámbitos en forma discreta, basándose en el tipo de servicio, asignando los comportamientos explícitamente por cada extremo, tal como se muestra en la figura 5.
Figura 5 Asignación de comportamiento explícita
<service name = "MySimulator">
<endpoint behaviorConfiguration = "SimulationScope"
...
/>
...
</service>
...
<behavior name = "SimulationScope">
<endpointDiscovery>
<scopes>
<add scope = "net.tcp://Simulation"/>
</scopes>
</endpointDiscovery>
</behavior>
Un comportamiento de detección puede enumerar varios ámbitos:
<endpointDiscovery>
<scopes>
<add scope = "net.tcp://MyScope1"/>
<add scope = "net.tcp://MyScope2"/>
</scopes>
</endpointDiscovery>
Si un extremo tiene asociados varios ámbitos, cuando el cliente trate de detectar el extremo basado en el ámbito coincidente, el cliente necesita que coincida al menos uno de los ámbitos, pero no todos.
El cliente tiene dos formas de usar los ámbitos. La primera es para agregar el ámbito a los criterios de búsqueda:
public class FindCriteria
{
public Collection<Uri> Scopes
{get;}
//More members
}
Ahora el método Find sólo devolverá extremos compatibles que también enumeren ese ámbito. Si el cliente agrega varios ámbitos, Find devolverá sólo extremos que sean compatibles con todos los ámbitos enumerados. Observe que el extremo puede ser compatible con ámbitos adicionales no proporcionados a Find.
La segunda forma de usar ámbitos es para examinar los ámbitos devueltos en FindResponse:
public class EndpointDiscoveryMetadata
{
public Collection<Uri> Scopes
{get;}
//More members
}
Estos ámbitos son todos los ámbitos que admite el extremo, y son útiles para realizar filtrado adicional.
Cardinalidad de la detección
Cada vez que se base en la detección, el cliente debe tratar con lo que llamo cardinalidad de la detección, que son la cantidad de extremos que se detectan y los que se invocan, si hubiese. Hay varios casos de cardinalidad:
- No se detecta ningún extremo. En este caso, el cliente necesita tratar con la ausencia del servicio. No es diferente a ningún cliente WCF cuyo servicio no esté disponible.
- Se detectó exactamente un extremo compatible. Este es el caso más común: el cliente simplemente continúa para invocar el servicio.
- Varios extremos detectados. Aquí el cliente, en teoría, tiene dos opciones. La primera es invocarlos todos. Este es el caso con un editor que activa un evento en los suscriptores, como se analiza más adelante, y es un escenario válido. La segunda opción es invocar algunos (incluso sólo uno), pero no todos los extremos descubiertos). Pienso que ese escenario es discutible. Cualquier intento de colocar una lógica en el cliente que resuelva el extremo que se va a invocar crea demasiado acoplamiento en todo el sistema. Anula la sola noción de detección en tiempo de ejecución, a saber, que cualquier extremo detectado hará. Si es posible detectar extremos no deseados, entonces usar la detección es una opción de diseño no adecuado, y debe en cambio proporcionar direcciones estáticas al cliente.
Si el cliente espera detectar exactamente un extremo (cardinalidad de uno), entonces debe indicar a Find que devuelva ese extremo tan pronto como lo encuentre. Hacerlo reducirá considerablemente la latencia de la detección y hará que sea adecuada para la mayoría de los casos.
El cliente puede configurar la cardinalidad usando la propiedad MaxResults de FindCriteria:
public class FindCriteria
{
public int MaxResults
{get;set;}
//More members
}
FindCriteria criteria = new FindCriteria(typeof(IMyContract));
criteria.MaxResults = 1;
Puede simplificar el caso de la cardinalidad de uno usando mi método auxiliar DiscoveryHelper.DiscoverAddress<T>:
public static class DiscoveryHelper
{
public static EndpointAddress DiscoverAddress<T>(Uri scope = null);
//More members
}
Con DiscoverAddress<T>, la figura 4 se ve reducida a:
EndpointAddress address = DiscoveryHelper.DiscoverAddress<IMyContract>();
Binding binding = new NetTcpBinding();
IMyContract proxy = ChannelFactory<IMyContract>.CreateChannel(binding,address);
proxy.MyMethod();
(proxy as ICommunicationObject).Close();
Simplificación de la detección
Hasta ahora, el cliente ha tenido que codificar el enlace que se va a usar. Sin embargo, si el servicio es compatible con un extremo de MEX, el cliente puede detectar la dirección del extremo de MEX, luego continuar para recuperar y procesar los metadatos para obtener el enlace que se va a usar, junto con la dirección de extremo. Para ayudar en la detección del extremo de MEX, la clase FindCriteria ofrece el método estático CreateMetadataExchangeEndpointCriteria:
public class FindCriteria
{
public static FindCriteria CreateMetadataExchangeEndpointCriteria();
//More members
}
Para simplificar esta secuencia, use mi método DiscoveryFactory.CreateChannel<T>:
public static class DiscoveryFactory
{
public static T CreateChannel<T>(Uri scope = null);
//More members
}
Con DiscoverAddress<T>, la figura 4 se ve reducida a:
IMyContract proxy = DiscoveryFactory.CreateChannel<IMyContract>();
proxy.MyMethod();
(proxy as ICommunicationObject).Close();
CreateChannel<T> supone la cardinalidad de uno con el extremo de MEX (es decir, sólo se encontró un extremo de MEX detectable en la red local) y que los metadatos contienen exactamente un extremo cuyo contrato es del parámetro de tipo especificado T.
Observe que CreateChannel<T> usa el método de MEX para el enlace de extremo y la dirección. Se espera que el servicio sea compatible con un extremo de MEX y con un extremo de detección (a pesar de que el cliente nunca usa el extremo de detección para encontrar el extremo real).
En caso de que haya varios servicios que sean compatibles con el contrato de servicio deseado, o que haya varios extremos de MEX, DiscoveryFactory también ofrece el método CreateChannels<T>:
public static class DiscoveryHelper
{
public static T[] CreateChannels<T>(bool inferBinding = true);
//More members
}
CreateChannels<T> inferirá de manera predeterminada en el enlace que se va a usar desde el esquema del extremo de servicio. Si inferBinding es falso, detectará el enlace desde los extremos de MEX.
CreateChannels<T> no supone una cardinalidad de uno en los extremos de servicio compatibles o en los extremos de MEX, y devolverá una matriz de todos los extremos compatibles.
Anuncios
El mecanismo de detección, tal como se ha presentado hasta ahora, es pasivo desde la perspectiva del servicio. El cliente consulta el extremo de detección y el servicio responde. Como alternativa a esta detección pasiva de la dirección, WCF ofrece un modelo activo, donde el servicio difunde su estado a todos los clientes y brinda su dirección. El host de servicio difunde un anuncio de “bienvenida” cuando se abre el host y un anuncio de “despedida” cuando se apaga de manera correcta. Si se cancela el host de manera incorrecta, no se envía ningún anuncio de “despedida”. Estos anuncios se reciben en un extremo especial para anuncios que hospeda el cliente (consulte la figura 6).
Figura 6 La arquitectura de anuncios
Los anuncios son un mecanismo individual a nivel de extremo, no uno a nivel de host. El host puede elegir el extremo para anunciar. Cada anuncio contiene la dirección del extremo, sus ámbitos y su contrato.
Observer que los anuncios no están relacionados con la detección de la dirección. Es posible que el host no sea compatible con un extremo de detección, y no se necesita el comportamiento de detección. Por otro lado, el host puede elegir ser compatible con el extremo de detección y anunciar sus extremos, tal como se muestra en la figura 6.
El host puede anunciar automáticamente sus extremos. Todo lo que debe hacer es proporcionar la información acerca del extremo de anuncios del cliente para el comportamiento de detección. Por ejemplo, cuando se usa un archivo de configuración:
<behavior>
<serviceDiscovery>
<announcementEndpoints>
<endpoint
kind = "udpAnnouncementEndpoint"
/>
</announcementEndpoints>
</serviceDiscovery>
</behavior>
Mi método de extensión EnableDiscovery también agrega el extremo de anuncios al comportamiento de detección.
Para que el cliente la use, WCF proporciona una implementación predefinida de un extremo de anuncios con la clase AnnouncementService, tal como aparecen en la figura 7.
Figure 7 Implementación WCF de un extremo de anuncios
public class AnnouncementEventArgs : EventArgs
{
public EndpointDiscoveryMetadata EndpointDiscoveryMetadata
{get;}
//More members
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class AnnouncementService : ...
{
public event EventHandler<AnnouncementEventArgs> OfflineAnnouncementReceived;
public event EventHandler<AnnouncementEventArgs> OnlineAnnouncementReceived;
//More members
}
AnnouncementService es un singleton configurado para el acceso simultáneo. AnnouncementService brinda dos delegados de eventos a los que se puede suscribir el cliente para recibir los anuncios. El cliente debe alojar el AnnouncementService usando el constructor de ServiceHost, que acepta una instancia de singleton. Esto se requiere para que el cliente pueda interactuar con la instancia y suscribirse a los eventos. Además, el cliente debe agregar el extremo de anuncios UDP al host:
AnnouncementService announcementService = new AnnouncementService();
announcementService.OnlineAnnouncementReceived += OnHello;
announcementService.OfflineAnnouncementReceived += OnBye;
ServiceHost host = new ServiceHost(announcementService);
host.AddServiceEndpoint(new UdpAnnouncementEndpoint());
host.Open();
void OnHello(object sender,AnnouncementEventArgs args)
{...}
void OnBye(object sender,AnnouncementEventArgs args)
{...}
Hay un detalle importante que se relaciona con recibir anuncios. El cliente recibiría todas las notificaciones de todos los servicios en la intranet, independientemente del tipo de contrato o también para aplicaciones o ámbitos. El cliente debe filtrar los anuncios importantes.
Simplificación de los anuncios
Puede simplificar y mejorar considerablemente los pasos sin procesar requeridos del cliente para usar anuncios con mi clase AnnouncementSink<T> definida de la siguiente manera:
public class AnnouncementSink<T> : AddressesContainer<T> where T: class
{
public event Action<T> OnHelloEvent;
public event Action<T> OnByeEvent;
}
AnnouncementSink<T> automatiza el hospedaje del extremo de anuncios al encapsular los pasos de la figura 7. A medida que AnnouncementSink<T> hospeda internamente una instancia de AnnouncementService, mejora sus deficiencias. Primero, AnnouncementSink<T> ofrece dos delegados de eventos para notificaciones. A diferencia del AnnouncementService sin procesar, AnnouncementSink<T> activa estos delegados de manera simultánea. Además, AnnouncementSink<T> desactiva la afinidad de contexto de sincronización de AnnouncementService, para que pueda aceptar los anuncios sobre cualquier subproceso entrante, lo que lo hace realmente simultáneo.
AnnouncementSink<T> filtra los tipos de contrato y sólo activa sus eventos cuando los extremos compatibles se anuncian a sí mismos. Lo único que necesita hacer el cliente es abrir y cerrar AnnouncementSink<T>, para indicar cuándo empezar a recibir notificaciones y cuando detenerse.
AnnouncementSink<T> deriva mi contenedor de direcciones de uso general llamado AddressesContainer<T>.
AddressesContainer<T> es una colección auxiliar de administración de direcciones enriquecida que puede usar cada vez que necesita manipular varias direcciones. AddressesContainer<T> es compatible con varios iteradores, indizadores, métodos de conversión y consultas.
La figura 8 demuestra el uso de AnnouncementSink<T>.
Figura 8 Uso de AnnouncementSink<T>
class MyClient : IDisposable
{
AnnouncementSink<IMyContract> m_AnnouncementSink;
public MyClient()
{
m_AnnouncementSink = new AnnouncementSink<IMyContract>();
m_AnnouncementSink.OnHelloEvent += OnHello;
m_AnnouncementSink.Open();
}
void Dispose()
{
m_AnnouncementSink.Close();
}
void OnHello(string address)
{
EndpointAddress endpointAddress = new EndpointAddress(address);
IMyContract proxy = ChannelFactory<IMyContract>.CreateChannel(
new NetTcpBinding(),endpointAddress);
proxy.MyMethod();
(proxy as ICommunicationObject).Close();
}
}
El explorador de MEX
En mi libro Programming WCF Services Second Edition (O’Reilly, 2008), presenté una herramienta a la que llamo el explorador de MEX (consulte la figura 9). Puede brindar una dirección de MEX al explorador de MEX y usarla para reflejar los extremos de servicio (su dirección, propiedades de enlace y contrato). La introducción de detección me permitió modernizar el explorador de MEX.
Figura 9 El explorador de MEX
Un clic en el botón Detectar desencadena una solicitud de detección para todos los extremos de MEX sin límite de cardinalidad. La herramienta entonces visualiza todos los extremos detectados en el árbol. Además, el explorador de MEX utiliza los anuncios de extremos de MEX. Para responder a los anuncios, el explorador de MEX se actualiza y presenta los nuevos extremos o quita del árbol los que ya no están en ejecución.
Patrón de publicación-suscripción controlado por la detección
En el artículo de octubre de 2006, Todo lo que necesita saber acerca de llamadas unidireccionales, devoluciones de llamadas y eventos, presenté mi marco para admitir un patrón de publicación-suscripción en WCF. Puede usar los mecanismos de detección y los anuncios para brindar incluso otra forma de implementar un sistema de publicación-suscripción.
A diferencia de las técnicas que aparecen en ese artículo, una solución basada en detección es el único caso de publicación-suscripción que no requiere pasos específicos por parte de los suscriptores o el administrador. Cuando se utiliza la detección, no es necesario suscribirse explícitamente en código o en configuración. A su vez, esto simplifica enormemente la implementación del sistema, y permite obtener una gran flexibilidad en la presencia tanto de editores como de suscriptores. Puede agregar o quitar suscriptores y editores fácilmente sin ningún paso adicional de administración o programación.
Cuando se usa una detección para un sistema de publicación-suscripción, los suscriptores pueden brindar un extremo de detección para que el servicio de publicación-suscripción pueda detectarlos, o pueden anunciar sus extremos de control de eventos, o pueden realizar ambos.
Los editores no deben detectar directamente a los suscriptores, porque pueden disminuir la latencia de detección en la activación de cada evento (teniendo la cardinalidad de todos los extremos). En lugar de eso, los editores deben detectar el servicio de publicación-suscripción, que implica un costo mínimo único. El servicio de publicación-detección debe ser un singleton (lo que permite una detección rápida, porque tiene cardinalidad de uno). El servicio de publicación-suscripción expone el mismo extremo de eventos que los suscriptores, por lo que a ojos de los editores parece ser un meta-suscriptor. Esto significa que requiere el mismo código para activar el evento en el servicio de publicación-suscripción que frente a un suscriptor real.
El extremo de eventos del servicio de publicación-suscripción debe usar un ámbito en particular. Este ámbito permite que los editores encuentren el servicio de publicación-suscripción en lugar de los suscriptores. Además de ser compatible con la detección que extremos de eventos dentro del ámbito, el servicio de publicación-suscripción brinda un extremo de anuncios.
El servicio de publicación-suscripción mantiene una lista de todos los suscriptores. El servicio de publicación-suscripción puede hacer que esa lista siempre esté al día al tratar constantemente de detectar los suscriptores usando alguna actividad en proceso en segundo plano. Observe nuevamente que tener el extremo de eventos del servicio de publicación-suscripción asociado con un ámbito especial también evitará que el servicio de publicación-suscripción se detecte a sí mismo cuando detecte todos los extremos de eventos. El servicio de publicación-suscripción también puede proporcionar un extremo de anuncios para supervisar a los suscriptores. La figura 10 ilustra esta arquitectura.
Figura 10 Sistema de publicación-suscripción controlado por la detección
El servicio de publicación-suscripción
Para facilitar la implementación de su propio servicio de publicación-suscripción, escribí el DiscoveryPublishService<T> definido como:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class DiscoveryPublishService<T> : IDisposable where T: class
{
public static readonly Uri Scope;
protected void FireEvent(params object[] args);
//More members
}
Todo lo que necesita hacer es derivar su servicio de publicación-suscripción desde DiscoveryPublishService<T> y especificar el contrato de eventos como el parámetro de tipo. Luego implemente las operaciones del contrato de eventos llamando al método FireEvent.
Por ejemplo, considere este contrato de eventos:
[ServiceContract]
interface IMyEvents
{
[OperationContract(IsOneWay = true)]
void OnEvent1();
[OperationContract(IsOneWay = true)]
void OnEvent2(int number);
}
La figura 11 muestra la manera de implementar su servicio de publicación-suscripción usando DiscoveryPublishService<T>.
Figura 11 Implementación de un servicio de publicación-suscripción
class MyPublishService : DiscoveryPublishService<IMyEvents>,IMyEvents
{
public void OnEvent1()
{
FireEvent();
}
public void OnEvent2(int number)
{
FireEvent(number);
}
}
De manera interna, DiscoveryPublishService<T> usa otra de mis clases derivadas AddressContainer<T>, denominada DiscoveredServices<T>, que se define de la siguiente manera:
public class DiscoveredServices<T> : AddressesContainer<T> where T: class
{
public DiscoveredServices();
public void Abort();
}
DiscoveredServices<T> está diseñado para mantener tanto como sea posible una lista actualizada de todos los servicios detectados, y almacena las direcciones que detecta en su clase base. DiscoveredServices<T> deriva una detección en proceso en un subproceso de segundo plano, y es útil en casos en los que desea obtener un repositorio actual de direcciones detectadas.
El método FireEvent extrae el nombre de la operación de los encabezados de mensaje. Luego consulta en la lista de suscriptores todos los suscriptores que no sean compatibles con el ámbito de publicación-suscripción (para evitar la autodetección). FireEvent luego combina la lista en una unión de entradas únicas (algo que se requiere para tratar con suscriptores que se anuncian a sí mismos y que son detectables). Para cada suscriptor, FireEvent infiere el enlace a partir del esquema de dirección y crea un proxy para activarlo en el suscriptor. La publicación de los eventos se realiza de manera simultánea con subprocesos del grupo de subprocesos.
Para hospedar el servicio de publicación-suscripción, use el método auxiliar estático CreateHost<S> de DiscoveryPublishService<T>:
public class DiscoveryPublishService<T> : IDisposable where T: class
{
public static ServiceHost<S> CreateHost<S>()
where S : DiscoveryPublishService<T>,T;
//More members
}
El parámetro de tipo S es su subclase de DiscoveryPublishService<T> y T es el contrato de eventos. CreateHost<S> devuelve una instancia de un host de servicio que debe abrir:
ServiceHost host = DiscoveryPublishService<IMyEvents>.
CreateHost<MyPublishService>();
host.Open();
Además, CreateHost<S> también obtendrá una dirección base TCP disponible y agregará el extremo de eventos para que no sea necesario contar con un archivo de configuración.
El editor
El editor necesita un proxy al servicio de eventos. Para eso, use mi DiscoveryPublishService<T>.CreateChannel:
public class DiscoveryPublishService<T> : IDisposable where T : class
{
public static T CreateChannel();
//More members
}
DiscoveryPublishService<T>.CreateChannel detecta el servicio de publicación-suscripción y crea un proxy a él. La detección es rápida, gracias a que la cardinalidad es una. El código del editor es sencillo:
IMyEvents proxy = DiscoveryPublishService<IMyEvents>.CreateChannel();
proxy.OnEvent1();
(proxy as ICommunicationObject).Close();
Cuando se trata de implementar un suscriptor, no es necesario realizar nada en especial. Simplemente admita el contrato de eventos en un servicio, y agregue otra detección o anuncios (o ambos) del extremo de eventos.
Juval Lowy es arquitecto de software de IDesign y se encarga de los programas de aprendizaje y asesoramiento de arquitectura para WCF. Su último libro es Programming WCF Services Third Edition (O’Reilly, 2010). También es director regional de Microsoft en Silicon Valley. Puede ponerse en contacto con Lowy en www.idesign.net.