Questo è un periodo veramente intenso che ha però il grandissimo pregio di permettermi di imparare moltissimo e di fare esperienze veramente interessanti.
Nei giorni scorsi stavo leggendo un po’ di “arretrati” e sfogliano un post di Ayende leggo il commento di Tommaso Caldarola e devo dire che, nonostante Ayende sia di parere contrario, io mi trovo in piena sintonia con quello che dice Tommaso.
Possiamo estendere e parafrasare quello che dice Tommaso (sperando di non prendere fischi per fiaschi) dicendo che:
“se un “framework” o per meglio dire la sua interfaccia pubblica, non sono di semplice ed intuitivo utilizzo probabilmente c’è qualche problema quantomeno di design se non addirittura architetturale.”
Quando mi approccio ad un nuovo problema quello che faccio è partire quasi sempre da un livello molto alto: l’utente finale. Mi metto cioè il cappellino dell’utente finale e scrivo le User Story. Come ho già avuto modo di dire quello è il nostro requisito vero e proprio se non soddisfiamo il nostro utente finale direi che abbiamo fallito.
Questo approccio mi permette quasi sempre di creare/modellare un insieme di servizi e componenti che dal macro problema portano alla risoluzione della singola operazione unitaria senza che però sia noto all’utilizzatore cosa succede dietro le quinte.
UserRepositoryProcess urp = new UserRepositoryProcess( currentSecurityContext );
IEntityCollection<IUser> allUsers = urp.GetAll();
Dietro queste due semplicissime righe di codice succedono una quantità industriale di cose ma l’utente (in questo caso il developer) non è tenuto a saperle, e questo non tanto per “segretezza” ma semplicemente perchè non c’è nessuna necessità di portare complessità la dove non serve.
Perchè sto dicendo tutto ciò? per il semplice fatto che in questi giorni mi sto scontrando con qualcosa che non riesco a rendere semplice… e quindi è prono a continui errori e rivisitazioni proprio perchè non è intuitivo.
Vediamo da cosa nasce il problema e se vi va intavoliamo una discussione (che nel caso sposterei sul forum):
public interface IUnitOfWork : IDisposable
{
void RegisterDeleted(object item);
void RegisterDirty(object item);
void RegisterNew(object item);
void Commit();
void Rollback();
}
Queslla esposta è, una parte, dell’interfaccia pubblica della UnitOfWork di NSK.
Vista così, voi direte…, ma che c’è che non va? in realtà nulla, ma se la caliamo in un’applicazione decisamente complessa direi che potremmo obiettare che:
- Non è un servizio, quindi non è esponibile come tale, per il semplice fatto che ha bisogno di mantenere lo stato;
- se ci caliamo in una realtà complessa lo strato di UnitoOfWork è decisamente lontano dall’utilizzatore e ritengo che ci debbano essere almeno un paio di strati tra l’utilizzatore e l’ingresso sulla scena della UoW. Ergo ho la sensazione che abbia più responsabilità di quelle che dovrebbe avere.
Il primo punto direi che non necessita spiegazioni: se faccio n chiamate ai vari Register* prima di arrivare ad una Commit o ad una Rollback devo mentenere la lista delle operazioni da eseguire internamente alla UoW.
Il secondo punto invece necessità di qualche approfondimento, la realtà in cui vorrei che vi calaste è la seguente:
- Siete in una applicazione web, con tutto quello che ne consegue...;
- avete bisogno di offrire un contesto transazionale anche in memoria: IRevertibleChangeTracking/similari…;
- Siete in una situazione in cui il ciclo di vita di un grafo di entity “spanna” più richieste http: Conversazione;
- Gestite un grafo di oggetti abbstanza complesso e non siete limitati alla manipolazione di una entity alla volta: per capirci dovete permettere all’utente di:
- aprire un ICustomer;
- modificare il CustomerName;
- aggiungere un IAddress;
- rimuovere un IAddress esistente;
- creare un nuovo IOrder per il customer aperto;
- inserire un po’ di righe/prodotti nell’ordine, di cui magari alcuni prodotti non esistono e quindi vanno creati al volo…
- e… permettere all’utente di fare un “Undo” progressivo delle modifiche che ha apportato al “grafo/mondo” che sta manipolando…;
- alla fine persistere tutto il lavoro effettivamente (quindi al netto degli Undo) fatto;
Se non ho interpretato male in NSK la UnitOfWork è la “sessione” quindi ho una o più UoW per “utente”, quello che mi manca è la possibilità di fare rollback di una singola attività fatta. E’ in questa direzione che vedo la UnitOfWork come un qualcosa che sta ad un livello basso e che viene utilizzato ad un livello troppo alto.
Quello che succede (o meglio che “dovrebbe”) nell’applicazione che stiamo sviluppando è quindi:
- Inizio della conversazione;
- Inserimento dei dati che stiamo manipolando nella conversazione;
- manipolazione del dato che a questo punto può “spannare” su più richieste http;
- recupero del set (ChangeSet) delle modifiche che sono state apportate ai dati in conversazione;
- passaggio del ChangeSet alla UnitOfWork;
Dietro le quinte la UoW dato il ChangeSet è in grado di:
- “estrarre” le sole entity che hanno bisogno di persistere modifiche;
- per quelle entity capire quale sia l’effettiva operazione (C..UD) da eseguire;
- recuperare il “Persistence Service” adeguato;
- Aprire una transazione e fare il commit in maniera transazionale;
Internamente ogni persistence service:
- recupra l’adapter corretto per la entity che ha in mano e per l’operazione che deve eseguire;
- invoca l’esecuzione;
E a questo punto se non ci sono stati problemi:
- conclusione della conversazione;
Detto questo siamo in grado di fare diventare la UoW un servizio che non mantiene stato e che effettivamente fa il lavoro per cui è stato pensato.
…mumble, mumble…
Oggi mi sento un po’ M.rkino pensieroso :-D
.m