17 giu 2011

Un DAL apolide (seconda parte)

Proseguiamo l'analisi sull'approccio migliore per la scrittura di un DAL che risponda a richieste di più client in contemporanea (eventualmente smistate su più database):

Consideriamo per ora l'ipotesi semplificativa di DAL che debba connettersi ad un solo db, con un unica stringa di connessione. Scartiamo l'idea di aprire una connessione unica all'avvio dell'applicazione, perché stiamo parlando di architetture distribuite, in cui le richieste di servizio non sono contigue: non vogliamo occupare una risorsa del db se non ne facciamo uso: apriremo una connessione solo quando un client ci chiederà un servizio.

Si osservi che, per il nostro DAL, il servizio richiesto dal client coincide con una funzione pubblica.

Dobbiamo aprire la connessione all'avvio della funzione pubblica e chiuderla alla fine, prima di restituire al client la risposta.

Come viene effettuata l'apertura della connessione? Tipicamente avremo una libreria di accesso allo specifico database, a cui chiederemo la connessione. Qui occorre operare una scelta: la libreria ci fornisce una nuova connessione creata apposta, oppure un puntatore ad un'unica connessione?

Nella prima figura immaginiamo che tre client invochino quasi contemporaneamente tre funzioni pubbliche, e che il DAL, per ogni funzione, richieda al database la connessione.

 Se l'architettura applicativa prevede che il database restituisca tre puntatori all'unica connessione, alla fine della funzione 3 la connessione verrà chiusa, le funzioni 1 e 2 che sono ancora in esecuzione corrono il rischio di effettuare operazioni su una connessione chiusa, generando errori.

Questo tipo di architettura può funzionare se e solo se esiste un solo client che invoca i metodi pubblici del DAL, e se li invoca in ordine strettamente seriale; qualsiasi parallelismo porterà certamente a condizioni d'errore. Quindi per un ambiente distribuito occorre operare una scelta diversa.

Nella seconda figura il database crea una nuova connessione per ognuna delle tre funzioni: questo ci permette di rispondere a più client in parallelo.

 Per quanto riguarda le performance, questo ultimo approccio naturalmente pone un dubbio: la continua creazione, apertura e chiusura di connessioni può essere un punto di attenzione a livello di prestazioni.

In ambiente .NET, le best practices ADO.NET suggeriscono di fare uso del connection pool, delegando al framework l'effettiva chiusura ed apertura delle connessioni: si veda http://msdn.microsoft.com/en-us/library/8xx3tyca(vs.71).aspx.

08 giu 2011

Campi blob in Oracle: come ottenerne la dimensione

Per ottenere la dimensione dell'oggetto contenuto in un campo BLOB di una tabella in Oracle, è sufficiente invocare una query di questo tipo:
SELECT dbms_lob.getlength(NOMECAMPOBLOB) FROM NOMETABELLA;

07 giu 2011

Compressione traffico IIS per RIA Services

Sviluppando applicazioni Silverlight / RIA, è possibile incorrere in un problema prestazionale in fase di rilascio: la quantità di traffico scambiato tra il servizio RIA ed il client silverlight (traffico che ha la forma di una serializzazione xml delle entities) appare piuttosto consistente: a titolo di esempio, una classica query di ricerca sul progetto CDS, che ritorna un numero di entities non superiore a 60 per riempire una griglia (paginata), può tranquillamente generare una risposta che raggiunge le dimensioni di 15-20 megabytes, con punte di quasi 30 megabytes.
la dimensione del traffico è dovuta alla procedura di serializzazione del servizio WCF, che trasforma le istanze delle classi in un nodo xml, accorpando tutte le informazioni per deserializzare il dato ottenendo l’istanza sul client silverlight; nel caso dei verbali le componenti della classe sono numerose, quindi è vasta la descrizione che viene inviata come traffico http (o https)
  • Una strada per ottenere una riduzione del traffico è quella di bypassare la serializzazione WCF e scriversi una routine ottimizzata che serializza le entities anziché su un xml, su un flusso di dati binari; occorre anche prevedere una routine di deserializzazione con la capacità di individuare errori nel flusso e richiederne la ritrasmissione. A monte della serializzazione "custom" e dopo la relativa deserializzazione devono rimanere valide le entities del RIA service, per continuare a giovarsi delle avanzate funzionalità introdotte con Silverlight 4. Questa strada appare immediatamente onerosa in termini di effort e rischiosa.
  • Un’altra strada è quella di continuare a giovarsi del servizio WCF per la serializzazione, e ridurre il traffico comprimendo a valle l’xml generato, usando un modulo di IIS (peraltro già attivo automaticamente in molte installazioni di IIS 7).
