Paolo Pialorsi

SOA, Workflow Foundation (WF), Windows Communication Foundation (WCF) e le Architetture Distribuite

febbraio 2007 - Posts

WF e BPEL 2.0

Come segnalato in questo post, a marzo dovrebbe essere resa disponibile una CTP di una serie di activity ed esetensioni per WF in grado di supportare la definizione di flussi compatibili con BPEL 2.0, oltre a poterne fare import/export. Il rilascio definitivo è previsto per Q4 2007. Questa è un'ottima notizia, considerando che mi ero già trovato più di una volta di fronte a clienti con l'esigenza di supportare BPEL ma con la voglia di utilizzare WF, quindi indecisi tra l'adozione di WF, BizTalk Server o altro ...

Bene! Se tutto verrà confermato dalla prossima versione di BizTalk avremo quindi WF integrato e con il supporto a BPEL 2.0 saremo completamente coperti in caso di esigenze di definizione di business processes "super-partes".

WebCast su WCF e Longhorn Server

Tra un paio d'ore inizierà il mio WebCast su WCF con Longhorn Server. Come da richiesta da parte di Microsoft il contenuto sarà di livello introduttivo (200) su WCF e toccherà temi legati alla prossima versione di Windows Server. In particolare vedremo come fare hosting di servizi WCF con IIS7 e WAS.

Intanto vi lascio le demo. Ci vediamo più tardi per il WebCast.

Un blog su WCF decisamente interessante

Non so come abbia fatto a sfuggirmi sino ad oggi ... ma ho appena trovato questo blog e lo segnalo sia per non perderlo, sia perché potrebbe interessare ad altri.

Meglio quindi XSLT o LINQ to XML Transformation?

Riprendo il mio post dell'altro giorno, per darvi un resoconto sui feedback che ho ricevuto (principalmente diretti e via email ... curioso) piuttosto che tramite commenti (siamo tutti un po' timidi evidentemente, ma non c'è nulla di male).

In sostanza tutte le persone che mi hanno risposto propendono per XSLT. Considerando che io adoro XSLT e consiglio di usarlo da diversi anni, posso esserne contento e dovrei ritenermi soddisfatto.

Ad onor del vero mi sembra però giusto segnalare i pro ed i contro più evidenti di entrambe le soluzioni che proponevo.

XSLT

  • PRO:
    • Possiamo scrivere trasformazioni che prescindono dall'ambiente di sviluppo: XSLT è "super-partes"
    • Possiamo modificare le trasformazioni senza dover ricompilare nulla del codice delle nostre applicazioni che usano quegli XSLT
    • Il codice è molto leggibile, perché descrive la struttura dell'output che vogliamo ottenere
  • CONTRO:
    • Il codice non viene compilato, se non con alcune piattaforma (tipo .NET 2.0) all'atto della prima esecuzione, ma a runtime, con evidenti rischi sulla stabilità degli applicativi
    • Eventuali errori nel codice XSLT, che per molti non è poi così leggibile, non sono evidenziati e non sono bloccanti per la compilazione del progetto che usa l'XSLT stesso
    • Non ci sono dati tipizzati (quanti di noi, usando XSLT 1.0, hanno desiderato funzioni per la gestione delle date o dei numeri?! Già ... magari ci fossero state nativamente!)
    • Eventuali errori nella modifica estemporanea del codice XSLT, in fase di manutenzione, potrebbero bloccare l'esecuzione di un'applicazione che di per sé si compila ed è corretta
    • Non possiamo svolgere unit testing sul codice XSLT con VSTS o altri tool

