Tuesday, December 16, 2008

E questa da dove salta fuori… :-|

image
Mi sa che mi devo preoccupare… ma che gli frega a Visual Studio di quello che sta facendo Outlook?
.m

Sunday, December 14, 2008

ok.. I’m complaining now!

tendenzialmente vedo la parte mezza piena del bicchiere, e in alcune cose posso tranquillamente essere annoverato tra i fanboy, altre però mi fanno veramente arrabbiare, una di queste è il Team Explorer.
Sono un “remote user” al 100%, non sono mai stato collegato alla LAN che ospita il Team Foundation Server che uso, e sono anche un utilizzatore di tutti i camnpionari possibili ed immaginbili di sistemi di connessione… dal fido UMTS/HSDPA a tutte le WiFi e ad una pletore di scandalose e meno scandalose ADSL vedute un po’ da tutti i carrier, il tutto condito dai pegggiori proxy…
In questo scenario potete ben immaginare che la connettività non è di certo una cosa che si possa definire garantita, anzi decisamente no, ma mi viene anche naturale pensare che sia giusto così e che sempre più spesso gli strumenti che usiamo debbano essere pensati per scenari come questi.
Ecco che combattere tutti i giorni con il Team Explorer a volte, anzi decisamente spesso, mi porta all’esasperazione. In questo caso il “buon…” prodotto parte dal presupposto che la connettività sia qualcosa di ben più che garantito e certo.
Evito di dilungarmi e tediarvi con il lungo elenco di magagne, quello che mi preme sottolineare è che una cosa del genere, nelle mani di un utilizzatore disattento, farebbe la così detta di tutta un erba un fascio e la colpa sarebbe di Visual Studio che inevitabilmente ne subisce le conseguenze…
.m

Friday, December 12, 2008

IQueryProvider…

Ok… predo spunto dal commento di Davide per approfondire la questione IQueryProvider e cercare di capire quali sono le difficoltà vere, o almeno quelle che ho scoperto io fino ad ora, nella realizzazione di un query provider per Linq.
Innanzitutto sottolineo che non mi sto cimentando nell’impresa, non per ora…, sto “semplicemente” cercando di capire come funziona. Come Roby sono curioso(issimo) per natura, devo smontare e rimontere tutto quello che mi passa per le mani e se non capisco mi intestardisco fino ad arrivare alla soluzione.
Vediamo innanzitutto da dove si parte. Questo snippet, preso dagli unit test, espone lo scenario:
String expected = "select [_t0].[id], [_t0].[firstName], [_t0].[lastName] from [Persons] as [_t0]";

DbRepositoryQueryProvider provider = new DbRepositoryQueryProvider();
TestPersonRepository repository = new TestPersonRepository( provider );

var query = from person in repository select person;

String actual = provider.GetQueryText( query.Expression );
Nulla di trascendentale, se non fosse che non va…,  mentre questo invece funziona:
String expected = "select [_t0].[id], [_t0].[firstName], [_t0].[lastName] from [Persons] as [_t0] where [_t0].[firstName] = ‘Mauro’";

DbRepositoryQueryProvider provider = new DbRepositoryQueryProvider();
TestPersonRepository repository = new TestPersonRepository( provider );

var query = repository.Where( p => p.FirstName == "Mauro" );

String actual = provider.GetQueryText( query.Expression );
nonostante all’apparenza sia più complesso, e lo è sia ben chiaro. Ma quali sono le differenza tra il primo ed il secondo?
Tutto sta nell’expression tree che viene generato dal motore di Linq, evitando per ora gli screenshot (fidatevi), la differenza fondamentale sta che nel primo caso nell’expression tree avete una MethodCallExpression che incapsula la chiamata a “Select”, mentre nel secondo no, è implicita…
Facciamo un passo indietro:
image
La query Linq di partenza è decisamente, decisamente è un parolone…, semplice da convertire, il repository nell’esempio è una IQueryable e grazie al mapping, tutto hard coded sia chiaro, sappiamo che il “tipo” Person è mappato sulla tabella [Persons], allo stesso modo sappiamo che Person ha una serie di proprietà che sono mappate sulle rispettive colonne id, firstName, lastName. In questo caso nell’expression tree che viene generato troviamo, in coda, una chiamata esplicita a “Select”, quella query Linq è infatti equivalente all’expression generata dall’extension method Select():
repository.Select( p => p );
e infatti produce lo stesso identico expression tree. Ma sappiamo molto bene che in realtà la chiamata a Select() ci serve solo in 2 casi:
  1. L’esempio triviale di cui sopra che corrisponde ad una “select * from tableName” senza condizione, ergo vogliamo tutti i record;
  2. Nel momento in cui vogliamo eseguire una “projection” verso un tipo diverso da Person, e questa è tutta un’altra storia.
