VSX Tales…


Avete mai pensato di sfruttare Visual Studio come piattaforma di sviluppo oltre che come fantastico ambiente RAD e non?

A caldo la prima cosa che mi verrebbe da rispondere se qualcuno me lo chiedesse è: “lascia perdere…è un delirio ;-)”. Se però siete come me e vi intestardite sulle cose che non capite fino in fondo allora forse potremmo darci man forte analizzando insieme pro e contro di un task che non è certo semplice: Sviluppo di un VSPackage.

Cercheremo di sviscerare i passi da seguire per arrivare a costruire un proprio Project System per Visual Studio, con:

  • Supporto per i propri project tempate;
  • Supporto per i propri item template;
  • Integrazione con la Toolbox;
  • Integrazione di un proprio custom designer;
  • intergrazione con la property grid;

Un paio di screenshot, giusto per farvi gola :-D:

ProjectSystem ItemTemplate
SolutionExplorerToolbox

Ci si vede la?

.m

author: Mauro Servienti | posted @ venerdì 27 giugno 2008 7.17 | Feedback (5)

[OT] Summit della Bresaola IV


Indovina chi è:

  • Ho installato un TFS 2008 in italiano, con Sql in italano su Windows Server 2008 in italiano...;
  • Non parlatemi di COBOL, dell'ambiente di lavoro poi...
  • Adesso come adesso parteciperei anche allo sviluppo di una applicazione web…;
  • Ho appena mandato a ca*are un cliente via mail…;
  • Piuttosto che scrivere Java vado a fare il magazziniere, detto fatto…;

Ieri sera solita bellissima serata, con mangiata di quelle che si rispettano e si ricordano ;-)

Grazie a tutti!

.m

author: Mauro Servienti | posted @ sabato 21 giugno 2008 13.23 | Feedback (3)

Trasparenza e sincerità: pagano?


Mi collego ad un post di ieri di Luka perchè uno dei punti elencati mi ha fatto pensare e vorrei riportare la mia esperienza di questo periodo.

“…L'unico modo di provare cose nuove è farlo di nascosto dal cliente, dai colleghi e dalla azienda per cui lavoro. Poi se ne risponde ... gioie e dolori…”

Il cliente che sto seguendo in questo periodo direi che può essere classificato, per tutta una serie di motivi che non mi interessa elencare, tra i clienti difficili, in cui il rapporto è complesso e a volte fatto di molta diplomazia.

All’inizio del rapporto la citazione si sarebbe calata perfettamente nella realtà che stavamo vivendo, ma con il passare del tempo mi sono completamente ricreduto, il cliente resta difficile ma sincerità e trasparenza pagano e pagano molto.

Questo ci ha permesso in più di una occasione di andare dal cliente e dire:

  • abbiamo un problema;
  • non sappiamo come risolverlo;
  • l’analisi che abbiamo fatto ci da una serie di alternative;
  • purtroppo non abbiamo esperienza diretta su queste alternative;
  • stimiamo che in x giorni/uomo siamo in grado di farci un cultura sufficiente a prendere una decisione;
  • possiamo procedere?

e la risposta è sempre stata: si va bene, tienici aggiornati su come procedono gli studi…

<update>
faccio un piccolo aggiornamento perchè non voglio essere travisato:
L’unico, e sottolineo l’unico, legame che c’è tra questo post e quello di Luka è semplicemente che la frase che ho estrapolato mi ha fatto pensare, la singola frase, non il resto del post. La singola frase mi ha fatto riflettere e ho ritenuto giusto citare la fonte delle riflessioni.

</update>

.m

author: Mauro Servienti | posted @ sabato 21 giugno 2008 13.17 | Feedback (14)

Se non è semplice c’è qualcosa che non va…


Questo è un periodo veramente intenso che ha però il grandissimo pregio di permettermi di imparare moltissimo e di fare esperienze veramente interessanti.

Nei giorni scorsi stavo leggendo un po’ di “arretrati” e sfogliano un post di Ayende leggo il commento di Tommaso Caldarola e devo dire che, nonostante Ayende sia di parere contrario, io mi trovo in piena sintonia con quello che dice Tommaso.

Possiamo estendere e parafrasare quello che dice Tommaso (sperando di non prendere fischi per fiaschi) dicendo che:

