Workflow/Webservice async operations

Tra le tante novita’ delle ultime versioni del framework una delle mie favorite e’ sicuramente Workflow Foundation. Il poter modellare il flusso della propria applicazione, spostarne le parti (se ben disegnato) senza rimettere mano al codice, raggruppare activities in blocchi come la SequenceActivity e gestirne tutte le eccezioni con un FaultHandler all’altezza dell’activity che le raggruppa, reliability, consumo contenuto di risorse in quanto non si appoggia a nessun prodotto (eg BizTalk) e bla bla bla.

Insomma, una gran bella tecnologia. Versatile (possiamo lanciare un workflow praticamente da qualsiasi tipo di progetto) e ben documentata.

In attesa di un vero e proprio ambiente di hosting come Azure che portera’ WF, magari non a competere con BizTalk ma almeno a considerlo una valida alternativa a seconda dei casi evitando work-around come schedulare servizi windows o console apps il cui scopo e’ solamente hostare workflows.

Ho diverse orchestrazioni esposte come web services, di seguito il flusso (a grandi linee):

Il punto in comune di queste orchestrazioni di cui parlo e’ (come da immagine) il loro comportamento “asincrono”.

Il processo, esposto come web service, riceve una richiesta e restituisce immediatamente una risposta al client.  Successivamente viene eseguita la logica dell’orchestrazione ( rappresentata nelle shape1,2…). Il comportamento asincrono e’ rappresentato dal fatto che al client verra’ sempre restituta una risposta fittizia indipendentemente dall’esito del processo in quanto quest’ultimo viene eseguito dopo che la risposta e’ stata servita. Un comportamento del genere ha un senso se l’esito delle operazioni da eseguire non deve essere  notificato al chiamante e se il tempo richiesto per completarle (se poste prima della response) potremme mandare in timeout la chiamata al web service.

Se in Biztalk e’ il Biztalk Isolated Host, con i suoi adapters ospitati all’interno di un’applicazione esterna (in questo caso il worker process di IIS) ad occuparsi di inoltrare la richiesta al Biztalk runtime utilizzando Endpoint manager e Messaging agent (rendendo semplice ed indolore l’invocazione “asincrona”) utilizzando Workflow Foundation le cose sono ben diverse.

Esponendo un workflow come web service il WorkflowRuntime viene implicitamente gestito  da IIS e reso inacessibile allo sviluppatore, per ogni richiesta viene istanziato un WorkflowRuntime responsabile del ciclo di vita del workflow. Chi provvede ad assicurare questo comportamento e’ il ManualWorkflowSchedulerService,  servizio di default per tutti i progetti web derivanti da un workflow, nel web.config se ne avra’ un’occorrenza:

<add type=”System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35″ />

Il servizio garantisce che l’esecuzione di un workflow avvenga all’interno di una richiesta. Questo significa che anche disegnando un flusso simile a quello riportato in cima al post la response sara’ sempre l’ultima operazione ad essere eseguita. Work around?

Dividere il workflow in due parti: una che fa da wrapper contenente solamente le activities di Send e Receive web service ed una code activity che si occuperà di istanziare un nuovo ManualWorkflowSchedulerService ed associarvici il workflow che vogliamo lanciare.  Il risultato e’ lo stesso che otterremmo utilizzando una InvokeWorkflow activity ma quest’ultima non puo’ essere utilizzata in quanto asincrona ed implementando il ManualWorkflowSchedulerService, di default, si lavora in un contesto sincrono.

Qui il codice che permette la creazione ed esecuzione di un secondo workflow in modalita’ asincrona pur utilizzando il ManualWorkflowSchedulerService, racchiuso in una classe , al metodo StartWorkflow puo’ essere aggiunto un overload per poter passare anche gli eventuali parametri di input del workflow da chiamare :

   1: public class StartWorkflowService : WorkflowRuntimeService
   2:     {
   3:         public Guid StartWorkflow(Type workflowType)
   4:         {
   5:             WorkflowRuntime workflowRuntime = new WorkflowRuntime();
   6:
   7:             WorkflowInstance workflowInstance = workflowRuntime.CreateWorkflow(workflowType);
   8:
   9:             workflowInstance.Start();
  10:
  11:             ManualWorkflowSchedulerService manualWorkflowSchedulerService = new ManualWorkflowSchedulerService(true);
  12:
  13:             if (manualWorkflowSchedulerService != null)
  14:             {
  15:                 manualWorkflowSchedulerService.RunWorkflow(workflowInstance.InstanceId);
  16:             }
  17:
  18:             return workflowInstance.InstanceId;
  19:         }
  20:     }

