Saturday, June 21, 2008

Gli store della persistenza

. Saturday, June 21, 2008

Senza persistenza il ciclo di vita di un workflow è limitato, per questo motivo, grazie ad un'architettura basata su vari servizi che possiamo agganciare al runtime, ci viene fornito un servizio che si occupa di salvare un workflow su un database SQL rimuovendolo dalla memoria per poi gestire il suo caricamento in un secondo momento.

La protagonista  di questa serie di operazioni è la classe SqlWorkflowPersistenceService.
In questo post non vedremo quali sono i passi iniziali per poter usare il servizio ( per chi invece fosse interessato rimando alla documentazione su msdn) ma bensì quali sono le situazioni in cui lo stato di un workflow viene fatto persistere.
E' bene fare però una precisazione, le situazioni che andrò ad elencare non sempre producono come risultato la serializzazione del flusso sul database, ma bensì sono da intendere come punti in cui avviene un qualche aggiornamento.
Questa affermazione forse sarà un pò più chiara con il procedere della lettura.
Iniziamo con l'indicare il primo store in cui lo stato del workflow viene persistito cioè quando un workflow diventa inattivo ( idle).
Per provare quanto detto servono due cose: un workflow sequenziale ( Figura 1) e SQL Server Profiler ( per chi usa SQL Express 2005 consiglio il seguente tool):
Persistence1
Nel caso specifico ho inserito un DelayActivity per rendere il workflow inattivo.
Ciò però non basta in quanto è necessario indicare alla classe SqlWorkflowPersistenceService che appena il flusso raggiunge lo stato di attesa, il workflow deve essere serializzato.
Il codice che aggiunge il servizio di persistenza al runtime e lo istruisce a quanto detto sopra è il seguente:

   1: WorkflowRuntime runtime = new WorkflowRuntime();
   2: ManualWorkflowSchedulerService scheduler = new ManualWorkflowSchedulerService();
   3: runtime.AddService(scheduler);
   4: NameValueCollection col = new NameValueCollection();
   5: col.Add("connectionString", "connessione al db");
   6: col.Add("UnloadOnIdle", "true");
   7: WorkflowPersistenceService persistence = new SqlWorkflowPersistenceService(col);
   8: runtime.AddService(persistence);
   9: runtime.StartRuntime();
  10: WorkflowInstance instance = runtime.CreateWorkflow(typeof(SeqWF.Workflow1));
  11: instance.Start();
  12: scheduler.RunWorkflow(instance.InstanceId);

La parte che in questo momento ci interessa è il valore true passato al parametro unloadOnIdle.
Monitorando con il profiler possiamo verificare che il servizio richiama la stored procedure di inserimento InsertInstanceState nella tabella InstanceState usata da Workflow Foundation per la persistenza dello stato di un workflow:
Persistence2
Il secondo store avviene quando un workflow è completato o terminato. Nel seguente caso il workflow sequenziale è quello di Figura 2:
Persistence3
In questo caso possiamo anche passare al parametro unloadOnIdle il valore false, oppure scegliere il costruttore della classe in cui passare semplicemente la sola stringa di connessione. Anche in questo caso tramite il profiler constatiamo che, al completamento del workflow, viene richiamata la solita stored procedure anche se con qualcosa di diverso.
Persistence4Come si può notare al parametro state non viene passato nessun blob e, cosa ancora più importante, al parametro status viene assegnato il valore 1.
Alla fine dell'esecuzione della stored procedure la tabella InstanceState non contiene nessun nuovo record. Il motivo è da ricercare nella stessa stored procedure, in quanto se lo status del workflow vale 1 ( workflow completato) o 3 ( workflow terminato, si può ottenere usando l'activity Terminate) viene rimossa l'eventuale copia persistita tramite la seguente delete:

   1: DELETE FROM [dbo].[InstanceState]
   2:        WHERE uidInstanceID=@uidInstanceID 
   3:        AND ((ownerID = @ownerID AND ownedUntil>=@now) 
   4:        OR (ownerID IS NULL AND @ownerID IS NULL ))

Con ciò forse risulterà più chiara la precisazione fatta ad inizio post, il comportamento di default del servizio di persistenza non sempre produce un record nella tabella del workflow serializzato, ma comunque un qualche aggiornamento.
Terzo store, al completamento degli activity TransactionScope e CompensatableTransactionScope.
Per garantire l'esito positivo o negativo di transazioni ACID entrano in scena le due activity sopra citate. Senza scendere troppo nello specifico possiamo dire che tramite il TransactionScopeActivity garantiamo un'unica transazione per tutte le activity racchiuse al suo interno. Così facendo otteniamo se sono completate correttamente un commit, altrimenti un'operazione di rollback.
Il CompensatableTransactionScopeActivity garantisce il meccanismo della compensazione cioè è possibile annullare gli effetti prodotti da un'operazione eseguita in precedenza; detto in altre parole è possibile scrivere il codice che eseguirà il rollback delle operazione che in precedenza sono state sottoposte a commit.
Il questo caso il workflow sequenziale è rappresentato dalla Figura 3:
Persistence5
Con il solito profiler verifichiamo il tutto.
Persistence6 Dopo il completamento dell'activity TransactionScope abbiamo il lancio della stored procedure, ma per quello che si è detto al secondo store, al completamento del workflow la copia serializzata viene cancellata.




Quarto store al completamento di un custom activity decorato con l'attributo PersistOnClose:

   1: [PersistOnClose]
   2: public partial class ActivityPersist: Activity
   3: {
   4:   public ActivityPersist()
   5:   {
   6:     InitializeComponent();
   7:   }
   8:  
   9:   protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
  10:   {
  11:     return ActivityExecutionStatus.Closed;
  12:   }
  13: }

Il workflow sequenziale è rappresentato dalla Figura 5:

Persistence7 Persistence8
Il quinto store quando si invoca sulla classe WorkflowInstance il metodo Unload o TryUnload:

   1: instance.Unload();

0 commenti:

Post a Comment