se un “framework” o per meglio dire la sua interfaccia pubblica, non sono di semplice ed intuitivo utilizzo probabilmente c’è qualche problema quantomeno di design se non addirittura architetturale.

Quando mi approccio ad un nuovo problema quello che faccio è partire quasi sempre da un livello molto alto: l’utente finale. Mi metto cioè il cappellino dell’utente finale e scrivo le User Story. Come ho già avuto modo di dire quello è il nostro requisito vero e proprio se non soddisfiamo il nostro utente finale direi che abbiamo fallito.

Questo approccio mi permette quasi sempre di creare/modellare un insieme di servizi e componenti che dal macro problema portano alla risoluzione della singola operazione unitaria senza che però sia noto all’utilizzatore cosa succede dietro le quinte.

UserRepositoryProcess urp = new UserRepositoryProcess( currentSecurityContext );
IEntityCollection<IUser> allUsers = urp.GetAll();

Dietro queste due semplicissime righe di codice succedono una quantità industriale di cose ma l’utente (in questo caso il developer) non è tenuto a saperle, e questo non tanto per “segretezza” ma semplicemente perchè non c’è nessuna necessità di portare complessità la dove non serve.

Perchè sto dicendo tutto ciò? per il semplice fatto che in questi giorni mi sto scontrando con qualcosa che non riesco a rendere semplice… e quindi è prono a continui errori e rivisitazioni proprio perchè non è intuitivo.

Vediamo da cosa nasce il problema e se vi va intavoliamo una discussione (che nel caso sposterei sul forum):

public interface IUnitOfWork : IDisposable
{
    void RegisterDeleted(object item);
    void RegisterDirty(object item);
    void RegisterNew(object item);

    void Commit();
    void Rollback();
}

Queslla esposta è, una parte, dell’interfaccia pubblica della UnitOfWork di NSK.

Vista così, voi direte…, ma che c’è che non va? in realtà nulla, ma se la caliamo in un’applicazione decisamente complessa direi che potremmo obiettare che:

  • Non è un servizio, quindi non è esponibile come tale, per il semplice fatto che ha bisogno di mantenere lo stato;
  • se ci caliamo in una realtà complessa lo strato di UnitoOfWork è decisamente lontano dall’utilizzatore e ritengo che ci debbano essere almeno un paio di strati tra l’utilizzatore e l’ingresso sulla scena della UoW. Ergo ho la sensazione che abbia più responsabilità di quelle che dovrebbe avere.

Il primo punto direi che non necessita spiegazioni: se faccio n chiamate ai vari Register* prima di arrivare ad una Commit o ad una Rollback devo mentenere la lista delle operazioni da eseguire internamente alla UoW.

Il secondo punto invece necessità di qualche approfondimento, la realtà in cui vorrei che vi calaste è la seguente:

  • Siete in una applicazione web, con tutto quello che ne consegue...;
  • avete bisogno di offrire un contesto transazionale anche in memoria: IRevertibleChangeTracking/similari…;
  • Siete in una situazione in cui il ciclo di vita di un grafo di entity “spanna” più richieste http: Conversazione;
  • Gestite un grafo di oggetti abbstanza complesso e non siete limitati alla manipolazione di una entity alla volta: per capirci dovete permettere all’utente di:
    • aprire un ICustomer;
    • modificare il CustomerName;
    • aggiungere un IAddress;
    • rimuovere un IAddress esistente;
    • creare un nuovo IOrder per il customer aperto;
    • inserire un po’ di righe/prodotti nell’ordine, di cui magari alcuni prodotti non esistono e quindi vanno creati al volo…
    • e… permettere all’utente di fare un “Undo” progressivo delle modifiche che ha apportato al “grafo/mondo” che sta manipolando…;
    • alla fine persistere tutto il lavoro effettivamente (quindi al netto degli Undo) fatto;

Se non ho interpretato male in NSK la UnitOfWork è la “sessione” quindi ho una o più UoW per “utente”, quello che mi manca è la possibilità di fare rollback di una singola attività fatta. E’ in questa direzione che vedo la UnitOfWork come un qualcosa che sta ad un livello basso e che viene utilizzato ad un livello troppo alto.