Questa ultima soluzione può sembrare un workaround, ma in realtà rappresenta la risposta migliore al problema del traffico: anche solo perché poggia su componenti software Microsoft già presenti sulle macchine, sicuramente testati e in produzione da anni.
Inoltre, questa soluzione ha il vantaggio di disassare il problema applicativo (già risolto nel caso CDS) dalle considerazioni prestazionali, permettendoci cioè di fare la cosa giusta: occuparci del design e della logica di business, senza deformare il modello per rispondere ad una problematica esterna, quale quella dell'efficienza trasmissiva, almeno un livello OSI sotto (ma probabilmente due) la nostra web-application.
Schematizziamo:
Problema: persistenza e gestione paradigma object oriented in ambiente distribuito.
Risposta: applicazione implementata sulle fondamenta di Silverlight 4 e RIA services
Problema: traffico elevato tra client e server
Risposta: compressione del solo traffico con procedure delegate ad IIS (lato server) e al browser (lato client)

Prova sul campo

Nelle prove effettuate il tempo di trasmissione di dati compressi è molto inferiore al corrispondente tempo per dati non compressi; la compressione è in carico al server, mentre l’operazione di decompressione è in carico al browser (i browser moderni gestiscono automaticamente questa funzionalità); il codice dell’applicazione non è stato toccato (non è stato nemmeno ripubblicato), ci siamo limitati a delegare ad IIS la compressione automatica dei dati xml in uscita dal servizio.
Abbiamo impostato IIS 6 sul server di test in modo che comprima automaticamente i contenuti, se nella GET del browser è impostato l’attributo

Accept-Encoding: gzip, deflateAccept-Encoding: gzip, deflate

I tempi (in connessione vpn) sono passati da 24 secondi a 5 secondi.

Link utili:

06 giu 2011

La teoria: normalizzazione di un database

Il termine normalizzazione indica il processo di organizzazione dei dati in un database. Tale processo comprende la creazione di tabelle e la definizione di relazioni tra di esse sulla base di regole progettate in modo da proteggere i dati e rendere il database più flessibile mediante l'eliminazione della ridondanza e delle dipendenze incoerenti.
Per la normalizzazione dei database è necessario seguire alcune regole, ciascuna delle quali viene definita "forma normale". Se si osserva la prima regola, il database viene considerato nella "prima forma normale". Se si osservano le prime tre regole, il database viene considerato nella "terza forma normale". Sebbene siano possibili altri livelli di normalizzazione, la terza forma normale è considerata il livello massimo necessario per la maggior parte delle applicazioni.


