Inversion of Control e Dependecy Injection fatti in casa (1)

Qualche giorno fa stavo lavorando ad un piccola applicazione con un collega e ci siamo dovuti subito confrontare con una scelta architetturale che mi ha ispirato la scrittura di questo post. Prima di mettere troppa carne al fuoco, meglio spiegare i requisiti di questo mini-sistema; chiaramente lo spoglierò di tutte le cose che "fanno solo volume", e che non sono significative in questo frangente.

Problema: data una bellissima GUI (WPF, Windows Forms, fate voi...) con una casella di ricerca "Google style", è richiesto che l'applicativo esegua una ricerca di tutti documenti, di un formato definito, che contengono nel nome del file oppure nel contenuto del documento stesso, il testo digitato. La richiesta particolare è che la ricerca debba essere fatta sia sul computer locale, che in una locazione remota utilizzando un servizio web (opportunamente disegnato per l'occasione) in grado di erogare le informazioni.

Sembra tutto facile, quindi mettiamo un bel pulsante "toggle" che permette all'utente di passare da una modalità di ricerca all'altra, molto semplicemente. Non passano due ore da quando abbiamo aperto l'ambiente di sviluppo, che il cliente (stranamente) cambia le carte in tavola: richiede che la ricerca debba essere fatta anche su una share di rete, accessibile tramite specifica autenticazione basata su Active Directory.

Il cervello mi dice "Ok, no problem. Mettiamoci un bel 'RadioButton'..." (o qualche cosa di più "stiloso"); l'istinto, invece, mi dice che il giorno seguente avrei ricevuto una nuova telefonata con la richiesta di eseguire la ricerca del documento anche nell'armadio pieno di folderoni che sta alle spalle del nostro committente di turno [ :) ]. Va bene essere "agili", ma questo è "Runtime Programming"...

Essendo un programma desktop, si porta dietro tutta una serie di problematiche di deploy che già potete immaginare, e che il nostro buon vecchio "ClickOnce" può solo mitigare considerati gli scenari in cui sarà utilizzato l'applicativo.

Quindi, per evitare di fare "duecentomila rilasci al giorno" al modificarsi di un minimo requisito, si è deciso di prendere la palla al balzo per mettere in pratica il concetto di "modularità", e i pattern di "Inversion of Control" e "Dependency Injection" in uno scenario tutto sommato molto banale.

"IoC" e "D/I" (abbreviazioni delle due tecniche appena citate) sono pattern architetturali estremamente validi, in grado di ridurre efficacemente la dipendenza diretta di una modulo applicativo dall'altro, usando approcci apparentemente complessi, ma che una volta fatti propri risultano essere estremamente naturali e puliti.

Come al solito evito di scendere nei dettagli teorici della tecnica, perchè in rete si possono trovare innumerevoli articoli estremamente ben scritti da persone che la sanno veramente lunga (vi segnalo un interessante post di Oren Eini, alias Ayende Rahien, su come sviluppare un contenitore IoC in 15 righe di codice). Quindi vediamo subito un po' di codice, cercando di capire come implementare un piccolo motore di Dependency Injection fatto in casa.

Prima cosa da fare è domandarsi quali sono le caratteristiche comuni delle tre modalità di ricerca che (per ora) ci sono state richieste; riuscire ad individuare il "minimo comune denominatore" è fondamentale per progettare un sistema in grado di rispondere alle nostre esigenze ed essere nel contempo flessibile.

Ad una prima occhiata sarà necessaria una sola caratteristica: data una particolare stringa di testo, è necessario vengano erogate una lista di informazioni, ciascuna contenente i dati di un singolo documento che corrisponde alle specifiche finite.

Tutto giusto. Ma ci sono delle limitazioni allo scenario? Certamente si. Se già pensiamo al modulo di ricerca tramite web service, oppure quello sulla share di rete, possiamo subito individuare una serie di impedimenti "fisici" per cui non è possibile portare a termine l'operazione: mancanza di permessi di rete, irraggiugibilità del servizio, autenticazione. Uno sviluppatore previdente deve prendere in considerazione la possibilità che uno dei "moduli" di cui vuole dotatare il programminino, per una qualsiasi ragione, non sia disponibile in ogni istante.

Fermiamoci qui dicendo che i due requisiti individuati (ricerca dei documenti e disponibilità del modulo) siano sufficienti per coprire le nostre esigenze. Ora diamo forma alla cosa con quello che in un linguaggio orientato agli oggetti risponde alle caratteristiche di un "contratto" senza definizione dell'implementazione reale: un'interfaccia.

Sarà quindi questa interfaccia a cui tutti i nostro moduli (o plugin) dovranno sottostare per poter essere correttamente utilizzati dall'applicativo. Se ci pensiamo bene, alla nostra GUI non serve sapere altro: una blackbox che possiede determinate caratteristiche, accetta input e emette gli output che richiediamo.

Si è detto che il metodo "FindDocuments" ritorna una lista di informazioni relative ai documenti che sono stati individuati. Per mantenere lo "stato" del documento utilizziamo un semplicissimo DTO ("Data Transfer Object") che sarà conosciuto sia dall'applicazione (che lo dovrà usare per mostrare le informazioni), sia dai plugin (che lo dovranno generare in funzione dei dati da loro raccolti nella specifica locazione). Di solito mi piace fare le cose per bene, quindi iniziamo a buttare giù una bozza del nostro "service layer" che ci sarà utile per gestire meglio le cose e dare un'interfaccia più ordinata al collega che svilupperà la GUI applicativa. Ci sarà sicuramente bisogno di un metodo che emette un elenco di tutte le tipologie di ricerca disponibili nel sistema, e un metodo che esegue la ricerca dei documenti in funzione della stringa di ricerca digitata e della modalità selezionata. Il metodo per il recupero della lista dei provider configurati è molto semplice, e si limita a leggere da un'ipotetica configurazione applicativa un'elenco di elementi che contengono i dati per i vari provider installati, estraendo di questi solo il nome. Il metodo di ricerca dei documenti esegue prima la creazione dell'istanza del provider stesso, verifica la disponibilità, quindi invoca la ricerca ed emette i risultati.

Ma come mio solito (sto cercando di farmi curare, credetemi) sto gettando troppa benzina sul fuoco, e il post sta diventando la Divina Commedia; quindi per questa volta chiudiamo qui. Nella prossima puntata andremo a vedere le implementazioni dei tre plugin e cercheremo di chiudere (parzialmente) il cerchio...
M.

Commenti

Post popolari in questo blog

Restore di un database SQL Server in un container Docker

Cancellazione fisica vs cancellazione logica dei dati

WCF RESTful service: esposizione del servizio