Quello che succede (o meglio che “dovrebbe”) nell’applicazione che stiamo sviluppando è quindi:

  • Inizio della conversazione;
  • Inserimento dei dati che stiamo manipolando nella conversazione;
  • manipolazione del dato che a questo punto può “spannare” su più richieste http;
  • recupero del set (ChangeSet) delle modifiche che sono state apportate ai dati in conversazione;
  • passaggio del ChangeSet alla UnitOfWork;

Dietro le quinte la UoW dato il ChangeSet è in grado di:

  • “estrarre” le sole entity che hanno bisogno di persistere modifiche;
  • per quelle entity capire quale sia l’effettiva operazione (C..UD) da eseguire;
  • recuperare il “Persistence Service” adeguato;
  • Aprire una transazione e fare il commit in maniera transazionale;

Internamente ogni persistence service:

  • recupra l’adapter corretto per la entity che ha in mano e per l’operazione che deve eseguire;
  • invoca l’esecuzione;

E a questo punto se non ci sono stati problemi:

  • conclusione della conversazione;

Detto questo siamo in grado di fare diventare la UoW un servizio che non mantiene stato e che effettivamente fa il lavoro per cui è stato pensato.

…mumble, mumble…

Oggi mi sento un po’ M.rkino pensieroso :-D

.m

author: Mauro Servienti | posted @ giovedì 19 giugno 2008 7.03 | Feedback (8)

Se anche il vostro Outlook 2007 è lento come la fame….


…benvenuti :-D

Se usate Outlook connessi con Exchange attraverso RPC over HTTP potreste sperimentare qualche problema di performance.

…“On fast networks, connect using HTTP first, then connect using TCP/IP” was off. Switched on, used KnockOut to force a quick restart of Outlook.. and suddenly no more ten minute logon process!…

Fonte: Fixing an agonizingly slow Exchange / Outlook 2007 connection | blog.arithm

.m

author: Mauro Servienti | posted @ martedì 17 giugno 2008 8.01 | Feedback (1)

IServiceContainer


In un'altro post di qualche giorno fa mi è stato chiesto di approfondire alcuni concetti.

Partiamo dal primo: IServiceContainer.

Come forse avrete capito sono leggermente ossessionato dal problema delle dipendenze e siccome in moltissimi casi liberarsene costa veramente poco faccio sempre il possibile per farlo. Nello specifico in questo periodo stiamo facendo uso massiccio di Castle Windsor come motore di Inversion of Control ma la mia ossessione mi dice che non è bello dipendere da Castle perchè domani mattina potremmo volerci mettere Sping.NET o Unity... quindi?!?!

Molto semplicemente astraimo l'uso che facciamo di Castle mascherandolo. In questo momento ci stiamo avvalendo solo delle funzionalità di IoC quindi abbiamo una semplice interfaccia:

public interface IServiceContainer
{
    T GetService<T>() where T : IService; 
    IService GetService( Type serviceType ); 
    void Release( Object instance );
}

l'applicazione a sua volta a runtime, sempre via IoC (ma con una implementazione custom non con Castle naturalmente), ottiene un'istanza di una classe concreta "CastleServiceContainer" che implementa l'interfaccia in oggetto.

public sealed class ServiceContainer : IServiceContainer
{
    private static IServiceContainer _instance = new ServiceContainer();
    public static IServiceContainer GetContainer()
    {
        return _instance;
    }

    private ServiceContainer()
    {
        Type t = Type.GetType(Properties.Settings.Default.ServiceProviderType, true);
        this.provider = Activator.CreateInstance(t) as IServiceContainer;
    }

    private IServiceContainer provider;
    public T GetService<T>() where T : IService
    {
        return this.provider.GetService<T>();
    }

    public IService GetService( Type serviceType )
    {
        return this.provider.GetService( serviceType );
    }

    public void Release( Object instance )
    {
        this.provider.Release( instance );
    }
}

Infine abbiamo il "provider" vero e proprio, in questo caso quello che sfrutta (ed è l'unico che lo sa) Castle Windsor.

public class CastleServiceProvider : IServiceContainer
{
    IWindsorContainer container = null;

    public CastleServiceProvider()
    {
        this.container = new WindsorContainer(
            new XmlInterpreter(
                new ConfigResource( "applicationSettings/ServiceProvider.Configuration" )
            )
        );
    }

    public T GetService<T>() where T : IService
    {
        return this.container.GetService<T>();
    }

    public IService GetService( Type serviceType )
    {
        Object obj = this.container.GetService( serviceType );
        return ( IService )obj;
    } 

    public void Release( Object instance )
    {
        this.container.Release( instance );
    }
}

In questo modo l'applicazione sfrutta tutto quello che le serve senza sapere in realtà chi le sta fornendo questi servizi.

Quanto costa tutto ciò? forse, e ripeto forse, un pomeriggio di lavoro, se ci mettiamo che dal codice che ho postato ho rimosso la gestione delle Exception e il motore di tracing/logging; ma lo ritengo un pomeriggio decisamente ben speso! l'unico vero problema: farlo capire al cliente... ma questa è un'altra storia ;-)