Poiché la normalizzazione richiede in generale l'uso di tabelle aggiuntive, viene considerata troppo dispendiosa da alcuni clienti. 

  • Prima forma normale: Una tabella di un database si dice "tabella in prima forma normale" se,e solo se, le informazioni presenti in due colonne sono identiche e quindi abbiamo dati ripetuti. Per portare il database alla prima forma normale, basta eliminare una delle due colonne doppie. Ad esempio, proviamo a pensare cosa succederebbe in termini sia di tempo e di spazio se in una tabella comparissero due campi con nome diversi ma contenenti il nome ed il cognome della persona in questione. Lo spazio sprecato sarebbe molto di più e anche le ricerche (avendo più dati da consultare) si allungherebbero come tempi.
  • Seconda forma normale: Una tabella di un database si dice "tabella in seconda forma normale" se, e solo se, non vi è presente al suo interno una colonna che contiene dati che possono derivare da altre colonne presenti all'interno della stessa tabella. Prendiamo come esempio un catalogo prodotti di fine 2001. Ormai è inutile inserire all'interno della tabella una colonna contenente i prezzi in lire ed una coi prezzi in euro. Infatti per ottenere un dato dall'altro basta solamente dividere (se dalle lire si vuol passare all'euro) o moltiplicare (se dall'euro si vuol passare alle lire) per il valore 1936.27 . Se in un database troviamo entrambi le colonne dei prezzi, questa tabella non si può ritenere di seconda forma normale. Se invece è presente solo un prezzo e l'altro lo ricaviamo tramite un semplice calcolo, questa tabella è considerata uniforme alla seconda forma di normalizzazione.
  • Terza forma normale: Un database si dice di terza forma normale se e solo se non vi sono dati duplicati all'interno dello stesso. Questo tipo di normalizzazione dobbiamo vederlo come l'ampliamento della prima. Mentre nella prima non vi devono essere dati ripetuti nella stessa tabella, qui la ripetizione dei dati non deve avvenire nelle colonne delle tabelle del database. Basta infatti una sola colonna con determinati valori e poi ci si relaziona a quella. L'adozione della terza forma normale non è sempre pratica anche se teoricamente auspicabile. Se si dispone di una tabella Clienti e si desidera eliminare tutte le possibili dipendenze tra campi, è necessario creare tabelle separate per città, CAP, rappresentanti, classi di clienti e gli eventuali altri fattori che possono essere duplicati in più record. Nella teoria la normalizzazione è sempre auspicabile, tuttavia l'utilizzo di un numero elevato di tabelle di dimensioni limitate può determinare una riduzione del livello delle prestazioni oppure richiedere capacità di memoria e di apertura dei file superiori a quelle disponibili. 
  • Altre forme di normalizzazione: sono inoltre disponibili una quarta forma normale, denominata Boyce Codd Normal Form (BCNF), e una quinta forma anche se vengono raramente prese in considerazione nella progettazione pratica. Il mancato utilizzo di queste regole può non consentire una perfetta progettazione del database, senza tuttavia influire negativamente sulla funzionalità.

Nota: può risultare appropriato applicare la terza forma normale solo ai dati soggetti a frequenti modifiche. Se sussistono dei campi dipendenti, progettare l'applicazione in modo da richiedere la verifica di tutti i campi correlati in caso di modifica di un campo.

02 giu 2011

La teoria: integrità relazionale


Andiamo a definire le regole di integrità all'interno del modello relazionale:

Chiavi primarie, candidate ed alternative

Sia R una relazione, con attributi A1, A2,..., An.
Il sottoinsieme di attributi K = (Ai, Aj,..., Ak) è detta chiave candidata se e solo se soddisfa le due seguenti proprietà:
  1. Unicità: in ogni momento, due distinte tuple di R non avranno mai lo stesso valore per gli attributi Ai, Aj,..., Ak.
  2. Minimalità: Nessuno tra gli attributi Ai, Aj,..., Ak può essere rimosso da K senza distruggere il vincolo di unicità
Ogni relazione ha almeno una chiave candidata, perché (come minimo) la combinazione di tutti i suoi attributi ha la proprietà di unicità.
Per una data relazione, una chiave candidata venga arbitrariamente designata come chiave primaria; le chiavi candidate rimanenti (se esistono) saranno dette chiavi alternative.

Chiavi esterne

Una chiave esterna è un attributo, o una combinazione di attributi, di una relazione R2 i cui valori corrispondono a quelli della chiave primaria di un'altra relazione R1. R1 ed R2 non devono necessariamente essere distinte.

Regole d'integrità

Il modello relazionale comprende due regole generali sull'integrità, espresse nei termini di chiavi primarie ed esterne. Le regole sono "generali" nel senso che ogni DBMS che affermi di conformarsi al modello relazionale deve soddisfarle.
  1. Integrità delle entità: nessun attributo che partecipi alla chiave primaria di una relazione può accettare valori NULL.
  2. Integrità relazionale: se la relazione R2 include una chiave esterna FK che corrisponda alla chiave primaria PK di un'altra relazione R1, allora ogni valore di FK in R2 deve alternativamente:
  • essere uguale al valore di PK in qualche tupla di R1, oppure
  • essere composta esclusivamente di valori NULL
R1 ed R2 non devono necessariamente essere distinte.

Nota: la terminologia del modello relazionale si traduce con la nomenclatura standard delle implementazioni RDBMS: relazione -> tabella; attributo -> colonna; tupla -> riga.
In generale la relazione R può essere realizzata tanto da una tabella quanto da una vista; si consideri però che le regole di integrità si applicano esclusivamente a tabelle reali e non a viste. 

Un DAL apolide (prima parte)

Nel corso dei progetti CDS (per aipa) e LabDB (per trw) abbiamo cercato di creare uno strato di software che permettesse, fatti salvi alcuni punti vincolanti che andremo a elencare nel seguito, di delegare la persistenza degli oggetti su un database (oracle, sql server o mysql) - senza dover scrivere esplicitamente le query per ogni oggetto.

Funzionalità 
Nel dettaglio, questo layer dovrebbe, senza altre informazioni che il tipo coinvolto (ovvero la definizione di classe) e la stringa di connessione, essere in grado di generare:

  1. select di tutti i record (select * from tabella)
  2. select di un record in base alla chiave primaria (select * from tabella where primarykey=x) 
  3. update di un record (update tabella set campo1=valore1, campo2=valore2, .... where primarykey=x) 
  4. delete di un record (delete from tabella where primarykey=x) 


Vincoli e Limitazioni
I vincoli che vanno rispettati sono:

  • gli oggetti del modello devono essere definiti con proprietà pubbliche ed attributi privati. Le proprietà pubbliche sono usate dal DAL per riempire i campi della tabella corrispondente alla classe. 
  • il modello ad oggetti deve essere definito in modo per cui ogni elemento sia contraddistinto da una ed una sola chiave primaria generata automaticamente (per esempio, un autoincrementale del db, oppure un GUID generato da un'authority) 
  • se nel modello c'è un riferimento tra la classe C1 e la classe C2 (relazione 1:n), allora sul database occorre che venga registrata la chiave della riga referenziata (della tabella C2) in un campo del record corrispondente all'oggetto che referenzia (sulla tabella C1) - esempio: la classe veicolo ha una proprietà ptarga, puntatore alla classe targa; sul database il campo ptarga della tabella veicolo conterrà una chiave esterna che referenzia la chiave primaria (id autoincrementale o GUID) della tabella targa. 
  • non gestiamo le relazioni n:m, perché occorrerebbe gestire tabelle di relazione con la reflection, cosa che introduce una complessità non giustificabile per i vantaggi offerti. 
  • di norma vengono salvate sul database tutte le proprietà degli oggetti; se si desidera creare una proprietà di comodo, cui non corrisponda un campo della tabella, si preponga alla definizione della proprietà l'attributo [Support]


Attenzione a... 
Un ulteriore problematica da tenere in considerazione è la corretta apertura e chiusura delle connessioni al database; come regola generale diciamo che:

Ogni funzione pubblica esposta dal DAL deve creare una connessione (possibilmente facendosela fornire dal connection pool), quindi aprire un blocco try all'interno del quale incorporare la logica di accesso ai dati, e terminare con un finally all'interno del quale la connessione va chiusa.

Tutte le chiamate private (interne al DAL stesso) NON devono più aprire la connessione, ma, alternativamente:
  • farsela passare come parametro (se il DAL prevede connessioni a più database) 
  • usare una proprietà privata che contiene la connessione aperta (se il DAL accede sempre ad un unico db e se risponde ad un unico client) 

in pratica la seconda opzione è percorribile solo se si sviluppa una soluzione rich-client (Win32, WinForm...) - mentre per le soluzioni distribuite (Web-services, Silverlight, ASP.NET, ecc..) occorre usare la prima tecnica.

Nella seconda parte dell'articolo vedremo in dettaglio le caratteristiche di un DAL inserito in un'architettura software distribuita.