Introduzione
Questo documento concetto fornisce una panoramica dei modelli di oggetto e di dati relazionali e fornisce una
descrizione di riepilogo di un framework di persistenza.
Database
relazionali e orientamento oggetti
I database relazionali e l'orientamento oggetti non sono completamente compatibili. Essi rappresentano due differenti
viste del mondo: in RDBMS, vengono visualizzati solo dati; in un sistema OO (object-oriented), viene visualizzata solo
la funzionalità. Nessuna di queste due prospettive è migliore dell'altra: il modello OO (object-oriented) tende a
funzionare bene per sistemi con funzionalità complessa e specifica dello stato in cui i dati sono secondari o in
sistemi in cui l'accesso ai dati avviene tramite navigazione in una gerarchia naturale (ad esempio, elenchi di
materiali). Il modello RDBMS è adatto per la creazione di report delle applicazioni e di sistemi in cui le relazioni
sono dinamiche o ad-hoc.
Ciò che conta è che molte informazioni vengono memorizzate nei database relazionali e, se le applicazioni OO
(object-oriented) desiderano accedere a tali dati, necessitano delle autorizzazioni alla lettura e scrittura a un
RDBMS. Inoltre, i sistemi OO (object-oriented) spesso necessitano la condivisione dei dati con sistemi non OO
(object-oriented). E' quindi naturale utilizzare un RDBMS come meccanismo di condivisione.
Sebbene la progettazione relazionale e quella OO (object-oriented) condividano alcune caratteristiche comuni
(l'attributo di un oggetto è concettualmente simile a una colonna di entità), differenze fondamentali rendono una sfida
un'integrazione priva di interruzioni. La differenza fondamentale è che i modelli di dati visualizzano i dati (tramite
valori di colonna), mentre i modelli di oggetti li nascondono (racchiudendoli dietro le relative interfacce pubbliche).
Il modello relazionale è composto da entità e relazioni. Un'entità può essere una tabella fisica o una proiezione di
diverse tabelle, note anche come vista. La figura seguente illustra le tabelle LINEITEM, ORDER e PRODUCT e le
rispettive relazioni. Un modello relazione presenta i seguenti elementi:
Modello relazionale
Un'entità contiene delle colonne. Ogni colonna è identificata da un nome e da un tipo. Nella figura precedente,
l'entità LINEITEM contiene le colonne LineItem_Id (la chiave primaria), Description, Price, Quantity, Product_Id e
Order_Id (le ultime due sono chiavi esterne che collegano l'entità LINEITEM alle entità ORDER e PRODUCT).
Un'entità contiene record o righe. Ogni riga rappresenta una serie univoca di informazioni che, generalmente,
rappresentano i dati permanenti di un oggetto.
Ogni entità contiene una o più chiavi primarie. LineItem_Id è la chiave principale per LINEITEM.
Il supporto per le relazioni è specifico del fornitore. L'esempio illustra il modello logico e la relazione tra le
tabelle PRODUCT e LINEITEM. Nel modello fisico, le relazioni vengono generalmente implementate utilizzando i
riferimenti chiave esterna / chiave primaria. Se un'entità è correlata a un'altra, essa conterrà colonne che sono
chiavi esterne. Le colonne di chiavi esterne contengono dati che possono correlare specifici record nell'entità
all'entità correlata.
Le relazioni presentano una molteplicità (nota anche come cardinalità). Le cardinalità comuni sono uno a uno (1:1), uno
a molti (1:m), molti a uno (m:1) e molti a molti (m:n). Nell'esempio, LINEITEM presenta una relazione 1:1 con PRODUCT e
PRODUCT presenta una relazione 0:m con LINEITEM.
Un modello di oggetto contiene, tra gli altri elementi, classi (consultare [UML01] per una
definizione completa di un modello di oggetto). Le classi definiscono la struttura e la funzionalità di una serie di
oggetti, talvolta denominati istanze di oggetti. La struttura è rappresentata come attributi (valori
di dati) e associazioni (relazioni tra classi). La seguente figura illustra un semplice diagramma di classe,
visualizzando solo gli attributi (dati) delle classi.
Modello di oggetto (Diagramma della classe)
Un ordine dispone di un numero (il numero di ordine) e di un'associazione a 1 o più (1..*) voci di riga. Ogni voce di
riga presenta una quantità (la quantità ordinata).
Il modello di oggetto supporta l'ereditarietà. Una classe può ereditare i dati e la funzionalità da un'altra classe (ad
esempio, i prodotti SoftwareProduct e HardwareProduct ereditano gli attributi e i metodi dalla classe Product).
La maggior parte delle applicazioni business utilizza la tecnologia relazionale come archivio dati fisico. La sfida per
gli sviluppatori delle applicazioni OO (object-oriented) è di separare e incapsulare il database relazionale in modo
tale che le modifiche nel modello di dati non interrompano il modello di oggetto e viceversa. Esistono numerose
soluzioni che consentono alle applicazioni di accedere direttamente ai dati relazionali; la sfida è raggiungere
un'integrazione senza interruzioni tra il modello di oggetto e il modello di dati.
Le API (application programming interface) del database vengono fornite con caratteristiche standard (ad esempio API
Open Data Base Connectivity di Microsoft o ODBC) e sono proprietarie (collegamenti nativi a database specifici). Le API
forniscono dei servizi di passaggio DML (data manipulation language) che consentono alle applicazioni di accedere ai
dati relazionali non elaborati. In applicazioni OO (object-oriented), i dati devono essere sottoposti a una traduzione
relazionale degli oggetti prima di potere essere utilizzati dall'applicazione. Ciò richiede una considerevole quantità
di codice dell'applicazione per tradurre i risultati non elaborati dell'API del database API in oggetti
dell'applicazione. Lo scopo del framework relazionale degli oggetti è incapsulare in modo generico l'archivio dati
fisico e fornire i servizi di traduzione oggetti appropriati.
Scopo di un framework di persistenza
Gli sviluppatori dell'applicazione impiegano oltre il 30% del proprio tempo nell'implementazione dell'accesso al
database relazionale in applicazioni OO (object-oriented). Se l'interfaccia relazionale degli oggetti non viene
implementata correttamente, l'investimento risulta inutile. L'implementazione di un framework relazionale degli oggetti
cattura tale investimento. E' possibile riutilizzare il suddetto framework in applicazioni successive, riducendo il
costo di implementazione relazionale degli oggetti a meno del 10% dei costi di implementazione totali. Il costo più
importante da considerare in caso di implementazione di un sistema è la manutenzione. Oltre il 60% dei costi totali di
un sistema durante il relativo ciclo di vita può essere attributo alla manutenzione. Un sistema relazionale di oggetti
implementato in modo non corretto è un incubo della manutenzione sia a livello tecnico che finanziario.
-
Prestazioni. E' necessario considerare alcuni elementi durante la scomposizione di oggetti in dati
e la composizione di oggetti dai dati. In sistemi in cui la produttività dei dati è elevata e critica, ciò
costituisce spesso un tallone di Achille di un livello di accesso progettato in modo inadeguato.
-
Riduzione al minimo dei compromessi di progettazione. Un modello familiare per tecnologi di
oggetti che hanno creato sistemi che utilizzano database relazionali, è cambiare il modello di oggetto per
facilitare la memorizzazione in sistemi relazionali e modificare il modello relazionale per una memorizzazione di
oggetti più semplice. Sebbene siano spesso necessari modifiche insignificanti, un livello di accesso ben progettato
riduce al minimo il degrado di progettazione sia del modello relazionale che dell'oggetto.
-
Estendibilità. Il livello di accesso è un framework scatola bianca, che consente agli sviluppatori
dell'applicazione di estendere il framework nel caso, al suo interno, occorra aggiungere una funzionalità.
Generalmente, un livello di accesso supporterà, senza estensione, il 65-85% dei requisiti di memorizzazione dati di
un'applicazione. Se il livello di accesso non è progettato come framework estendibile, raggiungere l'ultimo 35-15%
dei requisiti di memorizzazione dati di un'applicazione potrebbe essere molto difficile e dispendioso.
-
Documentazione. Il livello di accesso è sia un componente scatola nera che un framework scatola
bianca. L'API del componente scatola nera deve essere definita in modo chiaro, ben documentata e compresa in modo
semplice. Come indicato in precedenza, il livello di accesso viene progettato per essere esteso. Un framework
estendibile deve essere documentato in modo estremamente preciso. Occorre identificare le classi che si desidera
sottoclassare. Le caratteristiche di ogni protocollo della classe rilevante devono essere specificate (ad esempio
pubblica, privata, protetta, finale, ...). Inoltre, una parte sostanziale di progettazione del framework del
livello di accesso deve essere esposta e documentata per facilitare l'estendibilità.
-
Supporto per mappature comuni relazionali degli oggetti. Un livello di accesso dovrebbe fornire un
supporto per alcune mappature relazionali di base degli oggetti, senza necessità di estensione. Tali mappature
vengono discusse ulteriormente in una sezione successiva di questo documento.
-
Interfacce di persistenza: In un'applicazione OO (object-oriented), il modello di business per
un'applicazione di oggetti cattura la conoscenza semantica del dominio del problema. Gli sviluppatori dovrebbero
manipolare e interagire con gli oggetti senza doversi preoccupare troppo dei dettagli di richiamo e di
memorizzazione dati. Una sottoserie ben definita di interfacce persistenti (di salvataggio, eliminazione,
rilevazione) dovrebbe essere fornita agli sviluppatori dell'applicazione.
Stanno emergendo modelli comuni per applicazioni relazionali degli oggetti. I professionisti di IT che hanno
ripetutamente oltrepassato il baratro, stanno iniziando a comprendere e riconoscere determinate strutture e
funzionalità che evidenziano applicazioni relazionali degli oggetti. Tali strutture e funzionalità sono state
formalizzate dalle specifiche dei servizi CORBA di livello elevato (che si applicano in modo altrettanto adeguato a
sistemi basati su COM/DCOM).
Le specifiche del servizio CORBA applicabili e utili da considerare per la mappatura relazionale degli oggetti sono:
Le seguenti sezioni utilizzeranno tali categorie per strutturare una discussione di servizi comuni relazionali degli
oggetti. Si incoraggia il lettore a fare riferimento alle specifiche CORBA appropriate per ulteriori dettagli.
La persistenza è un termine utilizzato per descrivere il modo in cui gli oggetti utilizzano un mezzo di memorizzazione
secondario per mantenere il proprio stato attraverso sessioni distinte. La persistenza fornisce all'utente la capacità
di salvare gli oggetti in una sessione e di accedervi in una sessione successiva. Una volta ottenuto l'accesso ad essi,
il loro stato (ad esempio, gli attributi) rimarrà esattamente uguale a com'era nella sessione precedente. In sistemi
con più utenti, ciò potrebbe non verificarsi, poiché altri utenti possono accedere e modificare gli stessi oggetti. La
persistenza è correlata ad altri servizi discussi in questa sezione. La considerazione delle relazioni, della
simultaneità e di altri elementi è intenzionale (e coerente con la scomposizione di CORBA dei servizi).
Esempi di servizi specifici forniti dalla persistenza sono:
-
Gestione connessione dell'origine dati: le applicazioni relazionali degli oggetti devono avviare
la connessione all'origine dati fisica. I sistemi di database relazionali generalmente richiedono l'identificazione
del server e del database. La specifica di gestione connessione tende ad essere specifica del fornitore database e
il framework deve essere progettato e adattato di conseguenza in modo flessibile.
-
Richiamo degli oggetti: Quando gli oggetti vengono ripristinati dal database, i dati vengono
richiamati dal database e tradotti in oggetti. Questo processo implica l'estrazione dei dati da strutture
specifiche del database richiamate dall'origine dati, l'ordinamento dei dati dai tipi di database nei tipi e/o
classi di oggetti appropriati, la creazione dell'oggetto appropriato e l'impostazione di attributi degli oggetti
specifici.
-
Memorizzazione di oggetti: Il processo di memorizzazione di oggetti rispecchia il richiamo di
oggetti. I valori degli attributi appropriati vengono estratti dall'oggetto, una struttura specifica del database
(ad esempio, una stringa SQL, una procedura memorizzata o una chiamata di procedura remota) viene creata con tali
valori e inoltrata al database.
-
Eliminazione oggetto: Per gli oggetti eliminati dal sistema, è necessario eliminare dal database
relazionale anche i relativi dati associati. L'eliminazione degli oggetti richiede l'estrazione delle informazioni
appropriate dall'oggetto, la creazione di una richiesta di eliminazione (ad esempio, una stringa SQL, una procedura
memorizzata o una chiamata di procedura remota) e l'inoltro della richiesta al database. Tenere presente che, in
alcuni linguaggi, (ad esempio, Smalltalk e Java), l'eliminazione esplicita non è supportata; al contrario, è
supportata una strategia denominata raccolta dati obsoleti. I framework di persistenza che
supportano tali linguaggi devono fornire un modo alternativo per la rimozione dei dati dal database una volta che
le applicazioni non fanno più riferimento ai dati. Una modalità comune per il database è il mantenimento dei
conteggi di riferimento del numero di volte in cui altri oggetti fanno riferimento a un oggetto.
Quando il conteggio di riferimento per un oggetto cade su zero, nessun altro oggetto fa riferimento ad esso e
può essere possibile eliminarlo. E' possibile eliminare oggetti con un conteggio
di riferimento zero, poiché anche quando non si fa più riferimento a un oggetto, è comunque possibile interrogarli.
Un criterio del database relativo a quando l'eliminazione dell'oggetto è consentita è comunque necessario.
La memorizzazione persistente di un oggetto ha poco senso senza un meccanismo di ricerca e richiamo di oggetti
specifici. Le funzioni della query consentono alle applicazioni di interrogare e richiamare gli oggetti in base a una
serie di criteri. Le operazioni di base delle query fornite da un framework di mappatura relazionale degli oggetti sono
Torva e Trova univoco. L'operazione Trova univoco richiamerà uno specifico oggetto e Trova restituirà una raccolta di
oggetti basata su un criterio di ricerca.
Le funzioni della query di memorizzazione dati variano in modo significativo. Le semplici memorizzazioni dati basate su
un file possono implementare operazioni query rigide, mentre i sistemi relazionali forniscono un linguaggio di
manipolazione dati flessibile. I framework di mappatura relazionali degli oggetti estendono il modello query
relazionale per renderlo incentrato sull'oggetto anziché sui dati. I meccanismi di pass-through vengono inoltre
implementati per potenziare la flessibilità della query relazionale e le estensioni specifiche del fornitore (ad
esempio, procedure memorizzate).
Tenere presente che esiste un conflitto potenziale tra meccanismi della query basata sul database e il paradigma
dell'oggetto: i meccanismi della query del database vengono guidati dai valori di attributi (colonne)
in una tabella. Negli oggetti corrispondenti, il principio di incapsulamento impedisce la visualizzazione dei valori
degli attributi; essi sono incapsulati dalle operazioni della classe. La ragione dell'incapsulamento è
che rende l'applicazione più semplice da modificare: è possibile modificare la struttura interna di una classe senza
preoccuparsi delle classi dipendenti, finché le operazioni visibili pubblicamente della classe non cambiano. Un
meccanismo di query basato sul database dipende dalla rappresentazione interna di una classe,
interrompendo in modo efficace l'incapsulamento. La sfida per il framework è impedire alle query di
modificare la fragilità delle applicazioni.
Il supporto transazionale consente allo sviluppatore dell'applicazione di definire un'unità di lavoro atomica. Nella
terminologia database, ciò significa che il sistema deve potere applicare una serie di modifiche al database o deve
assicurare che non venga applicata alcuna delle modifiche. Le operazioni all'interno di una transazione vengono tutte
eseguite correttamente o la transazione non viene completata correttamente. I framework relazionali degli oggetti
dovrebbero almeno fornire una funzione della transazione di commit/rollback simile a un database relazionale. La
progettazione di framework relazionali degli oggetti in un ambiente a più utenti può presentare numerose sfide e, per
questo, occorre prestarvi attenzione.
Oltre alle funzioni fornite dal framework di persistenza, l'applicazione deve riconoscere le modalità di gestione degli
errori. Quando una transazione ha esito negativo o viene interrotta, è necessario che il sistema ripristini il proprio
stato a uno stato precedente stabile, generalmente consultando le informazioni relative a tale stato dal database.
Quindi, esiste un'interazione molto stretta tra il framework di persistenza e il framework di gestione degli errori.
I sistemi OO (object-oriented) e che prevedono più utenti devono controllare un accesso simultaneo agli oggetti. Quando
più utenti accedono contemporaneamente a un oggetto, il sistema deve fornire un meccanismo per assicurare che le
modifiche all'oggetto nell'archivio persistente si verifichino in modo prevedibile e controllato. I framework
relazionali degli oggetti possono implementare controlli di simultaneità pessimistici e/o ottimistici.
-
Il controllo di simultaneità pessimistico richiede che lo sviluppatore dell'applicazione
specifichi il proprio intento quando l'oggetto viene richiamato dall'archivio dati (ad esempio, di sola lettura,
blocco di scrittura,...). . Se gli oggetti sono bloccati, è possibile che altri utenti vengano bloccati durante
l'accesso all'oggetto e durante l'attesa del rilascio del blocco. Sarebbe opportuno utilizzare la simultaneità
pessimistica e implementarla con cautela, poiché potrebbero verificarsi situazioni con condizioni di stallo
(deadlock).
-
Il controllo di simultaneità ottimistico presuppone che sia improbabile che si acceda
contemporaneamente allo stesso oggetto. I conflitti di simultaneità vengono individuati quando le modifiche vengono
salvate nel database. Generalmente, se l'oggetto è stato modificato da un altro utente dopo il relativo recupero,
verrà restituito un errore all'applicazione, in cui si indicherà un errore dell'operazione di modifica. E'
responsabilità dell'applicazione rilevare e gestire l'errore. Ciò indica al framework di memorizzare nella cache i
valori simultanei degli oggetti e di confrontarli rispetto al database. La simultaneità ottimistica è meno
dispendiosa se sono presenti dei conflitti, ma lo è maggiormente se il numero di conflitti è abbastanza importante
(a causa della necessità di rieseguire il lavoro in tal caso).
Tutte le applicazioni che utilizzano dati condivisi devono utilizzare la stessa strategia di simultaneità; non è
possibile unire controlli di simultaneità ottimistici e pessimistici negli stessi dati condivisi o potrebbe verificarsi
un danneggiamento. La necessità di una strategia di simultaneità coerente viene gestita meglio tramite un framework di
persistenza.
Gli oggetti sono legati agli altri oggetti tramite relazioni. Un oggetto Ordine contiene numerosi oggetti Voce della
riga. Un oggetto Libro contiene numerosi oggetti Capitolo. Un oggetto Impiegato appartiene esattamente a un oggetto
Azienda. In sistemi relazionali, le relazioni tra entità vengono generalmente implementate utilizzando i riferimenti
chiave esterna / chiave primaria. In sistemi OO (object-oriented), le relazioni vengono generalmente implementate in
modo esplicito tramite gli attributi. Se un oggetto Ordine contiene LineItems, Ordine conterrà un attributo denominato
lineItems. L'attributo lineItems di Ordine conterrà numerosi oggetti LineItem.
Gli aspetti della relazione di un framework relazionale degli oggetti sono interdipendenti con i servizi di query,
transazione e persistenza. Quando un oggetto viene memorizzato, richiamato, sottoposto a transazioni o interrogato,
occorre considerare i relativi oggetti correlati:
-
Quando un oggetto viene richiamato, occorre richiamare anche gli oggetti associati? In modo semplicistico, la
risposta è Sì, ma tale operazione effettuata quando gli oggetti associati non sono necessari è estremamente
dispendiosa. Un buon framework consentirà un mix di strategie.
-
Quando un oggetto viene memorizzato, occorre memorizzare anche gli oggetti associati come se fossero stati
modificati? Ancora una volta, la risposta dipende dal contesto.
Sebbene sia concettualmente vantaggioso considerare separatamente i servizi relazionali comuni degli oggetti, le
implementazioni del framework relazionale degli oggetti dipendono l'una dall'altra. E' necessario implementare i
servizi in modo coerente non solo attraverso singole organizzazioni, ma su tutte le applicazioni che condividono gli
stessi dati. Un framework è l'unico modo meno dispendioso per ottenere tale scopo.
|