.m

Technorati Tags: ,

author: Mauro Servienti | posted @ domenica 25 maggio 2008 11.44 | Feedback (5)

IChangeTrackingService


...un po' di storia: tanto tempo fa in una galassia lontana lontana... comiciai a scrivere applicazioni gestionali Windows Forms... mano a mano che il mondo evolveva mi resi conto che, forse addirittura più nel nostro lavoro che in altri, l'abito fa eccome il monaco. Con questo intendo dire che spessissimo il giudizio ultimo sul nostro lavoro lo danno "non tecnici" che si limitano a valutare le funzionalità di alto livello che il nostro prodotto offre. Da questo ritengo abbastanza facile dedurre che l'interfaccia utente, e qui credo di non dire nulla di nuovo, ricopra un ruolo fondamentale perchè è il con la UI che il nostro giudice ha il primo approccio.

Fatta questa doverosa premessa veniamo a quello che successe: un bel giorno una delle ragazze che lavoravano in contabilità da un cliente mi chiese (parlando dell'applicazione che avevo sviluppato e che lei utilizzava): "ma non ci sarebbe la possibilità di avere l'avanti e indiero come in Word?" la pima reazione fu un grosso punto di domanda... avanti e indietro?? ma che è... indagando poi scoprii che la ragazza si riferiva alle funzionalità di Undo/Redo offerte da Office.

La cosa mi incuriosì non poco e comincia a studiare il "meccanismo" (aka "pattern") che gestiva la cosa e la prima, ed unica veramente problematica, considerazione a cui giunsi era che per Word la cosa era "relativamente semplice" perchè Word sapeva molto bene cosa stava gestendo, e cioè il documento, mentre la mia velleità era quella di realizzare un motore che fosse in grado di maneggiare un generico grafo di oggetti.

La prima implementazione di tutto vide la luce qualche anno fa, e la ragazza della contabilità fu felice, realizzata con il fx 1.0. Era qualcosa di decisamente embrionale erano le stesse entities che tenevano traccia delle variazioni (o meglio solo della prima variazione) che subivano:

public string Company
{
    get { return this._company; }
    set
    {
        if(base.ObjectCache["Company"] == null)
            base.ObjectCache["Company"] = this._company;
        this._company = value;
    }
}

Avevo semplicemente un dictionary dove il nome della proprietà era la chiave e il value il valore, veniva cachata solo ed esclusivamente la prima modifica e nulla di più. L'entity esponeva poi metodi e proprietà per giocare con la cache:

  • AcceptChanges();
  • AcceptChanges( Boolean recursive );
  • RejectChanges();
  • RejectChanges( Boolean recursive );
  • GetHasChanges();
  • GetHasChanges( Boolean recursive );

I metodi con il parametro boolean permettevano di capire se l'intero grafo avesse subito modifiche o se le modifiche erano limitate al solo oggetto che si stava indagando. Il tutto naturalmente funzionava anche per le collection anche se la manutenibilità del codice era decisamente lontana dal potersi considerare accettabile.

Adesso mi vengono i brividi ;-) ma 6 e più anni fa faceva la sua porca figura... il tempo e le necessità mi hanno fatto evolvere verso qualcosa di decisamente più funzionale, e finalmente oggi, anche molto più bello dal punto di vista del design.

Partiamo dai requisiti che possono essere sintetizzati in un una parola: "Memento" (che non è solo un bellissimo film).

