InstanceStore e WorkflowApplication in WF4

Quando si utilizza Workflow Foundation 4 si ha a disposizione più di un'opzione per l'hosting di un'istanza di workflow, cioè essenzialmente per avviare ed eseguire un workflow.

Una delle grandi funzionalità introdotte da Workflow Foundation 4 è la classe WorkflowServiceHost grazie alla quale è possibile esporre un workflow come servizio e, attraverso un meccanismo di correlation, legare l'input delle chiamate al servizio in modo da identificare l'istanza di workflow al quale la chiamata fa riferimento.

Tutto il sistema legato al WorkflowServiceHost è molto interessante e direi piuttosto facile da utilizzare considerando che gestisce in autonomia attività come il resuming di un'istanza di workflow, la gestione di timer che scadono per le istanze di idle, ecc...

Cosa accade, però, se si ha la necessità di ottenere maggior controllo sul runtime di Workflow Foundation 4? Supponiamo di dover realizzare un'infrastruttura che, dato uno standard di comunicazione tra i workflow e il mondo esterno, debba consentire di gestire più tipologie di un workflow? E' evidente che questo tipo di requisito è incompatibile con l'hosting via WorkflowServiceHost semplicemente perché tale hosting si basa sul presupposto che ogni tipologia di workflow ha i suoi specifici servizi e tale presupposto non consente una standardizzazione della comunicazione tra il runtime del workflow e il mondo esterno. 

A che serve, vi chiederete voi, una cosa del genere? Beh, se dovete disaccoppiare la logica del workflow dall'interfaccia grafica, è buona norma evitare di legare le chiamate al workflow ai web service esposti dal workflow stesso. In caso contrario una minima modifica a questi contratti, comporterà la ricompilazione di tutta l'applicazione.

La classe WorkflowApplication è la scelta prediletta nel caso in cui vogliate gestire manualmente l'hosting e la comunicazione tra le istanze di workflow e le applicazioni che devono richiamarle.

Fondamentale a tal proposito è la gestione della persistenza. Se siete così fortunati da non avere la necessità di persistere le vostre istanze di workflow, allora, oltre ad invidiarvi molto, vi comunico che potete anche non proseguire la lettura di questo post :-)

La persistenza passa dalle classi che ereditano dalla classe base InstanceStore. Ovviamente Workflow Foundation 4 integra quella necessaria per utilizzare SqlServer come repository e pertanto utilizzeremo la classe SqlWorkflowInstanceStore.

var workflow = new Workflow1();
var workflowApplication = new WorkflowApplication(workflow);
var instanceStore = new SqlWorkflowInstanceStore(connectionString);
workflowApplication.InstanceStore = instanceStore;

Il codice riportato funziona benissimo, ma ha un piccolo problema di efficienza: vi costringe a creare una nuova istanza di SqlWorkflowInstanceStore ogni volta che dovete gestire un'istanza di workflow. Se provate ad assegnare la stessa istanza di InstanceStore a più WorkflowApplication otterrete un bel InstancePersistenceCommandException con il seguente messaggio:

SqlWorkflowInstanceStore does not support creating more than one lock owner 
concurrently. Consider setting InstanceStore.DefaultInstanceOwner to share the 
store among many applications.

Il problema in sostanza è che un'istanza di SqlWorkflowInstanceStore funge anche da lock owner, cioè essenzialmente blocca un'istanza di workflow nel corso del suo ciclo di vita (dal caricamento alla persistenza) per impedire che parallelamente più host possano accedere alla medesima istanza.

Per fare in modo che un'istanza di SqlWorkflowInstanceStore possa caricare più istanze contemporanemente è necessario definire il ciclo di vita del lock owner e a tal proposito è necessario impostare la proprietà DefaultInstanceOwner con il risultato dell'esecuzione di un comando denominato CreateWorkflowOwnerCommand come segue:

var instanceHandle = instanceStore.CreateInstanceHandle();
var createOwnerCmd = new CreateWorkflowOwnerCommand();
var view = instanceStore.Execute(instanceHandle, createOwnerCmd, 
TimeSpan.FromSeconds(30));
instanceStore.DefaultInstanceOwner = view.InstanceOwner;

Particolare importanza risiede nel InstanceHandle. Non sottovalutate la gestione di questo handle perché è da lui che dipende l'uso delle risorse del SqlWorkflowInstanceStore.

Una volta impostato il DefaultInstanceOwner potete far utilizzare questo InstanceStore a più istanze della classe WorkflowApplication. Non dovete, tuttavia, dimenticare di richiamare il comando DeleteWorkflowOwnerCommand quando avete completato le operazioni da eseguire sulle istanze di WorkflowApplication:

var deleteOwnerCmd = new DeleteWorkflowOwnerCommand();
instanceStore.Execute(instanceHandle, deleteOwnerCmd, TimeSpan.FromSeconds(30));

L'esecuzione di questo comando garantisce che l'owner non sia più attivo e, pertanto, consente ad altri lock owner di accedere alle istanze di workflow. Da notare che sia il comando di Create che quello di Delete abbiano in comune l'instanceHandle che, a seguito del comando di Delete, viene correttamente rilasciato insieme a tutte le risorse che identifica. Nel caso in cui vogliate rilasciare le risorse prima di richiamare il comando Delete, potete utilizzare il metodo Free.