LINQ to XML Transformation

  • PRO:
    • Lavoriamo con un unico linguaggio all'interno di un unico ambiente di sviluppo: .NET
    • Lavoriamo con variabili tipizzate e con tutte le class library di .NET a disposizione
    • Il codice viene compilato e verificato in fase di compile-time e non di runtime
    • Possiamo eseguire unit testing sul codice scritto
  • CONTRO:
    • Siamo vincolati a .NET (Ma questo per Microsoft :-) non è un problema ...)
    • Il codice è meno leggibile, perché XML si legge meglio di C#, in particolare quando C# usa classi e oggetti nuovi (come quelli di LINQ to XML/XLinq)
    • In caso di modifiche alla trasformazione dobbiamo ricompilare l'applicativo (o l'assembly di estensione che contiene il codice delle trasformazioni ...)

Forse allora possiamo giocarci un jolly ... e valutiamo il codice che vedevamo nel post precedente, scritto con LINQ to XML Transformation, ma utilizzando i nuovi XML Literals di Visual Basic 9, anziché il solo LINQ to XML, in un qualsiasi linguaggio .NET. Con VB9 infatti potremo scrivere nel codice VB degli "inserti" di XML che saranno convertiti per noi, dal compilare VB9, in codice LINQ to XML. In pratica il codice C# che vedevamo nell'altro post, e che a molti di voi non è (forse giustamente) piaciuto rispetto ad XSLT, in VB9 sarebbe una cosa tipo la seguente:

 

Dim destinationXmlCustomers As XElement = _
<c:customers xmlns:c="http://schemas.devleap.com/Customers">
  <%= From   c in sourceXmlCustomers.Elements("customer")
        Where c.Attribute("country").Value = "Italy"
        Select _
        <c:customer xmlns:c="http://schemas.devleap.com/Customers">
           <c:name><%= c.Name %></c:name>
           <c:city><%= c.City %></c:city>
        </c:customer> _
  %>
</c:customers>

Dove questo "fritto misto" :-) di XML, VB9 e markup che ricordano ASP3/ASP.NET (<%= ... %>), consentono di scrivere codice XML (quindi visivamente chiaro), compilato a compile-time, verificabile con unit testing, tipizzato, che può sfruttare le class library di .NET, ma che trasmette anche l'idea di quello che sarà l'output XML desiderato, senza nemmeno i tag di XSLT che per molti sono ostici e fanno confusione. Ovviamente in caso di modifiche alla trasformazione questo codice dovrà essere ricompilato, però forse è anche giusto ricompilarlo ... dopo aver fatto unit-testing, piuttosto che provare se il nostro applicativo "gira ancora" dopo aver cambiato a mano un XSLT, magari con Notepad.

LINQ è ancora in beta (CTP di Gennaio 2007), quindi non sto dicendo cosa sia giusto o cosa sia sbagliato, anche perché è presto per tutti per dirlo (credo), però voglio stimolare la discussione su quello che sta arrivando ...

XSLT vs LINQ to XML Transformations

In questo periodo sto facendo diversi esperimenti con LINQ to XML. Devo dire che l'idea è interessante e ci sono diversi aspetti curiosi oltre che stimolanti. Per esempio stasera stavo valutando la possibilità di trasformare grafi XML usando codice .NET, tramite LINQ to XML Transformations, anziché XSLT. Lasciando stare i sentimenti :-) ... avete voglia di farmi sapere (via email o con i commenti) quale tra le due seguenti strade preferite per trasformare un documento XML fatto così:

<?xml version="1.0" encoding="utf-8"?>
<customers>
  <customer name="Paolo" city="Brescia" country="Italy" />
  <customer name="Luca" city="Florence" country="Italy" />
  <customer name="Ermes" city="Paris" country="France" />
  <customer name="Franke" city="Munich" country="Germany" />
</customers>

in uno fatto così:

<?xml version="1.0" encoding="utf-8"?>
<c:customers xmlns:c="http://schemas.devleap.com/Customers">
  <c:customer>
    <c:name>Paolo</c:name>
    <c:city>Brescia</c:city>
  </c:customer>
  <c:customer>
    <c:name>Luca</c:name>
    <c:city>Florence</c:city>
  </c:customer>
