Identity Map non è una cache

Questo post di Corrado è un buon punto di partenza per una riflessione dedicata ad una delle caratteristiche salienti (e spesso poco comprese) di ogni strumento O/RM, ossia l'Identity Map. Sintetizzando, l'esempio di Corrado mostra come LINQ to SQL effettui il fetch di un oggetto una sola volta per ogni ciclo di vita di un DataContext, e successivamente restituisca la stessa istanza ad ogni richiesta successiva. Ciò avviene poichè il DataContext di LINQ to SQL si pone come intermediario di ogni accesso al database (in termini di pattern, diremmo che agisce sia come Registry, sia come Unit of Work) e quindi memorizza una copia privata di ogni istanza recuperata dal db, per poter evitare di eseguire una nuova query quando l'applicazione chiederà al Data Access Layer (d'ora in poi: DAL) lo stesso oggetto (o, meglio, un oggetto dello stesso tipo dotato della stessa chiave).

L'insieme delle copie private degli oggetti è l'Identity Map che, ad un primo sguardo, appare svolgere il ruolo di una cache; "appare" perchè: "An Identity Map keeps a record of all objects that have been read from the database in a single business transaction. Whenever you want an object, you check the Identity Map first to see if you already have it." [P of EAA, 195]

Partiamo da un presupposto: quando introduciamo un Domain Model, esso rappresesenta a tutti gli effetti il database agli occhi della applicazione, ergo il Domain Model *é* il database (attenzione: database!=DBMS). Semplificando, ciò ha senso se:

  1. L'applicazione trae beneficio dalla disponibilità dei dati sotto forma di oggetti "nativi" rappresentati le entità di business (e le loro relazioni) e dalla possibilità di utilizzarne il comportamento
  2. L'applicazione non "perde" la possibilità di utilizzare sui dati (oggetti e non più tuple) i servizi tipici di un DBMS

Focalizziamoci sul secondo punto e saliamo sulla macchina del tempo, tornando indietro di 15 anni: "domain model" non è ancora una buzzword (siamo nell'era dei business object), nessuno parla ancora di object spaces e la salvezza del mondo è affidata ai DBMS. In questo scenario, una applicazione che debba implementare (compatibilmente con eventuali accessi concorrenti alla base dati) una "single business transaction" che preveda la modifica di un cliente sul database Northwind farà (più o meno) quanto segue:

  1. L'applicazione avvia una transazione
  2. L'applicazione recupera il cliente ALFKI dal database
  3. L'applicazione effettua svariate utilissime operazioni sia sul db sia in memoria
  4. L'applicazione modifica il cliente ALFKI sul database
  5. L'applicazione insiste nell'effettuare svariate ed utilissime operazioni
  6. L'applicazione chiede il commit della transazione

Tutto ciò premesso, ecco un paio di considerazioni:

  1. Se una "business transaction" concorrente dovesse effettuare il fetch del cliente ALFKI prima del punto 6, otterrebbe i valori "originali"
  2. Se all'interno della stessa transazione, l'applicazione dovesse effettuare nuovamente il fetch del cliente ALFKI successivamente al punto 4, otterrebbe i valori modificati

Ora "torniamo al futuro" e ipotizziamo lo stesso scenario:

  1. L'applicazione chiede al DAL l'avvio di una nuova transazione
  2. L'applicazione chiede al Registry il cliente (sotto forma di oggetto) ALKFI: poichè non è ancora stata effettuata alcuna modifica ai dati all'interno della transazione in corso, il Registry recupera i dati dal database e costruisce un oggetto che rappresenta ALFKI, restituendolo alla applicazione e mantenendone un riferimento in una lista privata: l'Identity Map
  3. L'applicazione lavora alacremente
  4. L'applicazione modifica l'oggetto ALFKI
  5. L'applicazione continua a lavorare alacremente
  6. L'applicazione chiede al DAL il commit della transazione

Riformuliamo le considerazioni espresse per lo scenario "vintage":

  1. Se una "business transaction" concorrente dovesse effettuare il fetch del cliente ALFKI prima del punto 6, il DAL restituirebbe un oggetto non consapevole delle modifiche effettuate al punto 4
  2. Se all'interno della stessa transazione, l'applicazione dovesse effettuare nuovamente il fetch del cliente ALFKI, il DAL troverà nella Identity Map il riferimento all'istanza precedentemente estratta e lo restituirà alla applicazione, evitando di inviare al DBMS una nuova query. Poichè si tratta di un riferimento alla medesima istanza, l'applicazione riceve un oggetto che mostra le modifiche effettuate al punto 4

In un mondo perfetto, il DAL non dovrebbe utilizzare l'Identity Map se la richiesta di fetch non avviene nel corso di una transazione e quindi il comportamento del DataContext di LINQ to SQL sarebbe da considerarsi errato perchè usa l'Identity Map proprio come cache e non al fine di rendere il comportamento del DataContext "coerente" da un punto di vista transazionale. E' però vero, come espresso da Martin Fowler [P of EAA, 197], che: "As I've implied here, you usually see a single Identity Map for a session; otherwise, you need to provide transactional protection for you map, which is more work than any sane developer wants to do."

In pratica, LINQ to SQL associa l'Identity Map alla sessione (DataContext) e non alla transazione "trasformandola" in una cache: è "accademicamente" errato, ma semplifica in maniera significativa l'implementazione del DAL. Sarei ipocrita se lo negassi, visto che anche la "mia" implementazione della Identity Map in NSK è legata alla sessione; così facendo, ho colto 2 piccioni con 1 fava: ho abbassato la difficoltà realizzativa del DAL (Registry+UnitOfWork) e, soprattutto, ho dimostrato di essere un "sane developer"... <g>

 

Technorati Tags: , , , , ,

posted @ Sunday, February 3, 2008 10:57 PM

Print

Comments on this entry:

# re: Identity Map non è una cache

Left by Mario Duzioni at 2/4/2008 10:50 AM
Gravatar
"- L'applicazione lavora alacremente"
ROTFL

# re: Identity Map non è una cache

Left by Lorenzo Barbieri at 2/4/2008 11:36 AM
Gravatar
- ho dimostrato di essere un "sane developer"...
ROTFL
Comments have been closed on this topic.
«September»
SunMonTueWedThuFriSat
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345