I problemi:

  • gestiamo un grafo non necessariamente noto a priori;
  • gestiamo un grafo che può essere internamente gestito con il "lazy loading" e non vogliamo che il modello venga caricato tutto solo perchè dobbiano gestirne le variazioni di stato;
  • vogliamo poter distinguere n track line per gestire in contemporanea grafi differenti senza che le modifiche vengano mischiate: faccio prima a fare un esempio che ha spiegare: se in VS fate modifiche su file diversi tutte le modifiche vengono tracciate ma, anche se le fate un po' qua e un po' la in maniera casuale, quando fate Undo viene fatta la cosa giusta sul file giusto ma soprattutto se fate Undo su un documento n volte vengono ripristinate solo le modifiche a quello specifico file anche se durante l'editing le avete mischiate, in quanto ad ordine, a quelle di un altro file.

Nel tempo sono passato attraverso varie implementazioni di un motore di caching dello stato di una entity, ho scritto anche un articolo che spiega (stringendo molto) quello che ho adesso in produzione, e seppur tutte funzionanti avevano un grosso difetto: delegavano alla stessa entity l'onere di fare cache del suo stato, non che ritenga la cosa così grave anche perchè il motore, che è tutt'ora usato, è decisamente smart e copre circa il 90% delle casistiche che ho incontrato sino a questo momento.

Il framework 2.0 introduce 2 nuove interfacce IChangeTracking e IRevertibleChangeTracking, peraltro inutilizzate dal fx stesso, che servono per gestire proprio un motore di caching. La cosa fuorviante in questo caso è MSDN stessa che, per IChangeTracking ad esempio, recita:

"Defines the mechanism for querying the object for changes and resetting of the changed status."

Lasciando supporre che sia la entity a dover fornire/gestire queste informazioni.

In questo periodo ci si stava ponendo però un potenziale scenario in cui avremmo sprecato un sacco di risorse (cicli CPU, non ore/uomo) inultilmente proprio perchè la gestione dello stato era delegata alla stessa entity. Non mi dilungo sui motivi, sto già scrivendo troppo oggi... ;-) avremo l'occasione per parlarne presto.... e chi ha orecchie per intendere intenda.

Dato questo incipit in questo week-end diluviante mi sono messo d'impegno e ho cercato di capire cosa potevo fare, la prima cosa è stato come sempre guardare il resto del mondo: in molti usano l'interfaccia INotifyPropertyChanged, o meccanismi simili proxando l'interfaccia pubblica di una entity, ma devo dire che a me piace veramente poco:

  • obbliga/permette di gestire le sole proprietà pubbliche;
  • il valore cachato è necessariamente quello esposto mentre potrebbe essere necessario customizzare questo processo;
  • in fase di "rollback/undo" è decisamente complesso capire, dall'interno della entity, che il set di una property è dovuto al rollback e non ad altro e questo è necessario per evitare di triggerare ancora il motore di caching portando a ricorsione;
  • va ancora ancora benino finchè stiamo tentando di tracciare le modifche ad una singola entity molto semplice, la classica Person, ma se pensiamo ad un grafo complesso, ad esempio un Customer con Orders e relativi OrderItems e Products, Addresses ed altro l'interfaccia INotifyPropertyChanged è decisamente inadeguata;

La soluzione a cui sono giunto è ancora un po' grezza ma perfettamente funzionante e decisamente più lineare dal punto di vista del design. Come al solito mi sono ispirato a quello che gia c'è, e di cui ho anche scritto, in questo caso System.Transactions.TransactionScope:

using( TransactionScope ts = new TransactionScope() )
{
    //Do something
    ts.Complete();
}

qualsiasi operazione eseguiamo o oggetto istanziamo, all'interno del blocco using, è in grado di eseguire l'enlistment nella transazione in maniera automatica senza che ci sia passaggio di alcunchè tra il blocco di codice di esempio e gli oggetti che vengono usati, a qualsiasi livello si trovino. Altra cosa interessante è che se la transazione non c'è, perchè ad esempio non abbiamo creato un oggetto TransactionScope, il codice funziona perfettamente... e non è che sia poi così scontato ;-)

Quello a cui sono giunto è questo:

IChangeTrackingServiceProvider provider = ChangeTrackingServiceProvider.GetCurrent();
provider.CreateTrackingService();

IList<Person> list = new EntityCollection<Person>();
Person p = new Person();
p.FirstName = "Mauro";
p.LastName = "Servienti";

list.Add( p );

IChangeTrackingService svc = provider.GetTrackingService();
if( svc.IsChanged )
{
    svc.RejectChanges();
}