</c:customers>

La prima strada è usare XSLT:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:c="http://schemas.devleap.com/Customers">
<xsl:template match="customers">
  <c:customers>
    <xsl:for-each select="customer[@country = 'Italy']">
      <c:customer>
        <c:name><xsl:value-of select="@name"/></c:name>
        <c:city><xsl:value-of select="@city"/></c:city>
      </c:customer>
    </xsl:for-each>
  </c:customers>
</xsl:template>
</xsl:stylesheet>

La seconda strada è usare LINQ to XML Transformations:

XNamespace ns = "http://schemas.devleap.com/Customers";
XElement destinationXmlCustomers =
  new XElement(ns + "customers",
    new XAttribute(XNamespace.Xmlns + "c", ns),
      from c in sourceXmlCustomers.Elements("customer")
      where c.Attribute("country").Value == "Italy"
      select new XElement(ns + "customer",
        new XElement(ns + "name", c.Attribute("name")),
        new XElement(ns + "city", c.Attribute("city"))));

Io ho una mia idea, però vorrei sentire anche il vostro parere...

Grazie.

IDesign WCF Coding Standard

Come segnalato da Graham Elliott è disponibile da qualche giorno un documento interessante:

IDesign WCF Coding Standard

redatto da Juwal Löwy, che sicuramente è una fonte autorevole in materia. A prima vista mi sembra di essere d'accordo più o meno su tutto e questo mi conforta :-).

In particolare condivido la regola 11.6, infatti da ormai almeno 2 anni "predico" :-) di non mettere i controlli autorizzativi sulle operazioni ma di demandare allo strato business questo tipo di attività.

Per contro non mi trovo d'accordo con la regola 11.12 e 11.13, personalmente preferisco utilizzare sistemi di autenticazione indipendenti da qualsiasi ambito, quindi se devo sfruttare i provider di ASP.NET 2.0 in realtà li "appoggio" su una infrastruttura di base riutilizzabile e sganciata da ASP.NET 2.0. Per questo di solito utilizzo custom come modalità di autenticazione degli UsernamePasswordToken.

VMware 6.0 Beta e Visual Studio 2005

Siccome ci sono cascato l'altro giorno e Google mi ha salvato :-) segnalo un problema che si può verificare installando VMware 6.0 beta e il suo addin per il debugging delle VM da VS2005.

Ecco un thread che ne parla:

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1058760&SiteID=1

ed ecco l'errore: "An error has occurred while trying to access the log file. Logging may not function properly."

Occorre rimuovere, o almeno disattivare, l'addin per il debug da VS2005 di macchine virtuali VMware 6.

Posted: feb 06 2007, 11.26 by paolo | with no comments
Filed under:
Rientranza delle chiamate in WF

In questi giorni, a seguito di una consulenza su WF, ho potutoverificare in dettaglio alcuni aspetti relativi alla gestione dei thread e delle code in WF. Il motore del WF Runtime utilizza, per default, uno scheduler (DefaultWorkflowSchedulerService) che fornisce al runtime, come impostazione predefinita, un pool di 5 thread, nel caso di macchine mono-processore, oppure un pool di 4 thread per ciascun processore, nel caso di macchine multi-processore. Si tratta di impostazioni modificabili, ma questi numeri hanno un senso, soprattutto rispetto alla scalabilità delle applicazioni server-side, quindi non alzateli "a manetta" perché potrebbe dare effetti peggiori. Ricordate sempre che i thread non sono infiniti e se c'è in mezzo ASP.NET, già lui utilizza il ThreadPool di .NET.

Ciascuna istanza di workflow è eseguita da uno e uno solo di questi thread. Significa che non c'è la possibilità, se non cambiando lo scheduler di default, di eseguire passi di un singolo workflow in parallelo. Se vi state chiedendo come sia possibile allora avere una activity di tipo ParallelActivity o di tipo ListenActivity in un workflow ... ecco una brillante ed esaustiva risposta da parte del team di WF. In generale vi consiglio di leggere e monitorare l'intero blog.

