MokaByte 94 - Marzo 2005 
Interoperabilità fra strumenti di ingegneria del software
Proiezione java di un database Obiect Oriented
di
Alessandro Trivilini
Nell'ambito dello studio dei database a oggetti,non sempre si ha la possibilità di poter utilizzare linguaggi di progettazione e ambienti di sviluppo eterogenei ma in grado di interagire senza problemi, al fine di progettare e implementare strutture dati orientate agli oggetti. L'obiettivo consiste nel mostrare la forte integrazione dei vari strumenti e linguaggi utilizzati, applicando di volta in volta le più note caratteristiche di una progettazione di tipo agile

Introduzione
Considerato l'utilizzo di un database che permette di lavorare sia a oggetti che in modo classico relazionale, non si vogliono evidenziare le sue caratteristiche e potenzialità specifiche, bensì mostrare come questo sia facilmente integrabile con altri strumenti che riguardano la progettazione del software e i linguaggi di programmazione.
Uno degli obiettivi principali di questo articolo consiste nel fornire le principali indicazioni su come procedere nel momento in cui si volessero utilizzare in modo ciclico e integrato un database orientato agli oggetti, uno strumento di progettazione software e un linguaggio di programmazione.

 

Definizione del problema
In modo particolare si vuole realizzare uno schema logico di un database a oggetti e mostrare come sia semplice la sua implementazione per mezzo di una proiezione Java delle diverse classi. Grazie a questa tecnica è possibile ottenere in modo veloce prototipi di applicazioni funzionanti - riferimento a un approccio di tipo RAD - in grado di aiutare nella scelta definitiva di un eventuale tecnologia.
Ciò che risulta essere interessante é l'ottima coesione che esiste fra i vari strumenti e linguaggi utilizzati, ognuno dei quali con le proprie definizioni e caratteristiche, ma tutti perfettamente coerenti con le specifiche di progettazione e programmazione a oggetti. Di seguito l'elenco degli strumenti utilizzati:

Progettazione software : Rational Rose - Rational XDE
Database a oggetti : Caché - Intersystem
Linguaggio di programmazione : Java

Vi sono tre possibili livelli di interazione con il progetto a cui ogni singola funzionalità viene sottoposta ciclicamente. Il primo riguarda la fase di progettazione dello schema logico delle classi, in cui il progettista può realizzare - secondo i requisiti funzionali - le caratteristiche principali delle singole classi; un secondo livello nel momento in cui le classi vengono importate all'interno dell' ambiente di sviluppo del database, esattamente all'interno di un namespace (contenitore nel quale è possibile inserire i vari progetti con le relative classi, precedentemente definito e configurato). A questo punto servendosi di una sintassi proprietaria del database si possono raffinare le strutture delle classi precedentemente create, inserendo codice a piacimento. Per mezzo di un linguaggio specifico, è possibile definire nuovi metodi e attributi, specificando quali caratteristiche devono avere le varie classi. Un terzo livello in cui è possibile intervenire riguarda i codici sorgenti Java, generati direttamente con la tecnica della proiezione. Questa tecnica consente di trasformare le classi precedentemente definite e in seguito modificate, in codice Java. Così facendo il programmatore non dovrà fare altro che continuare a lavorare direttamente sugli oggetti creati applicando le caratteristiche classiche della programmazione a oggetti.
Di seguito un piccolo schema delle tre fasi sopra descritte, che ciclicamente vengono considerate durante tutto il ciclo di vita del progetto.


Figura 1
- Le tre fasi dell'applicazione

 

Schema logico delle classi di partenza
L'esempio prevede la definizione di tre classi a contatto fra loro per mezzo di due relazioni, una di associazione e una particolare di aggregazione. Vengono pure definite delle cardinalità in grado di offrire valore aggiunto alle relazioni stesse. Si tratta di una classe Person con all'interno tre attributi di tipo %String, di una classe System, con anch'essa due attributi di tipo %String, e di una terza classe di nome Mouse aggregata alla classe System.

Per evitare problemi di compatibilità di tipi di dati durante la fase di esportazione delle classi, prima di iniziare con la definizione dello schema logico bisogna importare all'interno di Rational Rose e utilizzare in seguito il set di tipi riconosciuti e trattati dal database. Come si può notare nello schema sotto riportato, i tipi definiti introducono una sintassi particolare rispetto a quella standard conosciuta, ossia l'utilizzo del segno "%" nella definizione. Per esempio, un tipo Integer dovrà essere definito come %Integer, un tipo String verrà definito come %String, e così per tutti gli altri tipi di dati disponibili.


La scelta di queste due relazioni non è stata casuale, bensì pensata per mostrare come vengono gestiti all'interno della base dati i concetti che stanno a monte delle relazioni. Come detto, per poter memorizzare all'interno del database le informazioni che riguardano i vari oggetti, bisogna fare in modo che le classi portino al loro interno l'informazione che indichi la loro persistenza, e questo vale per le classi Person e System.



Figura 2
- Screen Shoot della definizione delle classi

 

Il discorso è leggermente diverso per la classe Mouse. Definita come classe aggregata, dovrà essere gestita diversamente, ciò significa che dal punto di vista degli oggetti verrà trattata come classe indipendente, mentre all'interno del database verrà considerata come classe serializzata della classe System. Si tratta di due classi ben distinte, i dati della classe aggregata (Mouse) saranno però inglobati e gestiti all'interno della classe aggregante (System). Così facendo, per questo tipo di classi non sarà necessario indicare nessuna persistenza.
Come anticipato nei punti precedenti, internamente il database differenzia la gestione delle classi associate da quelle aggregate. Di seguito un piccolo esempio che mostra ciò che si intende quando una classe aggregata viene serializzata all'interno di un'altra classe:



Figura 3
- Screen Shoot degli attributi serializzati

 

Come si può notare, gli attributi inizialmente definiti all'interno della classe Mouse (type, val), sono stati serializzati all'interno della classe aggregante, in questo caso System. Dal punto di vista della programmazione saranno due classi fisiche distinte, ma dal punto di vista dei dati e del contenuto saranno considerate come una classe (tabella) unica.

 

Esportazione delle classi all'interno di Caché
Una volta definita una prima bozza dello schema logico delle classi che rappresentano la struttura del database a oggetti che si vuole realizzare, si è pronti per l'esportazione della struttura fisica delle classi. Questa operazione avviene in modo molto semplice e veloce, basta selezionare la voce Caché dal menù Tools di Rational Rose, che subito appaiono le funzionalità messe a disposizione da Rose per interagire direttamente con Caché. Le possibilità previste sono quelle di import (da Caché a Rose), di export (da Rose a Caché) e di import del data type dictonary per poter definire correttamente gli attributi all'interno delle varie classi.

Nel nostro caso si tratta della seconda opzione, quella che riguarda l'esportazione delle classi. Questa operazione richiede davvero pochi secondi, anche se la struttura delle classi definita presenta un numero elevato di elementi. Un punto interessante sta nel fatto che non si tratta di una semplice operazione di conversione, ma attraverso questo procedimento è per esempio possibile verificare eventuali errori di cattiva definizione dei tipi di dati. In caso ve ne fossero, non sarà possibile portare a termine l'operazione di esportazione, e questo potrebbe tornare utile per avere la certezza che una volta esportate le classi siano definite correttamente secondo le specifiche.

 

 

Struttura delle classi esportate
Una volta esportate le classi, si può iniziare immediatamente il lavoro di definizione del codice all'interno dei vari metodi precedentemente definiti, come pure modificare le definizioni o crearne di nuovi.
In sostanza, da questo momento in poi ci si trova in un ambiente di sviluppo che permette di interagire a tutti gli effetti con le classi ottenute. Vi sono due possibili metodi per interrogare i vari oggetti, il primo è quello implicito offerto gratuitamente e automaticamente dalla proiezione, ossia l'utilizzo di metodi definiti come "metodi di sistema", il secondo riguarda quello classico definendo dei metodi espliciti in grado di interagire sugli oggetti in funzione delle varie esigenze.

Esempio della struttura ottenuta dopo l'esportazione delle classi viste nell'esempio:

Classe PERSON

Class User.Person Extends %Persistent [ ClassType = persistent ]
{
Property city As %String;
Property country As %String;
Property name As %String;
Relationship theSystem As User.System [ Cardinality = one, Inverse = theUser, Required ];
}


Classe SYSTEM

Class User.System Extends %Persistent [ ClassType = persistent ]
{
Property identName As %String;
Property label As %String;
Property theMouse As User.Mouse [ Required ];
Relationship theUser As User.Person [ Cardinality = many, Inverse = theSystem, Required ];
}

Classe MOUSE

Class User.Mouse Extends %Library.SerialObject [ ClassType = serial ]
{
Property type As %String;
Property val As %String;
}



Come si può notare dalla definizione delle varie classi, due portano la proprietà Persistent mentre una viene identificata come Serial.
Una volta ottenute le definizione delle classi, attraverso un semplice menù è possibile specificare tutta una serie di proprietà specifiche della classe che la caratterizzeranno durante la proiezione Java e la gestione all'interno del database. Per indicare che una classe è di tipo Serial basta modificare le seguenti proprietà:

Class Type : serial
Class Super : %Library.SerialObject


Dal momento in cui le classi vengono compilate senza errori, verranno create fisicamente all'interno del database e saranno utilizzabili attraverso gli strumenti disponibili, per esempio l'SQL manager messo a disposizione dal database, il quale permette immediatamente di lavorare attraverso il linguaggio SQL sui vari oggetti.

Per meglio capire quanto spiegato in precedenza, si consideri l'esempio seguente che tratta la definizione di un nuovo metodo per l'interrogazione dei dati sulla classe Person. Questo metodo lo potremo utilizzare normalmente una volta eseguita la proiezione Java delle classi interessate, come se fosse un normale metodo appartenente a una classe java.


Nuovo metodo per l'accesso ai dati di PERSON

// lookup a name using embedded SQL
Method myGetCity(id As %String) As %String
{
Set name = "name"
&sql(SELECT city INTO :name FROM Person WHERE ID = :id)
Quit name
}

L'esempio recupera il nome della città a cui appartiene l'OID dell'istanza dell'oggetto desiderato. Più avanti verrà mostrato l'utilizzo di questo metodo attraverso il linguaggio Java.


Proiezione Java delle classi definite
Una volta definito il diagramma delle classi, esportato all'interno dell'ambiente di sviluppo e in seguito esteso secondo le funzionalità desiderate, è possibile passare alla fase finale, proiettando le classi generate nel linguaggio java. Questa operazione di regola viene effettuata partendo dall'ambiente di sviluppo, inserendo all'interno delle definizioni delle classi il seguente codice:

Projection java As %Projection.Java(ROOTDIR = "C:\CacheSys\Dev\Java\esempio\Exe");

Ogni classe di cui si vuole ottenere le proiezione dovrà contenere al suo interno una dichiarazione di questo tipo. Informazione molto importante, riguarda il fatto che tutte le classi java (sorgenti compresi) ottenute dalla proiezione, vengono memorizzate all'interno di un direttorio nel file system. Questa cartella verrà trattata esattamente come un package all'interno del proprio progetto. Per un utilizzo corretto delle classi che compongono il database a oggetti, ogni volta che si ha la necessità di utilizzo delle stesse s i deve importare questo package, solo in questo modo potrò lavorare creando e gestendo i vari oggetti con i relativi dati, utilizzando i metodi definiti precedentemente.

 

Struttura delle classi dopo la proiezione
Una volta proiettate le classi interessate all'interno del package sopra indicato, ottengo il codice che rappresenta la definizione di tutto ciò che ho ottenuto (classi, metodi, attributi) all'interno dell'ambiente di sviluppo del database. Di seguito un estratto che riguarda la classe Person:


package user;
import com.intersys.*.*;
import user.System;
import user.Mouse;

public class Person extends Persistent {

private static String CACHE_CLASS_NAME = "User.Person";

// NB: DO NOT USE IN APPLICATION!
// Use <code>Person._open</code> instead.
// <p>
// Used to construct a Java object, corresponding
// to existing object
// in Cache database.

// @see #_open(com.intersys.objects.Database,
// com.intersys.objects.Oid)
// @see #_open(com.intersys.objects.Database,
// com.intersys.objects.Id)

public Person (CacheObject ref) throws CacheException {
super (ref);
}
}


A seguire alcuni esempi di metodi di sistema ottenuti e direttamente utilizzabili dalla proiezione Java.

 

Metodi di sistema per la gestione degli oggetti ottenuti

Per capire cosa si intende per metodi di sistema, di seguito vengono riportati alcuni esempi:


// Apertura dell'oggetto attraverso l'OID
public static RegisteredObject _open (Database db, Id id) throws CacheException {
CacheObject cobj = (((SysDatabase)db).openCacheObject(CACHE_CLASS_NAME,
id.toString()));
return (RegisteredObject)(cobj.newJavaInstance());
}


// Interrogazione di un oggetto
public User.Person getCity() throws CacheException {
Dataholder dh = mInternal.getProperty(ii_City,
jj_city,
Database.RET_OBJECT,
"City");
CacheObject cobj = dh.getCacheObject();
if (cobj == null)
return null;
return (User.Person)(cobj.newJavaInstance());
}