Perchè il tutto funzioni non è necessario implementare nessuna interfaccia sulle entity che si stanno realizzando è però necessario sacrivere un minimo di codice per "collegare" i 2 mondi. Nell'esempio per la collection viene utilizzata un EntityCollection<T> che è una mia classe, che implementa IList<T> (e non solo), da cui è possibile derivare e che sgravia l'utilizzatore/inheritor da tutto il plumbing code necessario per la gestione del tracking delle modifiche all'interno di una lista (cosa non proprio banale). Mentre la classe Person deriva da una mia classe Entity e si limita ad 1 chiamata ad un metodo della classe base, nel caso in cui ci si volesse totalmente sganciare dalla mia implementazione è sempre possibile farlo, anche in maniera parziale, perchè tutto è implementato da classi concrete che derivano da classi astratte (che offrono una implementazione di base) ma che vengono sempre esposte sotto forma di interfacce: è quindi possibile, in maniera decisamente semplice, pluggare un proprio modello e personalizzare pesantemente il tutto.

Ecco quello che succede in esecuzione:

image

Non uso un blocco using, anche se IChangeTrackingService implementa IDisposable, perchè il ciclo di vita della entity, e quindi la sua gestione dello stato tendono ad andare oltre lo scope del singolo snippet di codice. Ogni metodo esposto dall'interfaccia IChangeTrackingServiceProvider ha svariati overload che permettono di raffinare/personalizzare il comportamento del motore di caching. Una volta recuparata uan reference all'IChangeTrackingService corrente oltre ad accettare o rifiutare (AcceptChanges() o RejectChanges()) è possibile eseguire un "Undo" progressivo delle modifiche apportate, banalmente una cosa del tipo:

while( svc.IsChanged )
{
    svc.Undo();
}

E' infine possibile chiedere all'IChangeTrackingService di restituirci un IChangeSet che è l'insieme delle modifiche che sta tracciando.

IChangeSet cSet = svc.GetChangeSet();

Il metodo GetChangeSet() ha, per ora, un overload che accetta un IChangeSetFilter che è un oggetto che permette di "filtrare" dall'esterno quali modifiche debbano essere incluse nel changeset che si sta costruendo. Questa possibilità è ancora molto grezza ed è legata ad una possibile collaborazione con qualcosa che implementi il pattern "Unit Of Work" (vedasi NSK per i dettagli) al fine di poter scrivere una cosa del tipo:

using( IUnitOfWork uow = IServiceContainer.GetService<IUnitOfWork>() )
{
    uow.Append( cSet );
    uow.Commit();
    cSet.AcceptChanges();
}

Anche se già funzionante la sua implementazione interna lascia molto a desiderare.
Lascio ad un futuro post i dettagli implementativi del tutto... per oggi ho già intasato troppo il muro di UGI.

.m

author: Mauro Servienti | posted @ lunedì 19 maggio 2008 18.24 | Feedback (5)

LazyLoading, collection e il VirtualMode


Oggi sono blogorroico :-D, e quindi ci metto anche il terzo... se poi "il quattro vien da se" be allora siamo proprio alla frutta...

In questo caso parliamo di qualcosa che è ancora in una versione più che embrionale... è quasi tutto ancora nella mia testa... o meglio ben dettagliato sul fido OneNote.
Il problema è questo:

IMyEntity entity = whatEverDataProvider.GetMyEntity();
IMyCollection list = entity.Children;
Int32 count = list.Count;

if( count > 100 )
{
    foreach( var child in list )
    {
        //Do something with child
    }
}

e voi direte... ma dove sta il problema? sta sta... ;-) supponiamo che la collection "Children" sia caricata in modalità lazy e che, nell'esempio, contenga 90 elementi: bene stiamo caricando 90 "item" solo per fare un confronto con il Count di questi elementi. E giustamente voi potreste ribadire: si certo... bravo, fai un metodo GetCount() sul data provider...

IMyEntity entity = whatEverDataProvider.GetMyEntity();
Int32 count = whatEverDataProvider.GetChildrenCount( entity );

...etc

+1 sono perfettamente d'accordo, ma lo sviluppatore è pigro e se trova una proprietà Count usa quella anche se le policy, le regole, gli accordi o le litigate dicono diversamente... quindi mi sono chiesto, pensando al deferred loading di linq, ma perchè non introdurre questo concetto in una collection custom? e quindi avere la collection istanziata, ma vuota e solo ed esclusivamente al primo tentativo di lettura, ad esempio un GetEnumerator(), caricare effettivamente i dati; se poi siamo veramente smart potremmo implementare delle logiche per ad esempio ritornare il solo "Count" all'atto della richiesta.