Una seconda possibilita’ (probabilmente la piu’ pulita) e’ quella di utilizzare il DefaultWorkflowSchedulerService, sconsigliato in ambiente web a causa del numero di threads coinvolti per l’esecuzione del workflow  ma che in questo modo permette un comportamento asincrono del servizio esposto.

Il web.config dovra’ essere modificato sovrascrivendo il ManualWorkflowSchedulerService con :

<add type=”System.Workflow.Runtime.Hosting.DefaultWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35″ />

Di conseguenza le activies asincrone (invokeworkflow, delay) saranno abilitate e nel caso delle delay non si avra’ bisogno di UseActiveTimers = “True” nel config.

Di seguito un esempio di test:

Contract:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.ServiceModel;
   6:
   7: namespace George.Async.Wf.Library
   8: {
   9:
  10:     [ServiceContract]
  11:     public interface ISampleWfContract
  12:     {
  13:         [OperationContract]
  14:         bool ActivateWf();
  15:     }
  16: }

Workflow1:

CropperCapture[2]

   1: using System;
   2: using System.ComponentModel;
   3: using System.ComponentModel.Design;
   4: using System.Collections;
   5: using System.Drawing;
   6: using System.Linq;
   7: using System.Workflow.ComponentModel.Compiler;
   8: using System.Workflow.ComponentModel.Serialization;
   9: using System.Workflow.ComponentModel;
  10: using System.Workflow.ComponentModel.Design;
  11: using System.Workflow.Runtime;
  12: using System.Workflow.Activities;
  13: using System.Workflow.Activities.Rules;
  14:
  15: namespace George.Async.Wf.Library
  16: {
  17:     public sealed partial class Workflow1 : SequentialWorkflowActivity
  18:     {
  19:         public Workflow1()
  20:         {
  21:             InitializeComponent();
  22:         }
  23:
  24:         private void logWorkflowStarted_ExecuteCode(object sender, EventArgs e)
  25:         {
  26:             System.Diagnostics.EventLog.WriteEntry("Workflow1", "workflow started");
  27:         }
  28:
  29:         private void logInvokeWorkflow2_ExecuteCode(object sender, EventArgs e)
  30:         {
  31:             System.Diagnostics.EventLog.WriteEntry("Workflow1", "invoking Workflow2");
  32:         }
  33:
  34:         private void logEndWorkflow1_ExecuteCode(object sender, EventArgs e)
  35:         {
  36:             System.Diagnostics.EventLog.WriteEntry("Workflow1", "end Workflow1");
  37:         }
  38:     }
  39:
  40: }

Workflow2:

   1: using System;
   2: using System.ComponentModel;
   3: using System.ComponentModel.Design;
   4: using System.Collections;
   5: using System.Drawing;
   6: using System.Linq;
   7: using System.Workflow.ComponentModel.Compiler;
   8: using System.Workflow.ComponentModel.Serialization;
   9: using System.Workflow.ComponentModel;
  10: using System.Workflow.ComponentModel.Design;
  11: using System.Workflow.Runtime;
  12: using System.Workflow.Activities;
  13: using System.Workflow.Activities.Rules;
  14:
  15: namespace George.Async.Wf.Library
  16: {
  17:     public sealed partial class Workflow2 : SequentialWorkflowActivity
  18:     {
  19:         public Workflow2()
  20:         {
  21:             InitializeComponent();
  22:         }
  23:
  24:         private void logStartWorkflow2_ExecuteCode(object sender, EventArgs e)
  25:         {
  26:             System.Diagnostics.EventLog.WriteEntry("Workflow2", "workflow started");
  27:         }
  28:
  29:         private void logEndWorkflow2_ExecuteCode(object sender, EventArgs e)
  30:         {
  31:             System.Diagnostics.EventLog.WriteEntry("Workflow2", "end Workflow2");
  32:         }
  33:     }
  34:
  35: }

Output:




No Comments


You can leave the first : )