Normalmente possiamo scrivere:
var query = repository.Where( p => p.FirstName == "Mauro" );
e tutto funziona come deve senza esplicitare la select, cosa necessaria nella query linq per il solo fatto che è necessaria dal punto di vista del compilatore che deve capire cosa state facendo. Ma torniamo al nostro problema… e mi tocca fare un’altro passo indietro. IQueryProvider (System.Linq) non è sufficiente, nonostante sia il cuore di tutto, perchè non è l’entry point per la generazione di una “query”.
System.Linq.IQueryable è il vero entry point, noi sviluppatori scriviamo query che insistono su una IQueryable e solo quando invochiamo l’esecuzione, o deferred execution che sia, entra in gioco IQueryProvider. L’implementazione di IQueryable è decisamente triviale ed ha il solo scopo di “conservare” l’expression tree che viene generato da chiamate consecutive a n extension methods come in questo esempio:
var query = repository.Where( p => p.FirstName == “Mauro” )
                                   .OrderBy( p => p.LastName )
                                   .Skip( 10 )
                                   .Take( 5 )
                                   .Select( p => new { DisplayName = String.Format( “{0} {1}”, p.FirstName, p.LastName ) } );

Ogni chiamata genera un nuovo expression tree, che è immutabile (importantissimo), che sta a voi memorizzare nella vostra implementazione di IQueryable.
Quando andiamo in esecuzione, deferred execution in questo caso, viene effettivamente chiamato in causa il query provider:

foreach( var element in query )
{
    Console.WriteLine( element );
}
A questo punto viene il bello, dato l’expression tree avete l’onere di analizzarlo e generare innanzitutto il command, gli eventuali parameters, e infine mandarlo in esecuzione recuperare u cursore forward only (IDataReader) e scorrendo i record creare (projection) le istanze delle entity.
Detta così sembra facile… in realtà il problema vero è che se stiamo scrivendo un query provider il nostro obiettivo è quello di essere general purpose e non limitare le possibilità del nostro utilizzatore. Se ci pensate la cosa è più che normale la scrittura di in query provider è paragonabile alla scrittura di un ORM e di certo quando usate NH, ad esempio, non avete limiti nelle possibilità di scrittura delle query… purtroppo infatti, lo sviluppatore, è libero di scrivere:
repository.Where( p => true );
che probabilmente non ha molto senso ma è decisamente lecito e che deve essere trasformato in una cosa del tipo:
select * from [Persons] where 1 = 1
che ancora non è detto che abbia senso ma è sicuramente lecito, ma non solo potremmo sbizzarrirci e produrre:
repository.Where( p => myMethod( p ) );
e qui sono cavoli amari perchè nell’expression tree avete una MethodCallExpression per myMethod e avete l’arduo compito di capire, ed è tutt’altro che semplice, se sia possibile eseguire quella chiamata in maniera remota, ergo su sql server ad esempio, o se invece debba essere eseguita localmente con conseguente fetch di tutti i dati e filtro in memoria… ma potremmo peggiorare:
repository.Where( p => myMethod( p ) ).OrderBy( p => p.FirstName );
mescolando ulteriormente le carte in tavola al provider che deve essere in grado di capire che la prima chiamata deve essere eseguita localmente ma la seconda può essere eseguita su sql server, deve quindi interpretare quell’expression tree come se fosse stato scritto:
repository.OrderBy( p => p.FirstName ).Where( p => myMethod( p ) );
che non so se sia lecito, ad esempio per Linq2Sql, ma rende bene l’idea.
Da questi primissimi esempi è facile capire quale sia il grado di difficoltà a cui ci si trova davanti nella scrittura di un query provider, e questo purtroppo è solo l’inizio. Vedremo, se ci saranno altre “puntate”, quali sono gli altri scogli.
.m

Wednesday, December 10, 2008

Architettura: cui prodest?

Prendo spunto da un post di Mario per allargare la discussione e dire la mia in un contesto più comodo, i commenti purtroppo non sono comodi e non abbiamo più un forum, speriamo che torni presto perchè sarebbe stato il posto ideale.
Disclaimer:
Quando parliamo di architettura ci possiamo mettere su due livelli:
  1. Architettura fine a se stessa: inteso come la soluzione ideale in un mondo ideale;
  2. Architettura calata nel contesto: la soluzione ideale con tutti i constraint del caso derivanti dal mondo che sto vivendo in quel momento, tipicamente le esigenze del cliente;
Questo post vuole solo ed esclusivamente fermarsi al punto 1. Ha senso fermarsi al punto 1? si, perchè ad esempio il punto 2 non esisterebbe senza il punto 1 poi potremmo anche scoprire che neanche il punto 2 ha senso perchè non ha senso parlare di architettura… ma non è oggetto di questo post.
Partendo dalla notte dei tempi ci hanno insegnato che un modello possibile è sintetizzabile così:
image
Questo modello si può evolvere ed arricchire fino ad arrivare a qualcosa di simile al concetto di onion, ma non è questo l’argomento; quel modello alla fine della fiera ci permette di scrivere un qualcosa del tipo:
Person aPerson = myPersonRepository.GetById( “mauro” );
Questo probabilmente produce una chiamata verso un db con un comando sql, i dati tronano indietro viene istanziata una Person e i dati vengono “spinti” nell’istanza di Person appena creata.
Quali sono i limiti di questo approccio? è decisamente rigido. A fronte della necessità di “cercare” istanze di Person con criteri diversi bisogna estendere il DAL/BusinessLayer aggiungendo le funzionalità del caso. Ecco che ci si comincia a chiedere se un linguaggio di query ad alto livello non abbia senso in questo contesto e la risposta che ci diamo è quasi certamente si.

