una piacevole chiaccherata via messenger con Marco di Sanctis mi porta verso questo post che probabilmente sarà il primo di una serie, probabilmente.

Vediamo di dare un paio di definizioni giusto per inquadrare lo scenario:

Database Transaction: è una transazione specificamente sui dati il cui scope è limitato al database/storage, per sua natura deve essere il più “stretta” possibile, dove per stretta intendiamo iniziare il più tardi possibile e terminare il prima possibile; il perchè è evidente: durante una transazione a livello di storage abbiamo, in base al livello di isolamento, fondamentalmente un lock pessimistico sui dati ergo gli altri sono accodati in attesa che la nostra transazione finisca. Probabilmente è una visione molto semplicistica della situazione ma direi che rende bene l’idea.

Business Transaction: il mondo purtroppo non è più semplice come una volta e quindi astraendoci dallo storage abbiamo un concetto di transazione un po’ più ampio: abbiamo infatti il concetto di “transazione utente”, un esempio su tutti potrebbe essere la creazione di un preventivo che comporta probabilmente n transazioni con il database ed ha un ciclo di vita decisamente più ampio.

Schematizzando potremmo quindi dire che lo scenario più semplice è il seguente:

Sample

In cui la transazione di business coincide con la transazione sui dati, ma se ci ragioniamo su un po’ scopriamo che praticamente è inapplicabile ad uno scenario reale, vediamo quindi la situazione prima di andare avanti:

Avete la vostra bella applicazione desktop, quindi un rich-client, che ha la velleità di gestire questo semplicissimo dominio:

Domain

Ho collassato una parte di proprietà e metodi solo per leggibilità, per ora i tecnicismi sono del tutto irrilevanti. Detto questo vediamo alcune delle User Story che dobbiamo realizzare:

Creazione Anagrafica
Come utente voglio poter creare un nuovo soggetto al fine di gestire le anagrafiche dei mie clienti.

Modifica Anagrafica Esistente
Come utente voglio poter modificare l’anagrafica di un soggetto creato in precedenza al fine di tener aggiornati i dati.

Per ora fermiamoci qui e introduciamo qualche amato tecnicismo, essendo un’applicazione desktop è lecito aspettarsi (e questo è quello che si aspetta il mio utente) che le 2 operazioni sopra citate possano essere fatte in contemporanea e a loro volta contemoraneamente più volte, quindi posso ad esempio editare 4 anagrafiche diverse contemporaneamente ognuna nel suo “screen”, dove per screen potremmo intendere una form di editing o un documento in un modello mdi o quello che più vi piace restando il fatto che di questi screen ce ne possono essere n in contemporanea, esattamente come quando con Outlook aprite per l’editing 4 contatti insieme.

Le operazioni (operazioni utente) contemporanee avranno quindi la necessità di avere transazioni di business separate in modo che il commit di una transazione non influenzi lo stato dell’altra. Nelle User Story di cui sopra la cosa è ancora abbastanza semplice, diciamo che quello che succede è più o meno “sintetizzabile” così:

  1. L’utente clicca il bottone “nuovo soggetto”;
  2. La UI fa il dispacth di un messaggio con la richiesta in oggetto;
  3. Qualcuno reagisce, Separation of Concern, e:
    1. Crea una UnitOfWork;
    2. Crea un’istanza della nuova entity;
    3. Sparapacchia a sua volta un messaggio con la richiesta di edit di una entity, allegato al messaggio ci mette:
      • La entity da editare;
      • Una callback perchè vuole essere informato quando l’edit è finito e vuole pure sapere l’esito, in realtà la callback è un filino più complessa perchè esiste il concetto di salvataggio in itiniere… ma è un’altra storia => OT;
      • La UoW corrente;
  1. Un Editor reagisce alla richiesta e si “apre” uno screen…
  2. …l’utente fa quello che deve fare, tra cui i suoi comodi, e ad un certo punto pigia il pulsantino “Salva”;
  3. L’Editor non è responsabile del salvataggio, lui è un editor:
    1. si limita ad invocare la callback dicendo che l’utente vuole il salvataggio;
  4. Quando la callback viene invocata il chiamante:
    1. Apre la transazione con il db;
    2. salva la entity;
    3. fa il Commit della transazione;
    4. Butta la UoW che in questo caso non serve più a nessuno;
  5. L’editor si chiude;

L’altro scenario è un po’ più complesso… forse :-):

  1. l’utente esegue una ricerca e visualizza una bella lista di risultati che fondamentalmente sono projection sui dati, una sorta di SearchResult<Subject>, quindi non il soggetto vero e proprio, sceglie quello che vuole editare;
  2. fa doppio click sull’elemento incriminato;
  3. La UI fa il dispacth di un messaggio con la richiesta in oggetto;
  4. Qualcuno reagisce, Separation of Concern, e:
    1. Crea una UoW;
    2. carica in memoria la entity da editare facendo un fetch attraverso la UoW;
    3. …e poi è tutto uguale a prima :-)

Separation Of Concern

Un editor è un editor e fa il suo lavoro di editare qualcosa, fondamentalmente a lui non interessa quel qualcosa da dove arriva si limita ad editarlo e ad esempio a dialogare con i componenti per la validazione. Il chiamante, e qui è importante notare che non ho specificato chi sia il chiamante perchè può essere chiunque come ad esempio un altro editor… :-), fornisce all’editor i dati da editare e la transazione di business con cui lavorare, vedremo poi perchè ci potrebbe servire (anche se è facile da immaginare).