A questo punto la domanda sicuramente è: ma perchè ti vuoi fare del male da solo e non usi un ORM? me lo sono chiesto anche io... e mi sono risposto:

  1. Il cliente non ne vuole sapere. Si lo so non è una scusa valida ;-);
  2. Il team in questo momento, compreso il sottoscritto, ha miseri skill su NH e il lavoro è già abbastanza complesso di suo senza introdurre altre problematiche;
  3. Abbiamo requisiti che la "mia fonte" dice non essere esaudibili con NH;
  4. varie ed eventuali...;

.m

Technorati Tags:

author: Mauro Servienti | posted @ venerdì 16 maggio 2008 18.38 | Feedback (3)

Logica applicativa nelle SP...?


...lungi da me ;-), ma... ebbene si secondo me c'è sempre un ma.

In questo periodo, decisamente massacrante, stiamo lavorando ad una soluzione potenzialmente abbastanza complessa e potenzialmente molto distribuita ;-) quindi uno dei problemi è che abbiamo n tier che vengono coinvolti dal ciclo di vita di una entity o da una operazione di business; e questo mi fa sempre dire che ogni tier/layer è storia a se, non sa nulla di chi sta prima e sa molto poco di chi sta dopo... e se assieme a questo assioma ci mettiamo che il dato è sacro (meglio un service unavailable che una information disclosure, qualcuno disse: "it sucks, but it sucks less") allora è facile asserire e sostenere che, ad esempio, i controlli sulla validità del dato in ingresso devono essere fatti a qualsiasi livello e siccome uno di questi livelli è il database io ce li metto pure li.

.m

<update>
A seguito del commento di Igor forse è giusto che faccia un approfondimento. Quando dico che il dato è sacro intendo dire che è sacro per ogni tier a modo suo. Ogni tier ha una sua visione del dato e ha un suo modo per ritenerlo valido, quindi ha una sua logica di validazione che è decisamente diversa da tier a tier. Questo significa che se la business rule dice che il "Cognome" non deve essere più di x caratteri è un problema applicativo e al db non interessa minimamente mentre al db ad esempio interessa e molto che il dato che arriva non corrompa lo consistenza dei dati che già sono presenti, e di certo un Cognome troppo lungo non corrempe un bel nulla ;-)

Un'altro apsetto fondamentale di tale logica è ad esempio la validazione dei parametri/argomenti di un metodo, come lo faccio nel mio codice C# lo faccio anche nel codice T-Sql. E qui non c'è santo che tenga se una enumerazione può assumere 3 valori devo controllare che quello che arriva sia consistente.

Dipende tutto dal punto in cui ci si trova a guardare il flusso.

Resta il fatto che se hai un'applicazione distribuita devi prevedere che ci possa essere il "furbetto" che cerca di chiamarti direttamente il servizio WCF passando dei dati malformati solo per il gusto di vederlo schiantare.

per quel che riguarda infine le business rules tendo a centralizzare molto, una cosa del tipo:

IServiceContainer sc = ServiceContainer.GetContainer();
using( IValidationService svc = sc.GetService<IValidationService>() )
{
    IValidationContext ctx = MyEntityValidationContext( entity, action, bla, bla, bla );
    IValidationResult result = svc.Validate( ctx );
}
</update>

Technorati Tags: ,

author: Mauro Servienti | posted @ venerdì 16 maggio 2008 12.40 | Feedback (6)

Il database è al mio servizio...


... e non viceversa. Con questo intendo dire che il modello ad oggetti che utilizziamo non dovrebbe essere mai modellato, in nessuna delle sue parti, pensando al fatto che quello specifico modello ad oggetti dovrà essere mappato su di un modello relazionale.

Perchè dico questo? perchè spesso vedo, o semplicemente sento, che questo concetto viene in alcuni casi "dimenticato" a favore della semplicità (ma è poi vera questa semplicità o è solo apparente?) di implementazione della relazione tra i due mondi.

