Archietture a plugIn, pensieri e considerazioni

Archietture a plugIn, pensieri e considerazioni

Uno dei modelli architetturali che più mi piace adottare per garantirmi sistemi aperti e personlaizzabili senza toccare il core applicativo è l'architettura a plugIn. In parole povere il goal di questo tipo di artchitettura sta nel creare modelli di comportamento che poi vengono implementati da librerie esterne caricate a runtime. Un esempio di applicazione di tale architettura la vedo bene nell'implentazione dei DataProvider del DAL. Mi piace infatti definire un DAL astratto fatto di modelli comportamentali (classi base astratte e/o interfacce).

Ecco due righe di esempio pratico per capire cosa intendo. L'esempio illustra un architettura molto semplice mirata a far capire il concetto del plugIn. Ho per questo volutamente omesso complicazioni legate ai pattern di creazione dei provider concreti e i dettagli implementativi degli metodi specifici.

public interface IPersonsDataProvider
{
    void Inizialize(string configurationString);
    PersonsCollection Retrieve();      
}
public /* static */ class PersonsDataProviderFactory
{
    public static IPersonsDataProvider GetPersonsDataProvider()
    {
        string providerTypeName = ConfigurationSettings.AppSettings["personsProviderTypeName"];
        string providerConfiguartionString = ConfigurationSettings.AppSettings["personsProviderConfiguartionString"];
        
        IPersonsDataProvider p = (IPersonsDataProvider)Activator.CreateInstance(Type.GetType(providerTypeName));
        p.Inizialize(providerConfiguartionString);
        return p;
    }
}
public /* static */ class PersonsManager
{
    public static PersonsCollection Retrieve()
    {
        IPersonsDataProvider provider = PersonsDataProviderFactory.GetPersonsDataProvider();
        return provider.Retrieve();        
    }
}

...implementiamo ora un paio provider concreti...

public class SqlClientPersonsDataProvider
{
    private string connectionString;
    
    public void Inizialize(string configurationString)
    {
        this.connectionString = configurationString;
    }    
    public PersonsCollection Retrieve()
    {
        PersonsCollection persons = new PersonsCollection();
        SqlConnection conn = new SqlConnection(this.connectionString);
        
        //TODO: Apertura della connessione, esecuzione del comando e mappatura dei dati.
        
        return persons;
    }
}
public class FileSysPersonsDataProvider
{
    private string fileName;
    
    public void Inizialize(string configurationString)
    {
        this.fileName = configurationString;
    }    
    public PersonsCollection Retrieve()
    {
        PersonsCollection persons = new PersonsCollection();
                
        //TODO: Apertura del file, lettura riga per riga e mappatura dei dati.
        
        return persons;
    }
}

Nell'esempio che ho qui sopra riportato PersonsManager sa che chiedendo al PersonsDataProvider potrà avere una lista di persone ma ignora dove tale elenco è stato letto: è quindi noto il comportamento del PlugIn ma si ignora l'implementazione.

Bene, ora che ci siamo chiariti quello che intendo per architettura a PlugIn posso introdurre le riflessioni di cui voglio parlare. Questo Week end ho fatto un paio di riflessioni: meglio avere un interfaccia che i plug devono implentare (come nel mio esempio) o una classe base che devono estendere? La questione credo che sia interessante perchè ognuno in merito ha i suoi pro e i suoi contro. Ho quindi provato a fare un elenco di pro e contro cercando di individuare dei workaround per i problemi tecnici che si potrebbero incontrare.

Molti preferiscono usare la classe base in modo che il plug abbia facilmente a disposizione alcune funzionalità base del sistema. Questa idea preferisco scartarla. Se ho esigenza di dare al plugin visibilità ad alcune funzionalità base del sistema preferisco implentare una classe Helper che sia di facciata al sistema. Ricordiamoci che abbiamo a disposizione una sola classe base da cui estendere per cui potrebbe essere un grosso limite per chi deve implementare il plugin.

Credo che la diatriba classe base da estendere o interfaccia da implementare sia più interessante - e meno filosofica - quando siamo in un contesto di upgrade del sistema con esigenza di aggiungere/modificare funzionalità al plugIn e non si ha pieno e/o facile controllo sulla modifica dei plugIn già implementati. Se le funzionalità da aggiungere sono bloccanti per il sistema ovviamente occorre - ahimè - rimettere mano a tutti plugIn... ma se le funzionalità da aggiungere non sono bloccanti si potrebbe rischiare se non si sta attenti di buttare nel cestino tutti i plugIn già implementati per il nostro sistema. Vediamo di capire cosa intendo con un esempio.

In seguito alla richiesta di recupero di una persona dato il suo codice faccio la modifica qui sotto riportata all'interfaccia del provider.

public interface IPersonsDataProvider
{
    void Inizialize(string configurationString);
    PersonsCollection Retrieve();      
    Person RetrieveByCode(string Code); 
}

La semplice modifica che ho fatto farà si che tutti i plugin già realizzati vengono a perdere di validità e il loro caricamento a runtime andrà in errore. Definire un interfaccia implica la definizione di una constraint forte. Se avessimo usato un classe astratta le cose sarebbe andate meglio? Beh qualche piccola semplificazione ci sarebbe. Ecco un esempio di come potrebbe essere definita una classe astratta base per il nostro DataProvider.

public class abstract PersonsDataProviderBase
{
    public abstract void Inizialize(string configurationString);
    public abstract void PersonsCollection Retrieve();      
}

Poi in seguito alla richiesta di modifica avremmo potuto fare la modifica come segue.

