Zėŋōfōbìå

17 luglio 2009

AOP e Cache

Filed under: C#, informatica — Tag:, — Zeno @ 14:11

Certe volte accedere ad un dato è costoso, vuoi perché non è disponibile localmente, vuoi perché deve essere calcolato. In alcuni casi disporre di una cache locale può migliorare in modo significativo la velocità del programma. Con i linguaggi che dispongono delle dictionary viene comodo usarle per costruire una semplice cache locale: la chiave è il parametro della funzione, il valore è il suo risultato.

Supponiamo di avere un metodo oneroso, in C#, che estrae un record dal DB:

// VERSIONE SENZA CACHE
// estrae dal db un record dalla tabella records con l'id specificato
Record GetRecordById(int id)
{
   // questa estrazione dal db è potenzialmente lenta
   var record= from r in db.records where r.id==id select r;
   return record;
}

Per ottimizzare l’accesso al metodo, nell’ipotesi che i record in questione siano costanti, usando un Dictionary sarei portato a scrivere:

// VERSIONE CON CACHE INTEGRATA
// cache che popolo di record, riferiti dal loro id
static Dictionary<int ,record> cache=new Dictionary</int><int ,record> ();

// il metodo aggiunge la gestione della cache: ad ogni estrazione del db viene aggiunta
// la coppia id, record, in modo da poterla recuperare le volte successive
Record GetRecordById(int id)
{
    // verifica che la chiave sia presente nella cache
    if(cache.ContainsKey(id))
        return cache[id]; // se è presente restituisci il valore presente ed esci

    // estrazione onerosa del record
    var record= from r in db.records where r.id==id select r;
    // salvataggio in cache della coppia id,record
    cache[id]=record;

    return record;
}

In realtà quello che ho aggiunto in questa seconda versione è un “aspetto” che può essere codificato con il modello Aspect Oriented Programming, in modo da poterlo riapplicare anche ad altri oggetti. Per .NET c’è un tool chiamato PostSharp che implementa la AOP: permette in modo molto intuitivo di separare il codice legato al concetto di Cache e di “agganciarlo” ai metodi che lo richiedono usando la gli attributi di C#.

Così, per aggiungere la cache al metodo GetRecordById, usando l’aspect oriented programming, aggiungo al metodo l’attributo Cached, ottenendo un’implemetazione molto pulita della classe:

// VERSIONE CON CACHE AOP
// attributo Cached: fa riferimento alla classe CachedAttribute
[Cached]
Record GetRecordById(int id)
{
    var record= from r in db.records where r.id==id select r;
    return record;
}

Il codice di CacheAspect.cs che contiene l’implementazione di CachedAttribute:

namespace CacheAspect
{
    // uso una classe di supporto per la cache
    [Serializable]
    public class CacheHelper
    {
        Dictionary<object ,object> localcache=new Dictionary</object><object ,object>();
        public bool Get(object key, out object value)
        {
            if (localcache.ContainsKey(key))
            {
                value = localcache[key];
                return true;
            }else
            {
                value = null;
                return false;
            }
        }

        public void Add(object key, object value)
        {
            localcache[key] = value;
        }
    }

    [Serializable]
    public sealed class CachedAttribute : OnMethodInvocationAspect
    {
        CacheHelper cacheHelper=new CacheHelper()
        public CachedAttribute()
        {
        }

        // la magia è tutta qui: questo metodo viene eseguito al momento dell'invocazione del metodo che
        // l'attributo Cached decora. 
        public override void OnInvocation(MethodInvocationEventArgs context)
        {
            object value;
            object[] args = context.GetArgumentArray();

            // assumo per semplicità che esista almeno un argomento della funzione, che uso come chiave, 
            // in questo caso è un intero, l'id del record
            object key=args[0];

            // se la chiave non è presente...
            if (!cacheHelper.Get(key, out value))
            {
                // procedo con l'esecuzione del metodo decorato
                context.Proceed();
                // prelevo il valore restituito (in questo caso è un record)
                value = context.ReturnValue;
                // aggiungo la coppia id,record alla cache
                cacheHelper.Add(key, value);
            }
            else
            {
                // se invece la chiave è presente evito che il metodo decorato venga eseguito davvero
                // e restituisco il valore desiderato pescato dalla cache
                context.ReturnValue = value;
            }
        }
    }

Naturalmente questo snippet è inadeguato per la vita reale, perché non tiene conto della cache coherence in caso in cui la base di dati cambi. Tuttavia, per un uso in un ambiente service oriented che garantisca il fatto che l’accesso alla tabella Records sia interamente gestita da un solo servizio, sarebbe possibile fornire degli attributi CacheInvalidate ai metodi che agiscono sulla tabella.

Con la stessa tecnica si possono decorare dei metodi per renderli asincroni, per loggarne l’accesso, per aggiungere delle validazioni, per modificare gli argomenti, per trappare le eccezioni, per sincronizzare i thread.

Come fa PostSharp a funzionare?
Niente di più facile: agisce con un postprocessore sul IL compilato. :-)

Annunci

3 commenti »

  1. Premetto che il pattern decorator mi piace tantissimo, però il rischio di abusarne (=ogni funzionalità incapsulata in un decorator “per default” => decorator hell! :)) è un fatto incontrovertibile, specie se le implementazioni sono comode da usare… :D

    Commento di jp — 20 luglio 2009 @ 12:26

  2. Vero, mi sono già accorto che è facile abituarcisi…
    Però è molto comodo.
    Pensa ad esempio alla validazione sulle proprietà:

    [RegexValidator(“\w+ \d+ \w+”)]
    string Targa { get; set; }

    E’ molto più pulito così, no?

    Commento di Fabrizio — 24 luglio 2009 @ 10:44

  3. Oh sì, lo è decisamente.

    Commento di jp — 24 luglio 2009 @ 12:09


RSS feed for comments on this post. TrackBack URI

Rispondi

Effettua il login con uno di questi metodi per inviare il tuo commento:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...

Crea un sito o un blog gratuitamente presso WordPress.com.

%d blogger hanno fatto clic su Mi Piace per questo: