L’impedance mismatch tra il paradigma relazionale ed il paradigma ad oggetti è sicuramente uno dei temi più trattati e sentiti da parecchi anni. Per affrontare questo problema sono nati ormai più di dieci anni fa i primi database ad oggetti mai decollati, e sono nati framework che più o meno semplicemente, con maggiore o minore ambizione hanno cercato di gettare un ponte tra i due mondi. Le soluzioni di maggior seguito sono comunque tutte orientate ad implementazioni ad oggetti (da JDBC ad Hibernate, ad EJB) che rendano in qualche misura trasparente il mondo relazionale. E’ innegabile per chiunque che per quanto si cerchi di nasconderlo sotto il tappeto, il mondo relazionale salterà fuori tra gli oggetti quando meno ce lo aspettiamo, anche (o forse soprattutto) con i framework più ambiziosi.
D’altro canto ed ortogonalmente, un pattern nato nel mondo Smalltalk negli anni ottanta (l’MVC), è diventato la soluzione più utilizzata per la costruzione di applicazioni Web.
Quando si adottano le soluzioni più spinte nei due campi, ci si imbatte con maggiore forza nel problema del mismatch.
Infatti da un lato abbiamo le nostre tabelle relazionali normalizzate, dall’altro abbiamo il nostro mondo ad oggetti ben fattorizzato per saper rispondere prontamente al cambiamento della logica di business, ed infine dobbiamo visualizzare i nostri oggetti di dominio all’utente. Molto spesso è necessario mostrare all’utente dati esplosi rispetto alla granularità fine dei nostri oggetti, e questo ci porta nuovamente a fare i conti con il mondo relazionale. Una delle conseguenze principali di quest’ultimo aspetto è che tipicamente costruendo l’applicazione rispettando i principi dei due mondi, ci si trova prima o poi a dover risolvere problemi di prestazioni, dovuti proprio al differente paradigma di aggregazione di dati: navigare un grafo di oggetti piuttosto che fare join tra tabelle (aspetto dinamico del mismatch). Sto pensando in particolare ai framework orizzontali che propongono una soluzione generale e trasparente a questo problema.
Prendiamo ad esempio l’attuale miglior (a detta di molti) framework di Object Relational Mapping: Hibernate.
Pur offrendo una quantità davvero molto ampia di soluzioni al problema prestazioni, trovo che in molte situazioni tali soluzioni non siano soddisfacenti.
Immaginiamo ad esempio di voler implementare un meccanismo che permetta di effettuare una ricerca con un gran numero di criteri opzionali su un oggetto A legato ad altri oggetti, e che permetta inoltre ad ogni utente di personalizzare la pagine di visualizzazione dei risultati ottenuti, decidendo quali campi visualizzare. Immaginiamo inoltre che l’utente richieda di visualizzare dati aggregati, cioè esplodere il nostro oggetto A sulle sue relazioni con gli altri oggetti a lui legati. Vogliamo inoltre essere chiusi rispetto al cambiamento sull’aggiunta o la rimozione di una proprietà nell’oggetto stesso. La soluzione più semplice che vi sarà venuta sicuramente in mente per quest’ultimo requisito sarà di certo fare reflection sull’oggetto di dominio rispetto ad una configurazione del nome delle proprietà della classe da visualizzare memorizzata su file o su db.
Hibernate offre una serie di costrutti per affrontare questo scenario. Uno tra questi è <join>:
<class name="Attivita" table="TEST_ATTIVITA">
<id name="idAttivita" type="long" column="ID_ATTIVITA"/>
<property name="idStato" column="ID_STATO"/>
<join table="TEST_STATO">
<key column="ID_STATO"/>
<property name="descStato"/>
</join>
</class>
che nell’esempio riportato permette di “ribaltare” sull’oggetto Attivita la proprietà descStato dell’oggetto Stato. Tuttavia questa soluzione per un qualsiasi medio conoscitore del paradigma oggetti non soddisfa, perché costringe a portare dentro il nostro oggetto A, i campi degli altri oggetti su cui si vuole fare il join: avremmo un oggetto di dominio “mostro”, in cui la coesione sarebbe inesistente.
Altra soluzione è quella di modellare le associazioni mappandole ad oggetti in modo puro, cioè esclusivamente per mezzo di referenze ad altri oggetti di dominio (senza esploderne i campi dentro Attivita), con tag del tipo (ad esempio) <many-to-one>:
<many-to-one
name="stato"
class="Stato"
column="ID_STATO"
unique="true"/>
Tuttavia una soluzione di questo tipo ci costringe ad aumentare la complessità della struttura necessaria a garantire la chiusura al cambiamento rispetto all’aggiunta o alla rimozione di proprietà dall’oggetto A. E’ chiaro che una semplice reflection sul nome dei campi da visualizzare non basta più, ma probabilmente un piccolo DSL per configurare la personalizzazione dei campi potrebbe essere sufficiente. I problemi maggiori si avrebbero dal punto di vista prestazionale, costringendo ad un tuning sulle strategie di fetch già in presenza di quantità di dati non ingenti, e con la possibilità non remota di non riuscire a risolverli, visto che ad esempio Hibernate supporta un solo “eager-fetching” sulle associazioni per classe persistente.
Terza soluzione sarebbe quella di creare un oggetto ad-hoc, slegato dal nostro oggetto che modella il dominio, con la sola funzione di aggregare i dati come nella soluzione 1), ma senza che questo oggetto contenga alcuna logica di business. Gli amanti dei linguaggi di scripting a ragione storceranno il naso: perché dover mantenere un altro oggetto e quindi la consistenza con l’oggetto di dominio, quando in realtà questo oggetto ha la sola funzione di portare dati verso la View, e ci interessa solo a runtime? Una variante a questa soluzione (ma con lo stesso problema) è quella di mappare il nostro oggetto “cargo” su una View SQL.
Per onestà Hibernate 3 offre, anche se ancora in modo sperimentale, una funzionalità che ci verrebbe molto utile: è possibile infatti effettuare un mapping dinamico di una tabella su una generica java.util.Map senza creare l’oggetto Java corrispondente, che invece viene generato a runtime. E’ inoltre possibile (sempre in modo sperimentale) mappare le tabelle non come POJO, ma come XML interrogabile con HQL (quindi totalmente slegato dallo specifico DBMS utilizzato). Purtroppo però anche queste soluzioni (ottime sulla carta) si scontrano con altri problemi: Hibernate attualmente non permette di configurare direttamente dal file di mapping un join che abbia come campo di origine un campo che non è chiave. Questo limita fortemente l’utilità di questa soluzione (nonché della soluzione 1) soprattutto quando in presenza di database legacy.
Spero con questo esempio di aver dato un’idea dei problemi che si possono presentare in un semplice scenario, proprio nel momento in cui ci aspetteremmo il massimo del supporto dal nostro framework object-relational: scrivere un modello del dominio ad oggetti che rispetti tutti i principi di buon design, pensando il meno possibile al relazionale. Avrò modo di portare altri esempi sulla complessità delle soluzioni ORM (per quanto sofisticate, testate e consistenti siano) in altri post.
Quindi che fare? Una soluzione alternativa a quelle presentate fino ad ora, che tenta di risolvere in modo semplice ed efficiente il problema della visualizzazione esplosa del nostro dominio, potrebbe essere di utilizzare la potenzialità offerta ormai da molti DBMS di esporre il database in xml per mezzo di SQLXML, alimentando direttamente la View con l’xml piuttosto che con un oggetto Composite che lo rappresenti e che funga da databinding. Questa soluzione, permetterebbe di rispettare i requisiti di cui sopra, senza porre alcun vincolo sulla soluzione di persistenza che si decida di utilizzare (JDBC secco, o framework di persistenza di qualsiasi natura, o addirittura Business Logic tutta nel PL/SQL).
La parte più onerosa sarebbe quella di implementare un piccolo motore che permetta di aggiugere dinamicamente i criteri di ricerca espressi dall’utente nella form. Tuttavia il costrutto ‘EXECUTE IMMEDIATE’ del PL/SQL ci verrebbe in aiuto dando vita di fatto ad un linguaggio di scripting interpretato a runtime (alternativamente ad una soluzione fatta in casa si veda PL/XML). Potremmo poi controllare la complessità accidentale del mondo procedurale programmando alla TDD con utPLSQL. E’ interessante notare come un approccio di questo tipo non impugni la spada degli oggetti per combattere contro il mondo relazionale ma al contrario sfrutta tutte le funzionalità offerte da un RDMBS per cercare una soluzione semplice e mirata al problema.
Nella soluzione a cui sto pensando, la BL non è sparsa un po’ sul DB ed un po’ implementata in Java: il PL/SQL con SQLXML ci fornirebbe solo la funzionalità di esporre (quindi in lettura) le nostre tabelle in XML nativamente, così da poter alimentare direttamente la parte di Presentation. Spingendoci un po’ oltre potremmo esporre tali piccoli servizi come webservices (LOW) REST, invocabili direttamente via AJAX, eliminando ogni bisogno di avere un Model “cargo” in Java.
PROCEDURE GET_ATTIVITA(ID IN NUMBER) IS BEGIN DECLARE XML XMLTYPE; BEGIN SELECT XMLELEMENT("Attivita", XMLFOREST(ID_ATTIVITA "IdAttivita", R30.DESCRIZIONE_ATTIVITA "Tipologia", R61.DESCRIZIONE "Stato", INDIRIZZO "Indirizzo", NUMERO_CIVICO "Civico", CITTA "Citta", TO_CHAR(DATA_INIZIO,'YYYY/MM/DD HH24:MI') "DataInizio", N_ADM "NumeroAvviso")) INTO XML FROM R01_ATTIVITA_PROGRAMMATE R01, R30_AN_TIPO_ATTIVITA R30, R61_CD_STATI R61 WHERE R01.ID_TIPO_ATTIVITA = R30.ID_TIPO_ATTIVITA AND R61.ID_STATO = R01.ID_STATO AND ID_ATTIVITA = ID;MOBI_HTP_XML.HTP_XML(XML); END; END GET_ATTIVITA;
Alternativamente potremmo pensare, come accennato in precedenza, di implementare tutto il modello del dominio in PL/SQL, ma questa è tutta un’altra storia.
Chiaramente una soluzione di questo tipo non vuole essere la panacea al problema mismatch / MVC, ma anzi credo abbia tra i suoi punti di forza proprio la sua specificità. Con questo post volevo infatti anche portare un esempio di come spesso soluzioni ad hoc a problemi inerentemente complessi quali il mismatch dinamico e strutturale oggetti/relazionale, siano meglio gestibili e mantenibili di soluzioni generali (per quanto consistenti e sulla carta complete esse siano).



Terza soluzione sarebbe quella di creare un oggetto ad-hoc, slegato dal nostro oggetto che modella il dominio, con la sola funzione di aggregare i dati come nella soluzione 1), ma senza che questo oggetto contenga alcuna logica di business.
Questo mi ricorda qualcosa… bc4j/ADF? No buono, amico
Alternativamente potremmo pensare, come accennato in precedenza, di implementare tutto il modello del dominio in PL/SQL, ma questa è tutta un’altra storia.
No, mai. Non rubarmi la mi BL! La BL non la voglio sul db, il db deve fare il db, tenere i dati, anzi, può anche non esistere e tutto deve funzionare, magari con una batteria di mock e stub, ma deve funzionare.
Comment di FRANK — 18 Settembre, 2006 @ 2:32 pm
ah, ancora una cosa, il link!
http://blogs.tedneward.com/2006/06/26/The+Vietnam+Of+Computer+Science.aspx
Comment di FRANK — 18 Settembre, 2006 @ 2:36 pm
Questo mi ricorda qualcosa… bc4j/ADF? No buono, amico
…certo, neanche a me piace…
No, mai. Non rubarmi la mi BL! La BL non la voglio sul db, il db deve fare il db, tenere i dati, anzi, può anche non esistere e tutto deve funzionare, magari con una batteria di mock e stub, ma deve funzionare.
eheheh La frase era provocatoria (anche io credo fortemente nell’OO), ma anche il tuo no, mai spero (e credo) sia provocatorio…il Business prima di tutto.
http://blogs.tedneward.com/2006/06/26/The+Vietnam+Of+Computer+Science.aspx
Grazie per il link. Era mia intenzione con questo articolo iniziare una serie sull’argomento, con le “stranezze” di Hibernate elencate e spiegate, ma leggendo il post che hai linkato ripeterei solo il lavoro fatto egregiamente da tale Ted Neward.
Mi fa piacere essere in ottima compagnia..
Comment di Enri — 18 Settembre, 2006 @ 4:36 pm
Secondo me ci sono molte strade per ottenere il meglio dai due mondi, ovvero limitare le problmeatiche di persistenza nel mondo ad oggetti e limitare la BL nel modo relazionale.
Un modo è esporre le operazioni di base del DB come servizi, in questo modo l’applicazione non vede più un database ma vede un semplice xml.
In questo modo si elimina il problema della persistenza e si riducono gli strati applicativi, rendendo molto più facili test (i mock sono dei semplici file xml).
Ovvio che la tentazione di non avere servizi fine-grain è molto forte, come giustamente dovrebbe essere in una SOA, e quindi si rischia di spostare molta BL nel DB.
Un altro modo è ribaltare la classica relazione tra DB ed applicazioni ad oggetti, facendo diventare il DB client e l’applicazione ad oggetti server di BL.
In questo modo l’applicazione ad oggetti deve solo esporre i suoi servizi di BL senza minimamente essere contaminata dal problema della persistenza, sarebbe poi compito del DB recuperare tutti i dati necessari, passarli ai servizi di BL e rendere persistenti le risposte.
In pratica si tratta di portare in ottica SOA l’Hollywood principle dell’IOC.
Secondo me si è più agili se non si fa fare ad uno quello che sa fare meglio l’altro e viceversa.
Quindi lo stesso discorso lo si può traslare di livello ed applicare anche alla presentation.
Lo schema è più o meno quello che avevo ipotizzato qui: http://www.request.to.it/tom/index.php?entry=entry060329-102317
Per chiosare:
say NO to spaghetti code
say NO to lasagna code
say YES to ravioli code
Comment di Tom — 18 Settembre, 2006 @ 5:21 pm
il Business prima di tutto.
Ok, però la logica la voglio gestire in posto solo.
Il discorso cambia se ti trovi a lavorare su codice legacy, però con qualche ciclo di refactoring ce la si può fare
Comment di FRANK — 18 Settembre, 2006 @ 5:21 pm
Ok, però la logica la voglio gestire in posto solo.
Certo, siamo d’accordo: Alternativamente potremmo pensare, come accennato in precedenza, di implementare tutto il modello del dominio in PL/SQL, ma questa è tutta un’altra storia….
Tom, quello che dici (db come xml e BL in Java) è interessante e sicuramente merita qualche prova su progetti di media grandezza. Avere l’xml come “common view” è una soluzione che non rende schiavo un mondo al servizio dell’altro e per questo motivo mi stimola a sperimentare. Non so se esistano implementazioni efficienti di XPath in Java (quello che avevo provato tempo fa non soddisfaceva)…ma per questo al limite esiste il nostro eXstensible Properties..
Comment di Enri — 19 Settembre, 2006 @ 10:15 am
…il problema maggiore che vedo in questa soluzione è che i servizi di BL in Java avrebbero bisogno in input di tutti gli “oggetti” sui quali lavorare sotto forma di xml. Questo probabilmente porterebbe ad un esplosione di dati in input non gestibile e con molto overhead, e quindi finiremo con il disegnare servizi fine-grained, che in un ottica SOA non è proprio il massimo.
Alternativamente bisognerebbe avere un layer di DAO nella parte Java che interroga i nostri PL/SQL per ricevere l’xml necessario, ma così siamo di nuovo alle prese con una soluzione che presuppone una parte di data access.
Comment di Enri — 19 Settembre, 2006 @ 12:49 pm
Mi viene in mente anche una terza modalità, molto più pulita, in cui sia il DB che la BL sono solo servizi agnostici, ognuno non sa niente dell’altro, preservando così la corretta granularità.
A questo punto però per ottenere un processo di business occorre introdurre un terzo componente che si occupi di incollare ed orchestrare i servizi esposti da DB e BL.
Nel mondo puramente Java questo compito viene assolto in qualche modo dai framework di IOC come Spring.
Nel mondo SOA questo compito viene assolto tipicamente dagli EAI come BPEL PM.
Se vogliamo veramente arrivare ai ravioli occorre suddividere bene i singoli componenti e quindi distinguere la Business Logic (calcola IVA), dal Business Process (calcola IVA su importo), dalla persistenza (salva IVA).
Lo stesso discorso anche qui deve essere traslato poi sulla presentation e mi viene in mente che ai tempi di Assistant, ragionando con Fabio sul page flow dell’MVC e sul business flow, avevamo visto come le due problematiche in realtà fossero molto simili.
Con l’implementazione di XPWEB ho voluto proprio dimostrare che un unico controller ed una unica interfaccia per i servizi possono assolvere ad entrambi i compiti, semplicando notevolmente lo sviluppo, soprattutto grazie alla logica REST in qui quello che differenzia il comportamento è solo la rappresentazione, la renderizzazione del servizio, e non la sua implementazione.
Comment di Tom — 20 Settembre, 2006 @ 10:12 am
[...] Essere in presenza di un database legacy può essere altrettanto frustrante quanto fare i conti con del codice legacy, e spesso anche di più. Una delle strategie per non scontrarsi con un database che costringe a forti compromessi e soprattutto per non ritrovarsi con un database che a causa della sua fragilità ha perso la sua consistenza concettuale, è di decidere lo schema solo nel momento in cui i nostri oggetti di dominio hanno assunto una forma pressoché stabile. In pratica la strategia è quella di cercare di minimizzare il cambiamento dello schema stesso. Tale strategia ovviamente ha forti limitazioni: sappiamo ad esempio che nel ciclo di vita di un’applicazione vi saranno sempre cambiamenti che la interesseranno, che spesso avranno un impatto anche sul database. Inoltre il mismatch impedance ci ricorda che è rischioso ignorare completamente il mondo relazionale. [...]
Pingback di Database legacy? Un ricordo del passato con TDDD « Enri Blog — 4 Ottobre, 2006 @ 12:25 pm