Ma andiamo con ordine, ieri "chiaccherando" di architettura con il collega Marco, sono stato simpaticamente tacciato di farmi un sacco si "segh* mentali" ed io candidamente ho riposto che se il mio lavoro potesse essere fatto di magagne architetturali e segh* mentali che devono portare ad una soluzione elegante, performante e di semplice utilizzo sarei felicissimo. Quando ci sono problemi tecnici da risolvere io sono effettivamente a mio agio e sotto pressione poi rendo anche parecchio.

Perchè sono stato additato come "puntiglioso"? perchè eravamo di fronte al classico problema di dover gestire il rapporto del nostro modello ad oggetti con il database nel momento in cui di mezzo c'è una relazione "molti a molti".

Facciamo un esempio, decisamente simile alla realtà dei fatti, immaginiamo di avere una rappresentazione in memoria di un albero con però ha la caratterisitca che questo albero oltre ad avere la classica relazione padre-figlio/i ha anche la possibilità di avere, come "figli" di un nodo, dei link ad altri nodi, potremmo paragonarli ai link che su filesystem metteremmo in una folder per puntare ad un'altra folder sempre su filesystem.

Il nostro modello è quindi così rappresentabile:

interface INode
{
    INode Parent { get; }
    String Name { get; set; }
    INodeList Nodes { get; }
    INodeList Links { get; }
}

interface ITree : INode
{
}

faccio notare come sia Nodes che Links abbiano lo stesso tipo di ritorno.
Questo modello, nello specifico la collection Links, è una classica relazione molti a molti che sul db possiamo rappresentare con una tabella di ponte: "NodesTable" <--> "Links" <--> "NodesTable", dove in Links abbiamo la PK della Source e la PK della Destination.

In molti casi lo sviluppatore si arrende e per rappresentare quella relazione modella il suo dominio come se lo trova sul db, quindi modella una entità INodeLink, con la relativa collection, ed espone quella, ma questa cosa anche se non necessariamente sbagliata secondo me è comunque una di quelle cose che potrebbero ricadere sotto il cappello del "code smell". Sia chiaro ci sono dei casi in cui questo modello ha decisamente senso, anche se anche qui ci sono soluzioni probabilmente più eleganti, pensiamo ad esempio alla relazione "Studenti <--> EsamiSostenuti <--> Esami" in cui abbiamo bisogno di aggiungere informazioni (metadati?) oltre alla semplice relazione.

Tutto questo per arrivare a fare una cosa che per lo sviluppatore è decisamente naturale, e cioè una cosa del tipo:

ITree sourceTree = null;
ITree destTree = null;

IServiceContainer sc = ServiceContainer.GetContainer();
using( ITreeDataProvider dp = sc.GetService<ITreeDataProvider>() )
{
    sourceTree = dp.GetByKey( pk );
    destTree = dp.GetByKey( pk );
}

INode sourceNode = sourceTree.Nodes[ 0 ];
INode destNode = destTree.Nodes[ 3 ];
sourceNode.Links.Add( destNode );

IChangeSet cSet = null;
using( IChangeTrackingService<ITree> ctSvc = sc.GetService<IChangeTrackingService<ITree>>() )
{
    cSet = ctSvc.GetChangeSet( sourceTree );
}

using( IUnitOfWork uow = sc.GetService<IUnitOfWork>() )
{
    uow.Append( cSet );
    uow.Commit();
    cSet.AcceptChanges();
}

il bello è che funziona pure... e vista l'architettura che abbiamo messo in piedi ci sono volute un paio d'ore (dalla stesura delle classi alla creazione delle tabelle/sp sul db) per introdurre il "concetto" nel nostro mondo.
Direi che anche oggi (o meglio stamattina dalle 4.30 alle 6.30) ci siamo guadagnati una parte della pagnotta, che abbiamo poi consumato con il consueto training fisico mattutino...

il mattino ha l'oro in bocca, il mattino ha l'oro in bocca, il mattino ha l'oro in bocca <cit.>, che debba comiciare a preoccuparmi?
Noooooo finchè non chiamo Andrea "Wendy" direi che non ci dovrebbero essere problemi :-D

Detto questo direi che è doveroso ricordare che se vi può interessare capire cosa c'è dietro quegli snippet di codice siete i benvenuti: "cercasi".

.m

Technorati Tags:

author: Mauro Servienti | posted @ venerdì 16 maggio 2008 11.51 | Feedback (5)