Possiamo quindi evolvere il nostro modello introducendo qualcosa che ci aiuti, e che nel mondo reale probabilmente ci salva la vita:
image
e produrre un codice del tipo:
IDataContext session = //Bla bla…
Person aPerson = session.Get( “queryDefinition” );

l’esempio è triviale, in pseudo codice e potrebbe essere adattato a molti O/RM (o simili…) ma ancora una volta non è questo l’argomento, come non lo è il concetto di transazione di business ne tanto meno quello di data context.
Quello che invece mi preme far notare è che nel nostro modello abbiamo introdotto qualcosa che abbiamo definito infrastruttura e che alla fine della fiera è trasversale anch’essa all’applicazione introducendo una dipendenza.
n.d.r. Non è detto che questo debba essere visto come un problema nel mondo reale, anzi.

Se cominciamo a ragionare ci rendiamo conto che quella dipendenza, nel mondo reale è decisamente difficile da rimuovere, l’esempio lampante è NSK, in NSK Andrea, non è una critica ma una semplice constatazione, espone dal DataContext un metodo GetByCriteria( Query q ) che ha la velleità di astrarre il concetto di query ma se andiamo a guardare l’implementazione o pensiamo ad una possibile implementazione ci rendiamo subito conto che l’obiettivo è decisamente difficile da raggiungere, l’espressività del linguaggio T-SQL, ad esempio, è decisamenta alta e modellare un dominio che lo rappresenti è decisamente difficile.
Quello a cui giungiamo quindi è che la nostra creatura consolida la sua dipendenza dall’infrastruttura, e ripeto non è detto che sia un male.
Ci potremmo però trovare in una spiacevole situazione, esperienza proprio di questi giorni:

La topologia di rete dei client che usano la nostra applicazione cambia/evolve, rendendo necessario uno switch da multi-layer a multi-tier perchè alcuni client adesso sono remoti, di mezzo c’è un “malefico” firewall e da li ci passa solo https443.
image
L’infrastruttura smette di funzionare, semplicemente perchè non è pensata per funzionare in quello scenario, ne consegue che l’applicazione smette di funzionare perchè dipende dall’infrastruttura, quello cha abbiamo adesso è un nuovo modello concettuale:
image
in cui purtroppo non possiamo più produrre del codice come quello che abbiamo visto in precedenza se non astraendo il concetto di query, di data context, di transazione di business etc etc.
E’ possibile farlo? certo che è possibile;
Come? molto semplice Expression Tree;

Sarebbe molto bello poter scrivere una qualcosa del tipo:
IServiceContainer sc = ServiceContainer.GetContainer();
using( IDataContext dc = sc.GetService() )
{
    var query = dc.Persons.Where( p => p.FirstName == “Mauro” );
    IEntityCollection list = query.ToEntityCollection( dc );
    //Operate on list…
    dc.Commit();
}

E’ fattibile? certo che si! quello snippet compila e funziona pure… producendo questo sql:
select [_t0].[id], [_t0].[firstName], [_t0].[lastName] from [Persons] as [_t0] where [_t0].[firstName] = @p1
Cosa è costato? un bagno di sangue :-D oltre al dettaglio che quello funziona mentre questo neanche a morire:
var query = from person in dc.Persons where person.FirstName == “Mauro” select person;
…nonostante, per lo sviluppatore che l’ha scritto, siano esattamente la stessa cosa… solo per lui purtroppo :-D, ma anche questa è un’altra storia.
Quello a cui voglio arrivare è che dal punto di vista della modellazione/astrazione del linguaggio di query adesso (.NET >=3.x) abbiamo quasi tutto, le Lambda Expression sono in questo senso la soluzione ad alto livello, il problema adesso nasce quando si cerca di calare questa bellissima possibilità in un contesto reale e si (ri)scopre che scrivere un query provider per Linq è molto lontano dall’essere un’operazione anche solo pensabile…
Ma, alla fine dei conti, serve tutto ciò? non è detto, ripeto dipende dal contesto. Se ci caliamo nella realtà in cui stiamo lavorando potremmo anche arrivare a scoprire, estremizzando nella direzione opposta, che nulla di tutto ciò ha minimamente senso, potremmo arrivare a trovarci di fronte ad applicazioni scritte male, potenzialmente immanutenibili, e intestabili, ma che funzionano e lo fanno bene… e non è fantasia, ho sotto gli occhi due esempi realissimi proprio in questi giorni ;-)
.m