Ora provate ad immaginare un workflow che utilizza una CallExternalMethodActivity per chiamare l'host e il codice dell'host, relativo alla CallExternalMethodActivity, che a sua volta utilizza un evento per richiamare il workflow, che ha una HandleExternalEventActivity definita.

Pensiamo al caso sequenziale:

 

Vi ricordo che il thread che esegue il workflow invoca l'host senza preoccuparsi della thread safety, quindi può chiamare ad esempio un'applicazione Windows Forms che faccia da host, senza utilizzare il thread di UI. Per questa ragione dobbiamo sempre preoccuparci di verificare se siamo o meno sul thread di UI prima di utilizzare eventuali informazioni ottenute dal workflow.

Pensate quindi che si crei un deadlock? Il workflow chiama l'host, l'host chiama il workflow, ma il workflow sta aspettando il "rientro" della chiamata verso l'host. Ovviamente no! La richiesta di far scattare un evento nel workflow, sbloccando la HandleExternalEventActivity, è intercettata dall'ExchangeDataService (servizio di WF Runtime preposto a gestire la comunicazione tra l'host e il workflow) ed è accodata su una coda che verrà gestita solo nel momento in cui il workflow sarà "pronto". In pratica mettendo un po' di tracing nel codice avremo una sequenza di questo tipo:

  • Pre-Method Invoke - Thread Id: 6 - Physical Thread Id 1128
  • Method Invoking - Thread Id: 6 - Physical Thread Id 1128
  • Method Invoked - Thread Id: 6 - Physical Thread Id 1128
  • Event Invoking - Thread Id: 6 - Physical Thread Id 1128
  • OnTestEvent Invoked - Thread Id: 6 - Physical Thread Id 1128
  • Post-Method Invoke - Thread Id: 6 - Physical Thread Id 1128
  • Event Invoked - Thread Id: 6 - Physical Thread Id 1128
  • Post-Event Invoke - Thread Id: 6 - Physical Thread Id 1128

In rosso sono i trace da dentro il workflow, mentre in blu i trace nell'host. Notate che tutte le righe sono relative allo stesso thread e che l'evento scatta (penultima riga) nel workflow, solo dopo che l'esecuzione del metodo dell'host è stata completata.

Questo comportamento "sincrono" è appunto dovuto al fatto che lo scheduler utilizza un unico thread per l'esecuzione del workflow.

Vediamo ora che succede con un workflow a stati, anziché sequenziale. Immaginiamo di avere il seguente flusso a stati:

La StateInitializationActivity dello stato iniziale si limita, per brevità, a rimandare il flusso nello stato successivo.

Da qui la StateInitializationActivity dello stato "IntermediateState" chiama l'host del flusso con una CallExternalMethodActivity, oltre a tracciare alcune informazioni a fini di debug.

Da qui il codice che viene invocato nell'host richiama il flusso, come spiegavo all'inizio del post. L'evento richiamato dall'host è proprio quello per cui sta in attesa l'evento eventDrivenActivity1 all'interno dello stato "IntermediateState".

Qui arriva il bello! Ci aspetteremmo un comportamento analogo a quello del flusso sequenziale: la coda prende in carico l'evento e quando il controllo torna al flusso la gestione riprende, proprio dall'evento. Invece no! Siamo nello fase di inizializzazione dello stato "IntermediateState" e le code dei suoi eventi non sono ancora state attivate, perchè potrebbe essere prematuro ricevere eventi senza che lo stato sia completamente inizializzato. Infatti in questo caso ecco il tracing estratto dal log.

  • Pre-Method Invoking - Thread Id: 7 - Physical Thread Id 4964
  • Method Invoking - Thread Id: 7 - Physical Thread Id 4964
  • Method Invoked - Thread Id: 7 - Physical Thread Id 4964
  • Event Invoking - Thread Id: 7 - Physical Thread Id 4964
  • OnTestEvent Invoked - Thread Id: 7 - Physical Thread Id 4964
  • A first chance exception of type 'System.Workflow.Activities.EventDeliveryFailedException' occurred in System.Workflow.Activities.dll
    Exception - Thread Id: 7 - Physical Thread Id 4964
    Event "TestEvent" on interface type "Test_WF_Events.ICommunication" for instance id "53b2d8c4-2cd0-4f66-a6b8-07b9b963332b" cannot be delivered.
    A first chance exception of type 'System.Workflow.Activities.EventDeliveryFailedException' occurred in Use-Test-WF-Events.exe

Al solito il log rosso proviene dal workflow, mentre il log blu riguarda l'host. Come si vede ci prendiamo un bel EventDeliveryFailedException!
La InnerException ci spiega che si tratta in realtà di un errore di comunicazione perché l'evento TestEvent non può essere notificato (cannot be delivered).

Cercando con Google potreste trovare suggerimenti che consigliano di utilizzare un bel QueueUserWorkItem del ThreadPool per far scattare l'evento nel workflow. In pratica:

ThreadPool.QueueUserWorkItem(delegate(Object args) { OnTestEvent((ExternalDataEventArgs)args); }, e);

invece di:

OnTestEvent(e);

A prima vista sembrerebbe corretto, perché accodando l'invocazione dell'evento sul ThreadPool diamo un po' di "respiro" al thread che sta eseguendo l'istanza di workflow, così da consentirgli di uscire dall'host e rientrare nel flusso. Purtroppo però si tratta di una soluzione non corretta! Infatti mettere la notifica dell'evento nel ThreadPool sposta solo il problema. In alcuni casi infatti tutto funzionerà a meraviglia, in altri avremo comunque un errore, ma di natura diversa. Dipende dal carico della CPU o delle CPU. Infatti su sistemi multiprocessore il problema si enfatizza, perché abbiamo più cicli macchina a disposizione ed è più facile che il ThreadPool evada la richiesta di notifica dell'evento _prima_ che il thread rientri nel workflow. Per verificare sistematicamente il problema è sufficiente mettere una bella Thread.Sleep dopo l'accodamento dell'evento nel ThreadPool, per prolungare la permanenza del thread del workflow nel codice host.

ThreadPool.QueueUserWorkItem(delegate(Object args) { OnTestEvent((ExternalDataEventArgs)args); }, e);
Thread.Sleep(TimeSpan.FromSeconds(15));

Eccoci! A questo punto abbiamo creato un mostro :-) ! Ad ogni giro il workflow ci dirà che non è in grado di evadere l'evento e capiremo che la vera ragione è che le code sono appunto "disabled" come spiegavo alcune righe più sopra. Ecco l'errore:

Queue 'Message Properties Interface Type:Test_WF_Events.ICommunication Method Name:TestEvent CorrelationValues:' is not enabled.

Il ragionamento fila. Infatti se guardiamo con .NET Reflector possiamo notare che ad un certo punto il metodo SafeEnqueueEvent della classe WorkflowQueuingService verifica se le code sono enabled e se non lo sono scatena proprio questo tipo di eccezione.

// [... omissis ...]

if (!queueState.Enabled)
{

throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ExecutionStringManager.QueueNotEnabled, new object[] { queueName }));

}
queueState.Messages.Enqueue(item);

// [... omissis ...]

 

Come fare quindi? Possiamo osservare che tutti gli eventi che scattano dall'host verso il workflow richiedono un EventArgs particolare, di tipo ExternalDataEventArgs o da esso derivato. Questa classe prevede una proprietà WaitForIdle che fa proprio al caso nostro. Infatti sempre con .NET Reflector possiamo notare che l'evento EventHandler della classe WorkflowMessageHandler al suo interno verifica questa proprietà:

 

// [... omissis ...]

if (eventArgs.WaitForIdle)
{
wfInstance.EnqueueItemOnIdle(queueName, message, pendingWork, obj);
}
else
{
wfInstance.EnqueueItem(queueName, message, pendingWork, obj);
}