// Modifica del valore di un attributo di un oggetto
public void setCity(User.Person value) throws CacheException {
Dataholder dh = new Dataholder (value);
mInternal.setProperty(ii_City, jj_City,kk_city,
Database.RET_OBJECT, "city", dh);
return;
}


Ridefinizione di un metodo per l'interrogazione di un oggetto
Un'informazione molto importante sta nel fatto che i metodi di sistema per l' accesso ai dati dell'oggetto possono essere utilizzati nel momento in cui gli attributi dell'oggetto vengono definiti come Public.
Di seguito viene riportato un esempio di un metodo utilizzato per il recupero dell'attributo City della classe Person nel caso in cui venisse dichiarato come Private.

Interrogazione di un oggetto con un metodo ridefinito

public java.lang.String myGetCity (java.lang.String id) throws
CacheException {
Dataholder[] args = new Dataholder[1];
args[0] = new Dataholder(id);
Dataholder
res=mInternal.runInstanceMethod("myGetCity",args,Database.RET_PRIM);
return res.getString();
}


Esempi diversi di interrogazione degli oggetti
Il codice seguente mostra un esempio di connessione al database via JDBC, e la creazione di nuovi oggetti, istanziando le classi ottenute dalla proiezione.

// Connessione al database e creazione di una nuova persona
try {
// Connect to Cache in the USER namespace
dbconnection = CacheDatabase.getDatabase (url,
username, password);

InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);

// *****************************
// CREATE NEW INSTANCE OF PERSON
// *****************************
person = new User.Person(dbconnection);

person.setName("new name");
person.setCity("new city");

// Save the new object to the database
person._save();

// For each object
dbconnection.closeObject(person.getOref());
person = null;
dbconnection.close();

} catch (Exception ex) {
System.out.println("Obj not created: "
+ ex.getClass().getName()
+ ": " + ex.getMessage());
}


// Recupero e aggiornamento di un oggetto dal database attraverso l'OID
try {

System.out.print("\n\n\nEnter ID of Person object to be
opened and updated:");
String strID = br.readLine();

person = (User.Person)User.Person._open(dbconnection,
new Id(strID));

// Get name and city values with native methods
System.out.println("Person name: " + person.getName());
System.out.println("Person city: " + person.getCity());

// Get city value with re-defined methods
System.out.println("Person city: " +
person.myGetCity(strID));

// **********************************
// GET NEW DATA AND UPDATE OBJ VALUES
// **********************************
System.out.print("\n\n\nEnter new PERSON name:");
String newPersonName = br.readLine();
System.out.print("\n\n\nEnter new PERSON city:");
String newPersonCity = br.readLine();

person.setName((newPersonName));
person.setCity((newPersonCity));

// Save the new object to the database
person._save();

// For each object
dbconnection.closeObject(person.getOref());
person = null;
dbconnection.close();

} catch (Exception ex) {
System.out.println("Obj not updated: "
+ ": " + ex.getMessage());
}


Conclusioni
Naturalmente questo tipo di procedure richiede particolare attenzione, soprattutto nel momento della definizione delle classi. Un grosso vantaggio che si ha lavorando con questi strumenti è la velocità di sviluppo, in poco tempo si ottiene subito del codice Java utilizzabile per dei test di fattibilità o per la realizzazione stessa di un'applicazione. Partendo da una definizione logica statica di un diagramma delle classi si arriva ad ottenere del codice java compilato con pochissimi sforzi, e questo potrebbe tornare comodo quando le applicazioni sono di medio piccole dimensioni. Provando si verifica che l'intero processo non provoca nessun tipo di problema, la comunicazione fra Rational Rose e Caché avviene in modo semplice e trasparente, niente di ciò che non si è volutamente progettato viene incluso all'interno delle definizioni delle classi. Al contrario, servendosi della proiezione per arrivare al linguaggio Java, vi sono alcuni aspetti da considerare, in primo luogo la generazione del package con al suo interno le classi proiettate.

 

Bibliografia
[1] Object-Oriented Application Development Using the Caché Postrelational Database
[2] Corso di Gestione Avanzata dei Dati - Roberto Mastropietro Corso di Basi di Dati Relazionali e a Oggetti - Giancarlo Corti (Scuola Universitaria Professionale della Svizzera Italiana)
[3] http://www.intersystems.com/

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it