Università degli studi di Bologna Facoltà di Ingegneria Corso di laurea Specialistica in Ingegneria Informatica Relazione di Reti di Calcolatori LS “Reingegnerizzazione di un’applicazione per la gestione di uno studio odontoiatrico: da un modello single-user ad un’architettura distribuita tollerante ai guasti basata sul discovery dinamico dei servizi” Relazione di Gottardi Gianpaolo Anno Accademico 2006-2007 Indice Introduzione ........................................................................................................................... pag. 3 Capitolo 1 – L’applicazione originale.................................................................................... pag. 4 Capitolo 2 – Microsoft .Net Remoting ................................................................................... pag. 6 Capitolo 3 – L’architettura a - Il progetto............................................................................................................... pag. 8 b - Pattern PAC ........................................................................................................... pag. 8 c - Discovery dinamico dei servizi .............................................................................. pag. 10 d - Tolleranza ai guasti .............................................................................................. pag. 11 e - Il prototipo ............................................................................................................ pag. 12 Capitolo 4 – I componenti a - Condivisione dei metadati ..................................................................................... pag. 15 b - Cliente e servitore: l’interfaccia del remote object............................................... pag. 15 c - Cliente e servitore: attivazione .............................................................................. pag. 19 d - Cliente e servitore: configurazione ....................................................................... pag. 20 e - Server e Monitor: pubblicazione e check della liveness del servizio .................. pag. 21 f - DiscoveryServer: pubblicazione dei servizi .......................................................... pag. 22 g - StandbyManager e relativa copia del servizio ...................................................... pag. 23 Capitolo 5 – Conclusioni e sviluppi futuri ............................................................................. pag. 26 Bibliografia ............................................................................................................................. pag. 27 2 Introduzione Il progetto alla base di questa relazione riguarda la realizzazione di un’infrastruttura in grado di estendere il dominio di funzionamento di un’applicazione software preesistente per la gestione di uno studio odontoiatrico, la cui descrizione è demandata al Cap. 1. Esulando dalle motivazioni di carattere strettamente accademico, il lavoro ha una giustificazione reale, nata dalla collaborazione dell’autore con alcuni studi odontoiatrici dell’hinterland bolognese: uno dei committenti ha infatti esplicitamente richiesto l’ampliamento delle funzionalità dell’applicazione monoutente originale per rendere possibile l’accesso ai diversi servizi da più postazioni mobili, nell’ambito di una piccola rete locale. Per approfondire le tematiche affrontate nel corso di Reti di Calcolatori LS, sperimentarle concretamente e nello stesso tempo offrire al cliente una soluzione qualitativamente superiore, nella progettazione del sistema sono stati introdotti meccanismi per consentire il discovery dinamico dei servizi e per garantire agli stessi continuità in caso di guasto. L’architettura introdotta verrà illustrata nel terzo capitolo, posticipando la descrizione dettagliata dei singoli componenti all’analisi della tecnologia adottata, al fine di garantire una maggiore comprensione dei dettagli implementativi. La realizzazione prototipale è comunque omologa a quella dell’applicazione originale, ovvero si mantiene come linguaggio Microsoft C# e come ambiente di sviluppo MS Visual Studio 2005. Le motivazioni di questa scelta si devono ricercare sia nel costo (in termini di tempo) dell’apprendimento di altre tecnologie (e.g. CORBA) e dell’eventuale migrazione di componenti già testati e sviluppati, sia nell’ ampio corredo di librerie per il supporto alla programmazione distribuita, messo a disposizione dall’architettura MS .Net Remoting. Una sua breve descrizione verrà fornita nel secondo capitolo. 3 Capitolo 1 – L’applicazione originale La collaborazione dello studente con alcuni studi odontoiatrici ha portato alla realizzazione di un’applicazione in grado di supportare i committenti nella maggior parte degli aspetti logistici connessi alla loro attività. In particolare sono stati sviluppati diversi moduli, demandati alla gestione di: Pazienti dello studio (i clienti); Listini dei servizi erogabili; Cartelle cliniche contenenti lo storico delle prestazioni mediche relative ad ogni cliente; Anamnesi del paziente; Fatture ed acconti per consentire anche la gestione fiscale; Preventivi; Appuntamenti e richiami; Tutti gli elementi sopra riportati contemplano la possibilità di stampare la relativa documentazione nonché materiale di carattere legislativo (e.g. D.L. 626, legge sulla privacy). Da ultimo è stato realizzato l’interprete di un linguaggio attraverso il quale l’utente finale è in grado di creare, compilare e stampare autonomamente documenti, svincolando da questo aspetto l’autore e consentendo un buon grado di personalizzazione. Dal punto di vista tecnologico, l’applicazione è stata realizzata in linguaggio Microsoft C#, utilizzando l’ambiente MS Visual Studio 2005. L’interazione con l’utente è totalmente gestita attraverso finestre di dialogo in cui sia la veste grafica sia alcuni meccanismi di supporto (e.g. i ToolTips, labels associate ad ogni controllo visibile) sono pensati per informare e guidare l’utilizzatore nelle proprie scelte. La gestione dei report si è avvalsa dell’ engine Crystal, dell’omonima società, mentre l’implementazione del modello entity-relationship dei dati è stata effettuata attraverso ADO .Net: il modello disconnesso che questa tecnologia sussume, consente l’accesso ai dati, il loro salvataggio in una cache locale (il DataSet) e la chiusura della connessione con un database, evitando un’interazione permanente con quest’ultimo. L’utente in questo modo può lavorare sul DataSet, interagendo con il supporto di memorizzazione solo in caso di effettiva necessità (e.g. aggiornamento). Il costo del transitorio iniziale dovuto al caricamento dei dati e al conseguente popolamento delle tabelle (DataTable) è ampiamente ripagato dalla velocità di esecuzione delle operazioni di accesso e aggiornamento. In merito alla persistenza dei dati, i file Xml introdotti inizialmente sono stati abbandonati a favore di un database Access che risulta alquanto più “ingombrante” ma consente la definizione di politiche minime di sicurezza. Il modello di esecuzione della prima versione del prodotto era strettamente monoutente. Per soddisfare le richieste dei clienti in merito alla possibilità di accedere contemporaneamente ai servizi da più postazioni mobili, è stato sviluppato un primo prototipo basato sulla condivisione del 4 database e sull’impiego di un processo coordinatore centralizzato noto a tutti i client, in grado di garantire la mutua esclusione degli accessi. La soluzione era estremamente semplice e prona ad errori: il server costituiva infatti un single point of failure ed in caso di problemi, si potevano avere condizioni di deadlock, al meglio, o corruzione dei dati vanificando in pratica l’utilità dell’intero software. Per questo è risultato indispensabile il progetto di una nuova architettura, in grado di superare il modello two-tier delle applicazioni client/server a favore di quello three-tier (figura sottostante) che garantisce una maggior scalabilità, flessibilità e manutenibilità dei servizi, introducendo una netta separazione tra i livelli di presentazione (i client), di business (la logica applicativa mantenuta dai server) e di persistenza dei dati. Benché il progetto contempli questa distinzione, per motivi legati al ridotto tempo a disposizione del programmatore, i server manterranno localmente copie dei dati di loro interesse, demandando a sviluppi futuri l’introduzione di un database server per la gestione della concorrenza / mutua esclusione / transazionalità degli accessi ai dati. 5 Capitolo 2 – Microsoft .Net Remoting a – Un po’ di storia .Net Remoting è un’application programming interface (API) per la comunicazione tra processi rilasciata dalla Microsoft nel 2002 con la prima versione del framework .Net. Rientra nel filone delle tecnologie introdotte dal colosso mondiale a partire dal 1990 con la prima versione dell’ Object Linking and Embedding (OLE), seguito dal Component Object Model (COM) nel 1993, Distributed Component Object Model (DCOM) nel 1997 e poi rinominato ActiveX. Benché la versione 2.0 del framework sia stata resa pubblica da poco più di due anni, la 3.0 è già stata rilasciata come componente integrato nel sistema operativo Windows Vista e tra le novità annovera il passaggio da .Net Remoting a Windows Communication Foundation (WCF). b – Architettura di .Net Remoting L'infrastruttura di .Net Remoting rappresenta un approccio astratto alla comunicazione interprocesso e definisce la maggior parte degli elementi comuni a tutti i middleware attualmente a disposizione di un programmatore per la realizzazione di applicazioni distribuite. La descrizione di questi elementi esula dalla presente relazione, ma si ritiene opportuno effettuare una comparazione tra le caratteristiche di .Net Remoting e quelle di altri strumenti come CORBA, COM/DCOM e RMI: semplicità di implementazione: un’applicazione remota sviluppata mediante .Net consente di focalizzare l’attenzione sul dominio applicativo, agevolando notevolmente la gestione dei canali di comunicazione, dei formati per la codifica e dei riferimenti remoti. E’ ad esempio possibile passare da un canale Tcp di comunicazione (codifica binaria dei messaggi, velocità maggiore) ad uno Http (utilizzo di SOAP per l’incapsulamento delle richieste, minor ottimizzazione ma maggior interoperabilità) semplicemente modificando una linea di un file di configurazione. Rispetto a Java RMI non è necessaria la generazione manuale di stub e skeleton, né la definizione delle interfacce in un linguaggio astratto (IDL) come in CORBA e DCOM. Non viene inoltre imposto nessun vincolo di piattaforma o linguaggio come accade per gli Enterprise Java Beans o per DCOM. Uno dei pregi dell’infrastruttura riconosciuto dall’autore consiste nel fatto che a queste agevolazioni non corrisponde alcun vincolo implementativo: il programmatore può infatti decidere il livello di complessità dei singoli componenti; ad esempio l’attivazione di un servizio remoto può richiedere la sola scrittura di due righe di codice oppure il progetto di un protocollo di trasferimento personalizzato. estendibilità dell’architettura: .Net Remoting offre allo sviluppatore una vasta gamma di personalizzazioni della stessa architettura. Il suo funzionamento di default prevede le seguenti azioni: ogni volta che un client mantiene un riferimento ad un oggetto remoto, viene creato un transparent proxy (analogo allo stub in RMI) che maschera l’oggetto riferito e permette l’invocazione dei suoi metodi, attraverso la conversione della chiamata in un messaggio. Questo viene passato attraverso un numero variabile di livelli e sottoposto infine a serializzazione, ovvero alla sua 6 trasformazione in uno specifico formato per il trasferimento, ad esempio SOAP, per poter successivamente esser inviato al processo remoto mediante un canale / protocollo di trasporto (Http o Tcp). Lato server, il messaggio viene deserializzato ripercorrendo in maniera inversa i layer, fino al raggiungimento del dispatcher che invoca il metodo richiesto e (se la chiamata lo prevede) elabora un messaggio di risposta che verrà inviato con la stessa modalità al client. Diversamente dalle altre architetture, la maggior parte dei layers in .Net può esser estesa o rimossa e nuovi livelli possono esser introdotti per personalizzare la modalità con cui sono processati i messaggi. Il progetto esposto in seguito, ad esempio, sfrutta questa possibilità per ottimizzare l’interazione di un server con il proprio monitor. Si ritiene opportuno notare che l’aggiunta/rimozione di nuovi livelli (detti anche sink) può esser eseguita dall’amministratore del sistema, modificando il file di configurazione del server, senza richiedere la modifica del codice sorgente. definizione delle interfacce: molti middleware tra cui DCE/RPC, RMI, J2EE richiedono la generazione manuale di stub e skeleton, che nascondono (uno lato client,l’altro lato server) a livello applicativo la natura distribuita dell’oggetto cui si riferiscono. La maggior parte di questi ambienti richiede la definizione dell’interfaccia in un opportuno Interface Definition Language successivamente sottoposta a precompilazione per la generazione quantomeno degli header di stub e skeleton in uno specifico linguaggio di programmazione. .Net Remoting al contrario utilizza un proxy generico per tutti gli oggetti remoti, recuperandone le caratteristiche grazie al meccanismo della reflection. Non per questo viene meno l’esigenza di separare l’interfaccia dall’implementazione; la tecnica utilizzata anche in fase di realizzazione del prototipo di questo progetto è la definizione dell’interfaccia del servizio in una libreria separata da quella contenente l’implementazione, rendendola successivamente disponibile ai client. serializzazione dei dati: analogamente a quanto accade in CORBA e in Java RMI, il framework .Net si occupa del passaggio dei dati per copia tra cliente e servitore; se tutti i tipi primitivi sono serializzabili, quelli complessi richiedono semplicemente la specifica dell’attributo di classe [Serializable] o l’implementazione dell’interfaccia ISerializable. I tipi di dati primitivi che compongono l’oggetto o eventuali “sotto-oggetti” (i quali devono esser serializzabili a loro volta) verranno automaticamente codificati ed inviati al server, eventualmente in formato Xml per superare possibili eterogeneità di piattaforma. Lifetime management: come appreso dal corso di Reti di Calcolatori, la gestione del ciclo di vita dei componenti di un’applicazione distribuita può esser oggetto di diverse politiche: storicamente, il primo approccio prevedeva una connessione Tcp diretta tra cliente e servitore: una volta terminata le risorse di quest’ultimo potevano esser deallocate. Un modello più evoluto è quello adottato da DCOM che combina reference counting a un meccanismo di pinging: se un client non risponde ad un ping viene considerato non più raggiungibile e si decrementa il reference counter. Una volta che questo ha raggiunto lo zero, le risorse sono liberate. La validità di questi modello è stata vanificata nel tempo dal progressivo aumento dell’eterogeneità delle reti: tralasciando l’approccio più datato, la presenza di firewall, router o gateway (spesso configurati in maniera diversa e soprattutto da amministratori diversi) rende difficile la comunicazione tra componenti distribuiti e conseguentemente l’adozione di un modello unificato per la gestione del lifetime. Per esperienza diretta, è possibile perdere una giornata intera a configurare un server senza alcun risultato, perché il client possiede più servizi che gestiscono le porte di comunicazione. .Net Remoting di default assegna un tempo di vita ad ogni oggetto remoto, incrementato ad ogni chiamata di un suo metodo. Questo comportamento può esser ampiamente personalizzato modificando il time to live o registrando, ad esempio, presso il server un componente chiamato sponsor che viene contattato nel caso in cui sia scaduto il tempo di vita dell’oggetto, consentendo una sua estensione attraverso il rinnovo del lease, oggetto che incapsula un valore di TimeSpan, gestito da un LeaseManager. 7 Capitolo 3 – L’architettura a – Il progetto Come anticipato nei capitoli precedenti, le linee guida del progetto sono le seguenti: estensione del paradigma di esecuzione dell’applicazione originale, introducendo un modello three tier distribuito; discovery dinamico dei servizi. introduzione di meccanismi per garantire tolleranza ai guasti; b – Pattern PAC Per quanto riguarda il primo punto, la separazione dei diversi layer è stata eseguita applicando il pattern PAC, Presentation, Abstraction, Control. Il PAC rientra nell'insieme dei pattern architetturali ed è uno dei due dedicati ai sistemi interattivi i quali hanno come scopo la suddivisione coerente del sistema in parti, in modo da garantire allo stesso tempo espandibilità, riusabilità ed interoperabilità, come descritto in [POSA]. In particolare si propone una soluzione "a matrice" in cui si considerano a livello verticale i macrosistemi secondo una suddivisione three-tier (Client, Server e DbServer). I piani orizzontali evidenziano invece, all'interno del medesimo macrosistema, la suddivisione PCE delle entità componenti. Client: si prevede di mantenere il livello di presentazione dell’applicazione originale, mentre quello di business dev’esser completamente rielaborato per consentire l’interazione con i server remoti. Quest’ultima si prevede avvenga non solo in modalità sincrona con sospensione del client in attesa della risposta dal server, ma anche in modalità asincrona, sia di tipo Fire&Forget sia ResultCallback. Si rimanda una sua analisi ai capitoli successivi. I client sono costituiti dai dispositivi mobili a disposizione del personale medico e rappresentano sia dispositivi di input che di output, permettendo sia la visualizzazione, sia l’inserimento/modifica 8 delle informazioni gestite dal database remoto attraverso apposite form (i client saranno quindi WindowsApplications). Server: sono responsabili della business logic dell’intero progetto. Nel caso specifico si prevede l’introduzione di un numero di server pari alle diverse aree applicative (gestione pazienti, listini, prestazioni mediche, appuntamenti etc.) più quelli di supporto (discovery, monitor, manager copia di standby). Naturalmente devono esser realizzati ex novo e, non essendo caratterizzati da un livello di presentazione consistente, possono concretizzarsi in ConsoleApplications). La tipologia dei servizi offerti e la delega ad estensioni future dell’implementazione di meccanismi per la sicurezza (correlata al concetto di sessione) fa protendere il progettista verso l’introduzione di server stateless. In realtà, si potrebbero contemplare code per la memorizzazione temporanea dei messaggi di richiesta, in grado di sopperire a condizioni di stress del server, di cui rappresenterebbero lo stato. Tuttavia, il limitato numero di client (il committente lo indica non superiore a 8) , l’impiego di funzioni one-way per le richieste più onerose e di ADO.Net (molto performante nell’accesso ai dati) rende remota la possibilità di denial of service dovuto a sovraccarico del servitore. Per questo la scelta tra i seguenti modelli di attivazione (offerti dalle API di .Net Remoting) riguarda altre problematiche: Modello “Singleton”: la prima chiamata al servitore provoca l’attivazione di una sua unica istanza che si occuperà da quel momento in poi della gestione di tutte le richieste anche da parte di clienti diversi. L’overhead complessivo è limitato ed è possibile mantenere stato tra le diverse chiamate. Per contro, l’esecuzione del specifico servizio è sequenziale. La figura sottostante mostra come un oggetto remoto configurato in Singleton mode serva richieste di clienti diversi. Naturalmente è necessario prestare attenzione alla tempo di vita del Well-Known object, la cui scadenza comporterebbe una deallocazione automatica della risorsa da parte dell’infrastruttura per cui richieste successive potrebbero esser servite da istanze diverse. Modello “Single Call”: l’infrastruttura crea ad ogni richiesta da parte dei clienti una nuova istanza dell’oggetto servitore; una volta terminata l’esecuzione è resa disponibile per la deallocazione. A fronte del costo di attivazione (che può comportare un degrado eccessivo delle prestazioni in caso di forte interazione con i client) e del paradigma strettamente stateless, l’esecuzione lato server può esser concorrente. In tal caso sono comunque necessari meccanismi di sincronizzazione per l’eventuale accesso a risorse condivise. 9 Si intende sottolineare che la distinzione tra i due modelli si concretizza a livello di codice in un unico parametro: Singleton activation: RemotingConfiguration.RegisterWellKnownServiceType( typeof( SomeMBRType ), "SomeURI", WellKnownObjectMode.Singleton ); Single call activation: RemotingConfiguration.RegisterWellKnownServiceType( typeof( SomeMBRType ), "SomeURI", WellKnownObjectMode.SingleCall ); L’espressione “WellKnownObject” si riferisce al fatto che l’applicazione server pubblica l’oggetto remoto ad uno specifico indirizzo/endpoint ed è responsabile della sua attivazione; si tratta di un cosiddetto oggetto “Server Activated”, in contrapposizione a quelli “Client Activated” caratterizzati da un URI diverso (e quindi un’istanza diversa) per ogni copia mantenuta ed attivata dall’utente sul server. Database manager / server: gestisce la persistenza dei dati e fornisce servizi per l’accesso concorrente / mutuamente esclusivo / transazionale agli stessi. Data la complessità della realizzazione di un componente simile, in fase progettuale si prevede l’adozione di uno strumento commerciale come SqlServer , MySql o Oracle Db Server. Le considerazioni effettuate in merito a questo componente nella sezione dedicata all’analisi del prototipo comporteranno sostanziali modifiche alla struttura e al modello di interazione tra i diversi layer. c – Discovery dinamico dei servizi Anziché cablare l’Universal Resource Locator (URL) dei server nei file di configurazione di ogni client, per garantire una maggior scalabilità dell’architettura si prevede l’introduzione di un server di discovery che consente la registrazione dinamica degli URL dei servitori e lookup per il loro reperimento. Dal punto di vista della tolleranza ai guasti, questo componente rappresenta un single point of failure: un suo guasto potrebbe inficiare il funzionamento dell’ intero sistema. Vista la semplicità delle funzioni che è chiamato a svolgere e per il principio di minima intrusione non si prevede di adottare in tal senso alcun accorgimento, quale potrebbe esser l’introduzione di replicazione o di un supporto per la condivisione dei dati (e.g. uno spazio di tuple). La lista dei servizi è labile, ovvero mantenuta solo per il tempo di funzionamento del DiscoveryServer: allo spegnimento le informazioni sono perse e alla sua riattivazione, si deve procedere ad una nuova registrazione dei server. 10 d – Tolleranza ai guasti Al fine di garantire un livello minimo di recoverability del sistema a fronte di crash dei servitori, si associa ad ognuno di essi un Monitor che ne verifica periodicamente la vitalità attraverso messaggi di HeartBeat. Se il controllore non ottiene una risposta in tempo utile, provvede all’attivazione di una copia secondaria del server (il cui deployment è statico e a carico dell’amministratore) situata (possibilmente) su una macchina diversa e al conseguente aggiornamento del DiscoveryServer. L’interruzione del servizio è relativa al solo tempo necessario al recupero del nuovo URL da parte del client. In prima battuta, essendo i server stateless non è necessario effettuare alcun aggiornamento della copia secondaria. I concetti di hot / cold copy non possono esser quindi applicati, nemmeno per quanto riguarda l’attivazione perché quest’ultima è comunque di competenza dell’infrastruttura .Net e la semplice dichiarazione del servizio non ha alcun effetto sullo stesso. I modelli di guasto previsti dal progettista sono i seguenti: crash del cliente: non si intraprende alcuna azione correttiva; se era stata effettuata una registrazione per la segnalazione di eventi, il cliente è eliminato dalla lista (per una maggior comprensione si rimanda ai paragrafi successivi); crash del servitore: attivazione di una copia di standby. La nuova locazione del servizio viene recuperata in maniera trasparente all’utente; crash del monitor: il single point of failure costituito dal monitor viene trattato nella stessa maniera del DiscoveryServer; il basso carico computazionale da cui è interessato non induce il progettista alla replicazione del componente. L’ipotesi di guasto singolo permette di considerare remota la possibilità di failure contemporaneo del monitor e del server primario, consentendo la segnalazione del problema e l’intervento dell’amministratore per il ripristino del sistema. crash del DiscoveryServer: gli effetti del guasto sono diversi, dipendentemente dall’ottenimento o meno della lista dei servizi da parte del client. In caso affermativo, il guasto del componente non consente l’aggiornamento dell’URL da parte del monitor (che ha rilevato il crash del server primario) e si potrebbe avere un interruzione del servizio. L’ipotesi di guasto singolo anche in questo caso permette di supporre la tempestiva segnalazione del problema ed intervento dell’amministratore. In caso negativo l’interruzione del servizio è inevitabile, a meno che l’utente non conosca autonomamente l’indirizzo del servitore e lo immetta manualmente nel file di configurazione (soluzione poco elegante, ma efficace). crash del gestore della copia secondaria: si provvede alla segnalazione del problema e al suo recovery. La remota possibilità di crash contemporanei del server primario e del gestore della copia di standby potrebbe a prima vista portare ad una situazione di inconsistenza dei dati, le cui copie locali (introdotte per i motivi esposti nel seguente paragrafo) potrebbero esser diverse. In questo caso sarebbe necessario analizzare il timestamp associato alle ultime modifiche occorse, per individuare la copia più recente (e corretta, grazie al meccanismo di propagazione degli aggiornamenti – v. dopo). Quest’ultima osservazione è applicabile anche in caso di network partition, a cui il sistema reagirebbe in maniera errata, promuovendo la copia di standby. 11 Complessivamente, l’architettura proposta è quella sottostante: election Client Manager Standby copy Monitor election Discovery Server Price-List Manager Standby copy Monitor Client Manager Client Price-list manager Client DB Server e – Il prototipo La realizzazione di un prototipo funzionante, nel rispetto dei vincoli temporali sia accademici che personali, ha portato a due compromessi: impiego della tecnologia dell’applicazione originale per la persistenza dei dati: l’adozione di un database server avrebbe richiesto tempi di apprendimento / sviluppo / deployment troppo lunghi, per cui l’autore ha preferito introdurre database Access locali ai server, gestiti attraverso ADO.Net. A questa decisione segue però l’apertura di nuovi scenari connessi al mantenimento della coerenza tra i dati gestiti dai server primari e quelli mantenuti dalla copia di standby. La soluzione adottata prevede un aggiornamento event driven di quest’ultima da parte del servitore cui si riferisce (sempre che l’operazione sia stata da lui eseguita con successo), in modalità trasparente rispetto all’ application layer. Compito dell’amministratore sarà, oltre a scegliere la macchina su cui “attivare” la copia di standby (o meglio, il suo manager), anche modificare il file di configurazione del relativo server. L’insieme di dati su cui entrambi operano deriva dallo scorporo del modello entityrelationship previsto per l’applicazione originale. Si vuole anticipare una particolarità del processo di implementazione dell’architettura: tutti gli elementi sono stati pensati per ridurre al massimo l’intervento del progettista / programmatore, cercando di massimizzare la flessibilità del prodotto; a tale scopo, i file di configurazione sia dell’applicazione sia dell’infrastruttura remota hanno trovato largo impiego. In particolare uno stesso componente (detto StandbyServer) è in grado di fungere da gestore di una copia secondaria o da server primario (e di passare naturalmente dal primo al secondo in risposta ad un comando di election) semplicemente agendo su un flag booleano. implementazione completa dell’infrastruttura di supporto, ma integrazione di un unico servizio completamente funzionante a disposizione del cliente: sempre per motivi di tempo e per non dover adottare soluzioni temporanee per la sincronizzazione dell’accesso a dati condivisi (in vista dell’introduzione di un apposito database server), il prototipo prevede 12 l’erogazione di un unico servizio, ovvero quello demandato alla gestione dei pazienti dello studio odontoiatrico. Non per questo si deve considerare parziale la soluzione proposta: si tratta solo di un mezzo per descrivere le problematiche affrontate e i modelli di interazione introdotti. Dalla loro estensione, ulteriori servizi possono esser facilmente integrati nell’infrastruttura, richiedendo tempi abbastanza contenuti. Persistenza dei dati: Client Manager Standby copy Client Manager DataSet DataSet Price-List Manager Standby copy Price-list manager DataSet DataSet Ogni componente gestisce la propria copia dei dati mediante un meccanismo basato su ADO .Net realizzato e testato da tempo dall’autore: in estrema sintesi, una libreria appositamente sviluppata (RootDataProject) estende le funzionalità dei componenti propri del framework .Net consentendo non solo il mantenimento dell’associazione e della consistenza tra la base dati e il DataSet, ma anche la creazione automatica delle entità del business layer; in tal modo lo sviluppatore può concentrare la propria attenzione solo su quest’ultimo aspetto. Per fornire un esempio concreto, la necessità di gestire i pazienti dello studio odontotecnico prevede per il Data Layer: la specializzazione della classe RootDataTable attraverso ClientiDT e la sua dichiarazione nel DataSet; la specializzazione della classe RootDataRow attraverso ClienteRow per la definizione delle proprietà specifiche dell’oggetto e la loro associazione (mediante un’estensione di RootDataRowSchema) ai campi del supporto di memorizzazione (righe di una tabella di un database o nodi di un documento Xml). Il Business Layer prevede la sola specializzazione della classe ClienteRow per render possibile la creazione automatica di un istanza di Cliente, il popolamento di suoi attributi e la propagazione automatica nel database delle modifiche. Opportuni DataAdapter permettono l’impiego di diversi supporti per la memorizzazione (file Xml, database Access e altro) e i ConnectionManagers si occupano della gestione della connessione, evitando che il programmatore debba scrivere manualmente tutte le relative istruzioni Sql. Un Document si occupa della corretta inizializzazione di tutte le entità in gioco e del popolamento del DataSet cui si riferisce. Un componente, chiamato DataLoader, implementa il pattern Singleton e rappresenta un punto unico per l’accesso/aggiornamento dei dati. Come si potrà notare nei capitoli 13 successivi, il caricamento dei dati dal database (la cui locazione è un parametro configurabile) richiede la specifica della seguente linea di codice: StudioDocument _document = new StudioDocument(); Segue, a titolo esemplificativo, un diagramma delle classi dei componenti fondamentali dell’infrastruttura: Framework .Net Libreria universale di supporto all’infrastruttura elaborata dall’autore Elementi specifici del modello E-R di un’ applicazione 14 Capitolo 4 – I componenti a – Condivisione dei metadati Prima di passare all’analisi dei singoli componenti, è doveroso descrivere il metodo adottato per la condivisione delle informazioni tra client e server, riguardanti le entità remote: .Net Remoting non richiede che le interfacce siano definite attraverso un Interface Definition Language. Inoltre Proxy e Stub sono generati dall’infrastruttura. Come render consapevole il client delle operazioni che può richiamare sull’oggetto remoto? Il supporto consente la scelta tra quattro modalità principali: Shared Implementation: gli assemblies che contengono l’implementazione dei server objects sono condivisi con il cliente; è la soluzione che richiede il minimo sforzo, ma anche quella più pericolosa: oltre ad esser teoricamente scorretta in quanto viola i principi di base della programmazione distribuita, clienti maliziosi potrebbero disassemblare il codice (usando, ad esempio, l’ Intermediate Language Disassembler – ILDASM - fornito con la versione SDK del framework) ed accedere alla business logic del server; Shared Interfaces: l’assembly condiviso contiene unicamente le interfacce implementate dai servizi remoti (ed eventualmente classi di supporto); l’unico difetto di questa modalità è il mancato supporto ad applicazioni avanzate che richiedono il passaggio degli oggetti remoti come parametri di funzioni appartenenti ad un contesto diverso (ma non è questo il caso…); Shared Base Classes: risolve il problema esposto poco sopra, in quanto l’impiego di classi astratte anziché di interfacce permette il cast dinamico degli oggetti remoti che possono quindi esser passati tra domini applicativi diversi; Utilizzo di SOAP-SUDS: si tratta di programma introdotto recentemente in grado di estrarre i metadati (ad esempio una type definition) da un server, avviato o meno. A partire dalle informazioni recuperate crea un nuovo assembly a disposizione del client. L’utilizzo di quest’applicazione elimina l’intervento del programmatore nella descrizione delle informazioni condivise, ma richiede una sua completa conoscenza. La modalità impiegata nell’implementazione del prototipo prevede la condivisione delle interfacce e del conseguente assembly (e.g. libreria Shared_data per il server). b – Cliente e servitore: l’interfaccia del remote object. La descrizione dei due componenti principali parte dall’analisi dei servizi esposti dal server ClientManager, (dove per client si intende un paziente) , definiti da un’apposita interfaccia IClientManager; oltre a quelli “classici” di inserimento / modifica / rimozione dei dati di un paziente, sono presenti: metodi per la registrazione / deregistrazione di un “EventWrapper”; un metodo per la stampa di un report relativo ad uno o più pazienti; un metodo dedicato all’Heartbeating 15 public interface IClient_manager { Response add_client(string surname, string name,…, string e_mail); Response remove_client(int client_code); Response modify_client(int client_code, string surname, string name, … , string e_mail); Fully_defined_client get_client(int client_code); Thiny_client[] get_client_list(); void Register_for_events(Event_wrapper ew); void Unregister_from_events(Event_wrapper ew); [OneWay()] void Print_client_report(bool single_client, int code); bool Are_you_alive(); } I requisiti funzionali del server sono espletati dai primi cinque metodi. L’inserimento di un paziente (attraverso la specifica delle sue proprietà), la modifica e la rimozione si riflettono sui dati del database; per contemplare situazioni di incoerenza (rimozione di un paziente non esistente, duplicazione delle informazioni) al cliente è restituita un’indicazione dell’avvenuta esecuzione del metodo attraverso la classe condivisa e serializzabile Response. Essa mantiene un enumerativo che indica il successo (HRESULT.OK) o meno dell’operazione e nel secondo caso un messaggio di errore. In questo modo il paziente può eseguire azioni correttive in quanto la segnalazione si riferisce solamente a problemi derivanti dai dati inseriti dall’utente e non da guasti di componenti. I metodi per il recupero di informazioni riguardanti uno o più pazienti, come si può notare, hanno valori di ritorno diversi, per motivi connessi alla performance del servizio: difficilmente il cliente è interessato all’intera lista (che consta solitamente di centinaia di nominativi), per cui risulterebbe inutile inviare la totalità delle proprietà di un paziente (dodici campi testuali, uno numerico ed un DateTime). Per questo la classe Thiny_client definisce solo le caratteristiche minime (Nome, Cognome, Codice identificativo e Data di nascita, come concordato con il committente) necessarie alla sola visualizzazione di una lista lato client. La classe Fully_defined_client (le cui istanze sono costruite dinamicamente ad ogni richiesta) risponde tuttavia all’esigenza opposta per la visualizzazione di tutte le proprietà di un paziente attraverso un’apposita form del client. Meritano un discorso più articolato i due metodi Register_for_events e Unregister_from_events: per mantenere allineati clienti e servitore si adotta un modello push di quest’ultimo, basato sulla propagazione di eventi. Ad ogni modifica al database consegue la generazione di un BasicEvent che mantiene informazioni sia sull’azione effettuata (enumerativo Action), sia l’elemento da essa interessato (un ThinyClient). In questo modo si risparmia un eventuale round trip per il recupero dei cambiamenti occorsi. Il meccanismo è supportato da .Net Remoting grazie alla serializzazione dei delegati. Le implicazioni di questa soluzione sono diverse: passaggio dall’unidirezionalità del rapporto tra cliente e servitore alla bidirezionalità. Questo rende necessaria l’attivazione di un end-point di ricezione anche lato client, risolta direttamente a livello di configurazione. Il canale di comunicazione dev’essere naturalmente omogeneo a quello del server, mentre la scelta della porta non è di particolare importanza, per cui si pone l’infrastruttura in attesa di una connessione su una qualsiasi delle porte disponibili (specificando il valore 0 nel costruttore dell’ HttpChannel). Poco avanti sarà possibile ritrovare questi elementi, nella breve analisi del file di configurazione del client. introduzione di un legame tra le controparti, il quale potrebbe esser giudicato troppo vincolante: in realtà sono state adottate misure per ridurre al massimo i problemi derivanti 16 dalla necessità di compresenza tra le entità, del modello. Lato server la generazione degli eventi avviene recuperando la lista dei client che si sono registrati ed iterando sulla stessa per l’invocazione asincrona del relativo delegato. In caso di suo insuccesso (e.g. a causa del crash del cliente) l’esecuzione del server può continuare, previa deregistrazione del specifico handler. Il metodo BeginInvoke consente l’invocazione asincrona di metodi sincroni: il thread originale che ha inviato la richiesta può continuare l'esecuzione parallelamente al metodo di destinazione, che viene eseguito in un thread di un pool. Lato client, prima di eseguire qualsiasi accesso ai servizi remoti il cliente provvede alla sua registrazione: se questa non ha successo, probabilmente è occorso un guasto ed è necessario attivare la procedura di recovery. In fase di chiusura della finestra demandata all’interazione con un servitore si procede invece alla deregistrazione del cliente: eventuali problemi non sono segnalati proprio per la semantica dell’azione dell’utente. Il corretto funzionamento dell’intero meccanismo, richiederebbe la conoscenza dell’handler specifico di ogni cliente, da parte del servitore, portando ad una cosiddetta architettura server-to-server. Come si nota dalla signature dei metodi di registrazione / deregistrazione lo sviluppatore ha invece introdotto la classe (condivisa e serializzabile) EventWrapper che implementa il pattern Proxy e funge da ripetitore dell’evento, evitando il suddetto inconveniente. Alternativamente alla politica di CallBack cui è riconducibile l’intero meccanismo, sarebbe stato possibile introdurre funzioni di Polling del servitore, aumentando però considerevolmente il traffico tra le entità. Segue il diagramma di sequenza relativo all’interazione “ad eventi” tra cliente e servitore. 17 Lato client, alla ricezione di un nuovo evento si pone il problema dell’aggiornamento della ListView dedicata alla presentazione della lista dei pazienti ricevuta dal server : l’utente infatti mediante i controlli disponibili sul lato sinistro della form potrebbe aver aperto una delle finestre di dialogo, con conseguente passaggio del flusso di esecuzione e stallo dell’intera applicazione in caso di richiesta di gestione di un evento. Per ovviare a questo problema si impiega il metodo Invoke offerto dalla maggior parte dei controlli del namespace “System.Windows.Forms, il quale consente l’esecuzione di un delegato che punta ad un metodo del controllo stesso, in un thread separato da quello principale. Sono omessi ulteriori dettagli di carattere strettamente implementativo. Il metodo “Print_client_report” dell’interfaccia permette di inoltrare al server la richiesta di stampa del report di un singolo paziente o di tutti quelli presenti nel database. La peculiarità di quest’operazione è l’attributo “OneWay()”: il processo di stampa è computazionalmente oneroso e tipicamente richiede alcuni secondi. Possedendo un effetto visibile e potendo così discriminare facilmente sul successo o meno della richiesta, al metodo è stata associata una politica di tipo Fire&Forget : il client invoca l’operazione e prosegue nella computazione senza preoccuparsi di un eventuale valore di ritorno. Il server non si fa direttamente carico del servizio, ma delega l’impegno ad un thread creato ex-novo. In merito all’interazione tra il server ed il proprio monitor (descritta successivamente in dettaglio), l’interfaccia prevede il metodo Are_you_alive: la sua dichiarazione è necessaria unicamente per 18 consentirne l’invocazione, ma l’implementazione è del tutto opzionale, come dimostra il seguente snippet di codice: Il motivo verrà esposto nei prossimi capitoli. c – Cliente e servitore: attivazione. Il modello di base per l’attivazione dei componenti prevede: la registrazione del server presso l’infrastruttura come WellKnownObject, (sezione service del file di configurazione) in termini di tipo, Uniform Resource Identifier (URI), canale (e quindi porta e protocollo), modalità (Singleton o Single Call) più eventuali modifiche alla catena dei sink (layer interessati dal passaggio delle richieste che realizzano di fatto il pattern Interceptor) tra cui spicca il formatter, responsabile della ricostruzione del messaggio di chiamata, a partire dai dati (grezzi o strutturati) scambiati a livello di trasporto. I canali di default sono l’Http e il Tcp: il primo garantisce la massima interoperabilità, ma soffre in termini prestazionali utilizzando il protocollo Soap. Il secondo è più performante (usa stream binari per il trasporto), ma potrebbe esser bloccato da firewall, gateway etc. La flessibilità del supporto permette comunque la definizione di canali personalizzati. L’Updater_sink_provider verrà descritto nelle sezioni successive. 19 la dichiarazione da parte del client dei WellKnownObjects remoti cui intende accedere (sezione client) del file di configurazione in termini di tipo e Uniform Resource Locator (URL). La sezione channels attiva un canale di comunicazione anche per il cliente: il valore nullo pone l’infrastruttura in attesa di una connessione su una qualsiasi delle porte libere. I file Xml presentati poco sopra rappresentano un ottimo strumento per la configurazione rapida di un’applicazione remota. Il programmatore deve scrivere un’unica riga di codice per rendere attiva l’infrastruttura: Purtroppo il meccanismo non è sempre applicabile, come nel caso del client: il file precedente è puramente riportato a scopo dimostrativo, in quando il/gli indirizzi dei servitori sono prelevati runtime dal server di Discovery. L’alternativa è l’attivazione dinamica dei servizi, la quale richiede la scrittura di qualche riga di codice in più. d – Cliente e servitore: configurazione Entrambi i componenti richiedono l’intervento dell’amministratore in quanto, essendo responsabile del deployment dell’applicazione è in grado di infondere conoscenza nel sistema, ovvero aggiornare in modo opportuno i file di configurazione. Il client infatti necessita dell’Url del DiscoveryServer, per il recupero della lista dei servizi disponibili. Il server invece richiede l’Url del manager della copia di standby per l’aggiornamento di quest’ultima (v. oltre). Il programmatore ritiene più consono un intervento minimo dell’amministratore, rispetto all’introduzione di un round-trip tra un server ed il proprio Monitor. 20 e – Server e Monitor: pubblicazione e check della liveness del servizio. Il Monitor, unico per ogni server, possiede tre compiti: pubblicare il servizio attraverso le funzionalità del DiscoveryServer; contattare periodicamente il server controllato, per verificarne la vitalità; in caso di guasto, aggiornare l’entry del DiscoveryServer ed attivare la copia di standby, interagendo con il suo controllore. Per il proprio funzionamento, il Monitor richiede la presenza nel file di configurazione App.config degli Url relativi al servitore, al DiscoveryServer, alla copia di standby e al suo manager. Tralasciando la fase di pubblicazione che consiste nell’invocazione del metodo Suscribe dell’apposito server, si ritiene utile analizzare il meccanismo di fault tolerance: alla sua attivazione, il Monitor crea un nuovo thread responsabile dell’inoltro periodico di messaggi di heartbeating. La richiesta consiste nell’invocazione del metodo Are_you_alive già presentato nell’analisi dell’interfaccia del servitore. Per evitare di introdurre carico computazionale aggiuntivo connesso a funzionalità che non riguardano la logica applicativa, è stato sviluppato un sink lato server che filtra i messaggi in ingresso, costruisce la risposta alle richieste di liveness e l’invia al mittente senza interessare i livelli sovrastanti. Il sink è posizionato immediatamente al di sopra del formatter in quanto lavora su messaggi e non stream di dati o envelope Soap. In caso di guasto, il monitor aggiorna l’Url del servizio con quello relativo alla copia di standby la cui attivazione è dovuta ad un messaggio di election indirizzato al suo gestore (StandbyManager). Tutto questo garantisce la prosecuzione del lavoro dell’utente, a parte un transitorio conseguente alla segnalazione della caduta del server primario, alla comunicazione con il DiscoveryServer (al fine di aggiornare l’Url del servitore) e all’ottenimento del nuovo riferimento remoto (della copia di standby appena eletta). Lato client, è prevista una classe statica che ha il compito sia di gestire i servizi, attivando e rendendo disponibile a livello applicativo i remote refereces, sia di effettuare il recovery nel caso in cui si verifichino problemi di connessione con il server. La politica implementata nel prototipo prevede un unico tentativo di connessione al “nuovo” Url ; sviluppi futuri saranno indirizzati al suo raffinamento, ad esempio attraverso l’attivazione di un thread per il polling della connessione. 21 f – DiscoveryServer: pubblicazione dei servizi Come anticipato precedentemente, il DiscoveryServer è stato introdotto sia per garantire maggior scalabilità all’architettura, sia per supportare l’aggiornamento dinamico degli Url dei servizi (e quindi consentire il recovery dei client). Il componente mantiene al suo interno una lista (implementata tramite un’HashTable) formata dall’insieme delle coppie [NomeServizio,Url]. Il suo aggiornamento runtime è di competenza dei vari Monitor, senza però alcun salvataggio persistente delle informazioni. Per questo, l’attivazione del DiscoveryServer avviene necessariamente in modalità Singleton. La sequenzializzazione delle richieste è ovviata dal limitato numero di client dell’ambiente di deployment (il committente ne ha indicati al massimo otto) e dalla loro limitata interazione con questo server (unicamente al primo accesso o in caso di failure). Sviluppi futuri potrebbero prevedere l’attivazione del server in modalità SingleCall ed un thread separato, demandato alla gestione dell’HashTable: in tal caso sarebbero necessarie politiche di sincronizzazione sia per l’accesso che per l’aggiornamento dei dati. A tal fine, la classe System.Collections.Hashtable mette a disposizione del programmatore il metodo statico Hashtable.Synchronized(Hashtable _hashtable). Segue l’interfaccia del servizio, dichiarata nell’assembly “General”, condiviso con i componenti che necessitano dell’accesso al DiscoveryServer, ovvero i client ed i Monitor. Benché il prototipo preveda l’implementazione del solo Client_manager rendendo equivalente l’impiego dei metodi Lookup_service() e Get_services(), l’utilizzo del secondo è consigliato per l’applicazione di release in quanto limita l’interazione tra i client ed il DiscoveryServer. L’ArrayList restituita infatti contiene tante istanze della classe serializzabile Service_def (che banalmente memorizza la coppia di informazioni [Nome,Url] ) quanti i servizi disponibili. 22 g – StandbyManager e relativa copia del servizio: garantire fault-tolerance all’architettura. Il punto focale per conseguire un livello basilare di tolleranza ai guasti è rappresentato dal StandbyManager: come si può dedurre dal nome, si tratta del gestore della copia di standby del servizio e provvede non solo alla sua attivazione in caso di guasto, ma è responsabile dell’aggiornamento del database su cui essa opera, a causa delle peculiarità del prototipo. Nei capitoli precedenti è stata infatti descritta la soluzione di compromesso riguardante la gestione della persistenza dei dati: il progettista ha deciso di riservare l’adozione di un database server a sviluppi futuri dell’applicazione (per motivi di tempo). A questo consegue l’introduzione di una copia replicata del database, locale al StandbyManager, continuamente aggiornata dallo stesso, in risposta alle sollecitazioni inviate dal server primario attraverso l’interfaccia a lato. Sono necessarie alcune osservazioni: innanzitutto, come si può notare, gli argomenti di input dei metodi non possiedono uno strong type, ma sono semplici array di istanze della classe base System.Object. Questa scelta deriva dal metodo con cui le operazioni sono invocate dal server primario, descritto successivamente. In secondo luogo l’attributo [OneWay()] potrebbe far nascere qualche perplessità riguardo l’aggiornamento di tipo Fire&Forget della copia secondaria. L’autore ha consapevolmente adottato una politica ottimista, per i seguenti motivi: la fase di checkpointing è subordinata al successo dell’esecuzione dell’operazione sul server primario; l’occorrenza di un problema viene propagata all’utente (mediante la classe Response), ma non influenza il StandbyManager; la limitata complessità delle operazioni di aggiornamento del DataSet rende remota la possibilità che il manager sollevi eccezioni durante tale fase, a meno di un suo fallimento totale; la dimensione contenuta dell’ambiente di deployment (una rete locale configurata ad hoc) limita fortemente la probabilità di perdita di messaggi (la quale comporterebbe un disallineamento dei due database); il prototipo, inteso come tale, mira all’esposizione dell’intera architettura progettata: dettagli di questo tipo potrebbero esser gestiti o segnalando eventuali problemi al server primario, abbandonando la semantica Fire&Forget delle operazioni, ma introducendo overhead o ritardi (a causa della politica eager dell’aggiornamento) ed ulteriori meccanismi di fault-tolerance (e.g. per un nuovo inoltro della richiesta), contro il principio di minima intrusione; o avvisando l’amministratore del sistema, vincolando però il buon funzionamento di quest’ultimo al tempestivo intervento umano; 23 o mantenendo la politica Fire&Forget, ma bloccando o terminando l’esecuzione del StandbyServer a fronte di problemi, annullando in tal modo il grado di recoverability del sistema; Come si può notare, ognuna delle soluzioni possiede sia pregi che difetti. Qualsiasi sforzo da parte dello sviluppatore per garantire consistenza delle copie a fronte di problemi sarebbe inoltre vanificato dalla successiva introduzione di un database server. Per questo si preferisce l’adozione di una politica ottimista. Per quanto riguarda l’invocazione dei suddetti metodi, si propone un meccanismo simile a quello di heartbeating: il server primario non è interessato a livello applicativo dal checkpointing, ma quest’ultimo è gestito dagli strati software sottostanti, attraverso l’introduzione di un apposito sink. L’Updater_sink analizza infatti sia i messaggi in ingresso, sia quelli in uscita, testando l’avvenuta esecuzione dell’operazione attraverso l’enumerativo ServerProcessing.Complete. Solo in questo caso inoltra il messaggio di richiesta al metodo statico a lato che si occupa dell’estrazione dei parametri e dell’interazione con il StandbyManager per l’aggiornamento della copia del database. L’integrazione dell’ Updater_sink nell’infrastruttura .Net Remoting si deve all’introduzione della classe Updater_sink_provider, che implementa l’interfaccia IServerChannelSinkProvider e provvede sia alla creazione di un’istanza di Updater_sink sia alla sua registrazione nella catena dei sink del server primario. L’ ultimo aspetto non ancora analizzato riguarda il meccanismo di attivazione del StandbyManager e della relativa copia, in risposta alla ricezione di un messaggio di election: a tal fine è stata sviluppata una classe Configurator dotata di una coppia di metodi: Init_copy_manager: provvede dinamicamente alla creazione / registrazione del canale di comunicazione e di un’istanza del manager. Si noti la definizione di un event handler. 24 Disable_server: è l’handler dell’evento Disable_server generato dalla ricezione di un messaggio di election da parte del StandbyManager: in risposta al fallimento del server primario si procede alla deregistrazione del controllore della copia di standby e all’attivazione (naturalmente dinamica) di quest’ultima. Tutte le informazioni riguardanti porta ed Uri del servizio sono recuperate dal file di configurazione dell’applicazione, permettendo una loro personalizzazione. Il file App.config sopra riportato possiede un flag booleano “Is_primary_copy” modificabile dall’amministratore di sistema: come si può intuire questo permette, in fase di deployment dell’applicazione, il setting del medesimo componente come server primario o copia di standby, incrementando la flessibilità e manutenibilità dell’architettura (anche grazie alla riduzione delle entità in gioco). 25 Capitolo 5: conclusioni e sviluppi futuri L’architettura discussa in questa relazione ha consentito all’autore di confrontarsi con le problematiche caratteristiche dei sistemi distribuiti, svincolandosi dal modello Desktop delle applicazioni precedentemente sviluppate. Il prototipo presentato, benché non abbia alcuna pretesa di compiutezza, ha portato ad un buon livello di comprensione della tecnologia Microsoft .Net Remoting e di sperimentazione concreta dei concetti appresi. Si ritiene che entrambi gli aspetti siano fondamentali per rispondere alle esigenze dei committenti di prodotti software: molto spesso infatti non hanno loro stessi chiare le caratteristiche del servizio desiderato e la scarsa formazione informatica concorre alla credenza comune che il passaggio da un’applicazione monoutente ad un sistema distribuito sia gestito automaticamente dal sistema operativo (rendendo valida la trasposizione del termine “utente” in “utonto”). La fase di sviluppo ha richiesto il confronto dell’autore con il cosiddetto “chatty vs. chunky tradeoff”: la maggior parte delle tecniche di programmazione object oriented proposte dai testi consultati forniscono eleganti soluzioni per problemi comuni, ma sono perlopiù appropriate nel caso in cui gli oggetti siano “vicini” tra loro (ovvero appartengano allo stesso processo o “application domain” in .Net). La realizzazione di applicazioni distribuite scalabili ed ottimizzate invece impone stretti limiti sulla collaborazione delle entità pena l’introduzione di livelli inaccettabili di overhead di comunicazione: nel caso particolare è stata necessaria una riorganizzazione del software preesistente per ridurre al massimo l’uso di properties e callbacks, in favore di metodi con un elevato numero di parametri (e.g. IClient_manager.Modify_client()). Il test dell’intero lavoro è stato effettuato utilizzando il programma VMware Workstation attivando più virtual machine contemporaneamente. Per quanto riguarda i possibili sviluppi futuri, oltre a quelli specificati in fase di analisi dei singoli componenti, sono state identificate le seguenti evoluzioni, più o meno necessarie (in ordine di priorità): introduzione di un database sever per il superamento delle limitazioni del prototipo; implementazione degli innumerevoli servizi previsti dall’applicazione originale e loro integrazione nell’architettura progettata; sviluppo di un LogServer per unificare la raccolta di informazioni in merito sia al funzionamento normale del sistema, sia all’occorrenza di guasti; utilizzo di nuovi interceptor e dei contesti per garantire l’identificazione degli utenti e la sicurezza dei loro accessi; realizzazione di canali di trasporto personalizzati per problematiche connesse alla privacy dei pazienti, adottando a tal scopo strumenti per crittografare i flussi di dati; perfezionamento del deployment, con avvio automatico in background dei vari server al boot del sistema operativo; 26 Bibliografia “Microsoft .Net Remoting”, S.McLean, J.Naftel, K.Williams Microsoft Press 2003; “Advanced .Net Remoting (C# edition)”, I.Rammer, APress 2002; “Pattern-Oriented Software Architecture – A system of patterns (volume 1)”, F.Buschmann, R.Meunier, H.Rohnert, P.Sommerland, M.Stal, Wiley 2001; “Pattern-Oriented Software Architecture – Patterns for Concurrent and Networked objects (volume 2)” F.Buschmann, R.Meunier, H.Rohnert, P.Sommerland, M.Stal, Wiley 2001; “A programmer’s guide to ADO.Net in C#”, M.Chand, APress 2002; “Visual C# 2005 – How to program”, P.Deitel, J.Listfield, T.Nieto, C.Yaeger, M.Zlatkina, Prentice Hall 2005; “Distributed systems, principles and paradigms”, A.Tanenbaum, M.Van Steen, Prentice Hall 2002; “MSDN Library – Febbraio 2005”, Microsoft, 2005; 27