// [... omissis ...]

Nel caso in cui sia asserita, anziché evadere subito l'evento, lo accoderà in attesa che il flusso diventi Idle. In questo modo il flusso avrà tutto il tempo di concludere la chiamata all'host, qualunque sia il tempo richiesto, per poi passare la palla all'evento. Ecco il log delle fasi, nel caso in cui impostiamo correttamente il flag WaitOnIdle a true.

  • Pre-Method Invoking - Thread Id: 10 - Physical Thread Id 3248
  • Method Invoking - Thread Id: 10 - Physical Thread Id 3248
  • Method Invoked - Thread Id: 10 - Physical Thread Id 3248
  • Event Invoking - Thread Id: 10 - Physical Thread Id 3248
  • OnTestEvent Invoked - Thread Id: 12 - Physical Thread Id 3176
  • Post-Method Invoked - Thread Id: 10 - Physical Thread Id 3248
  • Event Invoked - Thread Id: 12 - Physical Thread Id 3176
  • Post-Event Invoked - Thread Id: 12 - Physical Thread Id 3176

Notate che in questo caso c'è il cambio di thread (a causa dell'accodamento nel ThreadPool) e che il giro si chiude correttamente. Rimane comunque consigliabile accodare nel ThreadPool l'evento di callback per non bloccare il codice chiamante, anche perché senza questo passaggio avremmo comunque un errore di delivery verso il nostro evento, in quanto andremmo a creare un deadlock. Infatti la situazione diventerebbe la seguente:

  • Il thread corrente accoda un evento, in sincrono, che aspetta che il flusso sia Idle prima di scattare
  • Il flusso, con quello stesso thread, sta aspettando che il metodo che esegue il codice precedente finisca, per poi completare l'inizializzazione dello stato e mettersi Idle

Risultato: deadlock! Quindi avremmo una EventDeliveryFailedException.

Ok quindi in questi casi occorre:

  • Definire WaitOnIdle = true
  • Chiamare l'evento in un thread secondario, ad esempio usando il ThreadPool

Nessuno dei due step precedenti risolve, da solo, il problema. Servono entrambi.

L'unica altra alternativa che abbiamo, ma non cambia il risultato finale, è definire nello stato "IntermediateState" un evento ad inizio immediato, tramite una DelayActivity con TimeoutDuration impostata a 0 (zero), anziché una StateInitializationActivity. In questo modo infatti le code del flusso, relative agli eventi dello stato, verrebbero attivate prima della chiamata all'host. In questo caso non è strettamente necessario impostare WaitOnIdle a true perché comunque il thread che evade i messaggi in coda è uno solo, quindi comunque dovremmo aspettare la fine del primo evento per poter proseguire.

Ecco lo schema dell'ultimo caso.

Direi comunque che la soluzione più idonea è quella di utilizzare il ThreadPool o comunque un thread secondario e il WaitOnIdle impostato a true, sfruttando - se serve - lo StateInitialization.

Per chi volesse "divertirsi" con queste cose rendo disponibile il progettino "grezzo" che ho usato per approfondire l'argomento. C'è da giocare un po' con i commenti nel codice per abilitare e verificare il comportamento di WF nei vari casi... se non torna qualcosa potete mandarmi una email

Spero di aver dato una fotografia della situazione utile ad altri e vi rimando alle sessioni su WF che terrò alla nostra DevCon 2007 per ulteriori dettagli sul funzionamento di Windows Workflow Foundation, anche in progetti reali.

Posted: feb 06 2007, 11.23 by paolo | with 4 comment(s) |
Filed under: ,