Conversazione

Purtroppo non è tutto così facile, anche se già così di carne al fuoco ce ne è parecchia. Complichiamo le cose: il dominio di cui sopra è un classico esempio di master-detail (un Subject e tanti Address) rimettiamo quindi per un attimo il cappellino dell’utente e immaginiamo la scena:

Come utente eseguo uno ricerca, scelgo un’anagrafica da editare, e avvio lo screen di edit, all’interno dello screen di edit ho anche la lista degli indirizzi scelgo uno degli indirizzi e apro lo screen per la modifica dell’indirizzo, al temine della modifica premo “ok” e infine premo salva nello screen di edit dell’anagrafica.

o peggio:

Come utente eseguo uno ricerca, scelgo un’anagrafica da editare, e avvio lo screen di edit, all’interno dello screen di edit ho anche la lista degli indirizzi scelgo uno degli indirizzi e apro lo screen per la modifica dell’indirizzo, al temine della modifica premo “ok” alla fine chiudo lo screen di edit dell’anagrafica senza salvare, naturalmente mi aspetto che neanche le modifiche all’indirizzo vengano salvate.

Se ci pensate gli scenari possibili sono innumerevoli e anche decisamente più complessi dei 2 esposti ma se facciamo le cose per bene e se teniamo ben in mente il concetto di Separation of Concern (per gli amici “molto tanti e molto piccoli”) granularizzando i nostri componenti scopriamo che tutto è riconducibile al percorso logico di prima: abbiamo degli “inner” step fondamentalmente identici a quelli dell’editing dell’anagrafica e avendo applicato SoC allo screen di edit non abbiamo problemi a passare da una UoW a qualcosa di più complesso che possiamo finalmente definire Conversazione. Una conversazione è quindi una sorta di long-running-business-transaction.

Possiamo quindi sintetizzare quello che abbiamo detto e/o immaginato così:

image

Come si vede abbiamo una Conversazione “A” che è quella che sta gestendo l’editing dell’anagrafica dalla quale potrebbe nascere una seconda Conversazione “B”, che gestisce la creazione di un ordine per quell’anagrafica, che deve essere separata dalla precedente. Però purtroppo…

Evil is in the details

Osservate questo screenshot:

image

notate che il subject del draft in edit è stato modificato ma giustamente, non essendo ancora stato salvato l’item nella ListView di Outlook ha ancora il subject vecchio?

Tutto quello che abbiamo detto fino ad ora non tiene minimamente conto dei dettagli implementativi che è vero che sono dettagli ma rischiano di mandare tutto alle cozze… quindi okkio ai dettagli ;-) dettagli che poi sono quelli che mancano dalla stragrande maggioranza dei post sull’argomento, cosa che in generale mi fa abbastanza incazzare perchè è facile fare l’architetto figo che sentenzia a destra e a sinistra dimenticandosi che poi le cose devono essere implementate e usate dall’utente finale e non restare delle belle cose sulla carta… sfogo :-)

Presentation perchè sei tu Presentation

quando c’erano i terminali a caratteri era tutto più facile :-) fino ad ora abbiamo sempre e solo parlato di entità del dominio, sono quelle che giustamente ci portiamo in giro tra gli editor ma se pensiamo di “appiccicare” un editor vero e proprio sopra il nostro modello ci scordiamo quello che vediamo, ed è la cosa giusta, in Outlook.

Il problema è molto semplice: Reference Type.

Marco giustamente diceva: passa all’editor un “id” e lascia che sia lui a fare il resto, in effetti la cosa non fa una grinza ma durante i mie 13km a piedi per andare in palestra ci ho rimuginato a lungo e alla fine non può funzionare: vediamo il caso più semplice in cui fallirebbe (ne ho trovati almeno altri 3):

  1. Avvio dell’edit passando la PK;
  2. L’editor apre la UoW e carica il dato;
  3. L’utente vuole editare uno degli indirizzi;
  4. Avvio dell’editor:
    1. passaggio della pk da editare;
    2. passaggio della UoW perchè l’editing dell’indirizzo è nella nostra conversazione;
    3. l’AddressEditor carica l’Address…

Boom… siccome siamo bravi, oppure usiamo un ottimo ORM, l’IdentityMap ci frega in curva e ci ridà la stessa reference che ha già caricato in precedenza quindi noi siamo convinti di avere in mano una cosa diversa ma in realtà abbiamo in mano la stessa cosa.

“Ma mi facci il Dominio…” <semi-cit.>

il problema nel suo insieme è abbastanza evidente, stiamo facendo fare al dominio qualcosa che non è pagato per fare e lui ci frega in corner :-). Manca un attore/mediatore nel mezzo, stiamo usando, ad esempio, Model-View-ViewModel ma non stiamo facendo fare tutto il lavoro al ViewModel (il mediatore)… perchè? perchè all’inizio sembra più facile e veloce :-), implementiamo INotifyPropertyChanged sul dominio e dal ViewModel esponiamo il dominio… e non è bello :-).

E’ il ViewModel che deve mettersi in mezzo, implementare lui INotifyPropertyChanged, e fare da mediatore con il dominio. Solo al momento della richiesta di commit da parte dell’utente il ViewModel che dialoga con il dominio deve preoccuparsi di travasare i dati nel dominio stesso.

image

Questo apparentemente ci salva-dalla-fine-del-mondo, apparentemente perchè sto ancora lavorando sul primo prototipo un po’ complesso per verificare di averle pensate tutte.

.m