WS-Discovery e WCF
WS-Discovery è un protocollo WS-* compatibile che permette di utilizzare un servizio, senza specificarne l'indirizzo e il tipo di trasporto.
Di recente ho dovuto implementarlo con WCF e ho pensato di riportare su questo blog le principali problematiche.
Innanzitutto WS-Discovery, al contrario di WCF, si basa sul contratto e non sulla terna ABC del mondo WCF. Infatti il contratto è l'unico parametro necessario per la ricerca del servizio. Binding e Address saranno determinati dopo la fase di discovery.
La cosa che mi sarebbe piaciuto fare è un DiscoveryBindingElement che utilizzato da configurazione permettesse di definire il solo contratto. Questo aspetto non è un cambiamento da poco dal punto di vista della configurazione di WCF e ho dovuto abbandonare questa strada in quanto l'ho trovata non percorribile.
<
system.serviceModel>
<extensions>
<bindingExtensions>
<add name="discoveryBinding" type="WSDiscovery.Configuration.DiscoverySection, WS-Discovery, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</bindingExtensions>
</extensions>
<client>
<endpoint address="discovery://239.255.255.250:3702" binding="discoveryBinding" bindingConfiguration="discovery" contract="SampleInterface.IBorsa" name="DiscoveryBinding.Proxy" />
</client>
<bindings>
<discoveryBinding>
<binding name="discovery">
<discoveryBinding>
<scopes mathcBy="http://schemas.xmlsoap.org/ws/2004/10/discovery/rfc2396">
<scope>http://mieiscopes.org/urlmioscope</scope>
</scopes>
</discoveryBinding>
</binding>
</discoveryBinding>
</bindings>
</system.serviceModel>
L'alternativa è un proxy che utilizza WS-Discovery implementato tramite una classe astratta, ma anche concreta, del tipo DiscoveryClientBase<TChannel> che ha un costruttore con parametro e un costruttore con un array di scopes (uri che specificano parametri aggiuntivi utili per la scelta del servizio a parità di contratto).
Un componente di infrastruttura deve essere assolutamente trasparente. Sulle orme della classe ClientBase di WCF ho utilizzato un sistema a proxy dinamici per esporre una proprietà Channel del tipo <TChannel> che permette in modo analogo alla classe ClientBase dirigere tutte le chiamate ai metodi dell'interfaccia ad una funzione "asso piglia tutto" che imbusta fisicamente il messaggio soap e lo invia. Una buona descrizione del metodo dei proxy dinamici è disponibile qui.
Le cose da tenere conto lato client sono due:
- Ogni entry recuperata dal campo va memorizzata in una global storage con tutti i contratti recuperati o pervenuti dal campo (grazie ai messaggi di Hello)
- Se dovesse comparire un DiscoveryProxy i meccanismi di trasporto cambiano completamente (unicast->multicast e viceversa) e quindi bisogna essere pronti all'evenienza
Il client ha funzioni analoghe quando lavora con la presenza di un discovery proxy e quando questo non è presente: utilizzare uno State (Gof) con una classe astratta che tiene le funzioni comuni e due classi concrete, DiscoveryProxyState e DiscoveryNoProxyState, agevola parecchio il lavoro.
Una funzione interessante che ho aggiunto nel client è che memorizzando le risposte dal campo è in grado di fornire piu servizi che implementano un contratto. La scelta del contratto da utilizzare è realizzata tramite un componente di riordino, che utilizzando logica di processo esegue il suo lavoro. Nel momento in cui invio un messaggio al servizio e dovessi avere un Fault nella comunicazione la classe proxy automagicamente prova ad usare un'altro servizio presente in lista cancellando quello irraggiungibile. Una funzione banale ma di ottima efficacia e totale trasparenza per l'utilizzatore, avendo tutto mascherato attraverso il dynamic proxy.
Dedicherò un successivo post per esprimere le mie considerazioni per il lavoro fatto lato servizio.