public class abstract PersonsDataProviderBase
{
    public abstract void Inizialize(string configurationString);
    public abstract void PersonsCollection Retrieve();      
    public virtual Person RetrieveByCode(string Code) 
    {
        PersonsCollection persons = Retrieve();
        foreach(Person person in persons)
        {
            if(person.Code == code)
            {
                return person;
            }            
        }
        return new UnknownPerson();
    }
}

Così facendo i plugin fino ad ora implementati continuerebbero a funzionare garantendo tempi più rilassati per apportare le dovute modifiche... ma non avremmo la garanzia che i nuovi DataProvider implementino correttamente tutti i metodi, qualche programmatore un pò svogliato implementerebbe solo i metodi astratti senza sentirsi in dovere di implementare tutti i metodi. ;-p

Quale portebbe essere la soluzione al caso dell'uso delle interfacce? Un workaround potrebbe essere quello che segue.

public interface IPersonsDataProviderV2: IPersonsDataProvider
{
    Person RetrieveByCode(string Code);
}
// ...
public /* static */ class PersonsManager
{
    public static PersonsCollection Retrieve()
    {
        IPersonsDataProvider provider = PersonsDataProviderFactory.GetPersonsDataProvider();
        return provider.Retrieve();        
    }
    public static Person RetrieveByCode(string Code) 
    {
        IPersonsDataProvider provider = PersonsDataProviderFactory.GetPersonsDataProvider();
        if(provider is IPersonsDataProviderV2)
        {
            ((IPersonsDataProviderV2)provider).RetrieveByCode(string code)            
        }
        else
        {        
            PersonsCollection persons = Retrieve();
            foreach(Person person in persons)
            {
                if(person.Code == code)
                {
                    return person;
                }            
            }
            return new UnknownPerson();
        }    
    }
}

Per il nostro esempio abbiamo avuto fortuna... per il metodo richiesto era facilmente implementabile una soluzione alternativa. Se non è possibile? In tal caso si potrebbe scegliere di far tornare una collezione vuota, un elemento speciale o sollevare una NotSupportedException o una NotImplementedException (rimando per la filosofia delle eccezioni a "The difference between NotSupportedException and NotImplementedException".)

Che conclusioni mi sono fatto in merito al problema? Storicamente sono sempre stato legato all'uso di interfaccia - ma non ho mai affrontato il problema che ho qui trattato. Scrivendo il post mi sono invece ricreduto... L'uso di interfacce di upgrade potrebbe facilitare l'inidividuaizone della versione del plugin grazie a semplici test sul tipo implementato, ma le potenzialità offerte dalla classe base sul tema di upgrade del sistema mi sembrano davvero valide se si considera la conservazione dell'esistente.

E il limite riguardo al problema tecnico di un unica estensione? Quale potrebbe essere il workaround in tal senso? In tal caso la classe pluggabile al sistema dovrebbe diventare una facciata, proxy o wrapper che dir si voglia. Per meglio capire quello che intendo facciamo un esempio. Ho esigenza che il mio DataProvider sia un ServicedComponent!

public class MyServicedComponent: ServicedComponent
{
    //TODO: Implementare i metodi di PersonsDataProvider
}
public class MyPersonsDataProvider: PersonsDataProviderBase
{
    private string configurationString;
    
    public override void Inizialize(string configurationString)
    {
        this.configurationString = configurationString;
    }    
    public override  PersonsCollection Retrieve()
    {
        using(MyServicedComponent component = new MyServicedComponent())
        {
            component.Inizialize(configurationString);
            return component.Retrieve();
        }
    }
    public override  Person RetrieveByCode(string Code)
    {
        using(MyServicedComponent component = new MyServicedComponent())
        {
            component.Inizialize(configurationString);
            return component.RetrieveByCode(code);
        }
    }
}
Technorati Tags:

posted @ lunedì 7 novembre 2005 13:02

Print

Comments on this entry:

# re: Archietture a plugIn, pensieri e considerazioni

Left by Lawrence Oluyede at 07/11/2005 15:25
Gravatar
Interessante, praticamente quello che Ruby ti offre by design con i Mixin:
http://www.rubycentral.com/book/tut_modules.html

Comunque concordo, meglio i plugin piuttosto che un'architettura pesantemente basata sull'inheritance. Ovvio che poi dipende dal caso...

# Classe Base Oppure Interfaccia

Left by Pingback/TrackBack at 08/11/2005 10:48
Gravatar
Quando definisci una classe base stai applicando il ParadigmaDiProgrammazioneObjectOriented, quando definisci una interfaccia stai applicando il ParadigmaDiProgrammazioneBasataSuInterfacce. Un esempio concreto di un problema che ammette entrambe le possibilità e della ricerca della soluizione ottimale la trovi in questo post di MarcoBarzaghi.

# re: Archietture a plugIn, pensieri e considerazioni

Left by Gundam at 23/11/2005 20:34
Gravatar
Credo che il concetto di plug-in sia filosofico e non solo una semplice questione di uso non uso.

Chi non comprende le potenzialità dei Plug, probabilmente si ritroverà a breve con del codice vecchio e peggio ancora destinato a rimanere tale.

Chi ha iniziato programmare ieri pomeriggio tardi, del resto fa fatica a comprendere quanto sia importante avere delle applicazioni capici di crescere senza troppi compromessi. Dando non solo un senso "filosofico di open source", ma di ottime capicità di integrazione.

Chi sa prevedere gli scenari di domani non è un veggente ne uno che si para le chiap. ma qualcuno sa il fatto suo.


Comments have been closed on this topic.
«novembre»
domlunmarmergiovensab
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011