Utiliser la persistance avec Workflow Foundation

| Louis-Guillaume MORAND | Jérôme Lambert |

Au cours de cet article, nous verrons comment mettre en place le mécanisme de persistance afin de rendre plus fiable l'utilisation de Workflow Foundation au sein de vos applications.

N'hésitez pas à commenter cet article ! Commentez ♪

- Titre : Utiliser la persistance avec Workflow Foundation

- Auteurs : Louis-Guillaume MORAND, Jérôme Lambert

- Parution : 17 décembre 2008

- Licence : Copyright ® 2008 Louis-Guillaume Morand et Jérôme Lambert. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Présentation

- I. Intérêt de la persistance

- II. Préparation

- II-A. Contexte

- II-B. Base de données

- III. Persister un workflow

- IV. Récupérer un workflow persisté

- V. Utilisez des versions différentes de votre workflow en même temps

- VI. Pour aller plus loin

- VII. Conclusion

Présentation

L'une des premières problématiques à laquelle j'ai été confronté en utilisant Workflow Foundation a été de gérer la notion de persistance. Il faut savoir que j'ai débuté avec les workflows à l'aide d'outils dédiés comme StaffWare qui étaient très complets, mais aussi très chers et il fallait surtout parfois les personnaliser à l'aide de code…JAVA.
Pour ces deux raisons (véridique), les choix techniques dans l'entreprise dans laquelle je travaille ont été de se tourner vers Workflow Foundation, moins cher, plus facilement utilisable par des concepteurs .Net et surtout plus léger; peut-être même trop léger…

La grande force de Worfklow Foundation, mais aussi sa plus grande faiblesse, est que cela fonctionne comme une « simple méthode », au sein de vos applications. Cela signifie qu'il ne faut pas de serveur dédié, qu'il ne faut pas forcément de base de données dédiée et que le développeur garde entièrement la main sur tout le workflow.

Tout ceci est très beau et très utile sur le papier, mais quand on veut garantir une fiabilité de workflow et garantir que tout workflow qui commence est en mesure de se terminer correctement qu'importe les contraintes (application qui se coupe, crash serveur, tremblement de terre), alors cela devient plus délicat. En effet, le Workflow Foundation fait ce qu'on attend d'un moteur de workflow basique, mais pas plus.

Or, comme dans quasiment toutes les applications utilisant un workflow, on veut s'assurer que même dans le cas d'un workflow durant moins d'une seconde, si la première tâche de ce dernier s'exécute et que le serveur crashe par la suite, on veut pouvoir relancer le workflow pour exécuter la(les) tâche(s) suivante(s) sans la moindre perte de données et c'est à ce moment-là que la notion de persistance entre en jeu.

Comme nous l'avons vu dans l'article précédent, Workflow Foundation n'est pas à proprement parler un moteur de Workflow, c'est une surcouche du framework .Net qui permet d'implémenter une notion de workflow au sein de vos applications. Workflow Foundation est une mini brique qui ne fait que ce qu'on lui demande et par défaut, cette brique ne fait pas de persistance. Nous allons donc voir au sein de cet article comme mettre en place cette persistance.

I - Intérêt de la persistance

La persistance sera plus ou moins critique selon la durée de vie de votre workflow et des choix techniques que vous ferez. En effet, plus le workflow sera long, plus il sera important de persister son état. Cette criticité est augmentée par le niveau de fiabilité de l'application qui hébergera votre workflow, car plus cette application risque de planter ou d'être fermée prématurément, plus grands sont les risques qu'un workflow en cours d'exécution cesse d'un coup sans avoir pu se finir ou enregistrer son état.
La persistance de votre workflow vous permettra alors de mettre en place un service de sauvegarde d'état (persistance) et de récupération de cette sauvegarde pour reprendre les workflows là où ils s'étaient arrêtés.

Un autre avantage insoupçonné de la persistance est d'améliorer les performances de votre application. En effet, imaginez un workflow comme une chaine d'usine sur laquelle vous déposer un matériel et celui-ci est transformé le long du process. Ce matériel est en .Net, un objet et cet objet prend de la place en mémoire. Vous aurez alors probablement à lancer des centaines d'objets au même moment et la quantité de mémoire utilisée ne fera alors qu'augmenter. Dans le cas d'un workflow séquentiel ininterrompu, vous ne pouvez pas faire grand-chose, mais dans le cas où celui-ci passe en mode d'attente à un moment ou un autre, il est possible durant ce délai d'attente de décharger cet objet de la mémoire et de le sérialiser en base de données pour ne le désérialiser qu'au moment où vous aurez besoin de l'utiliser de nouveau.

II - Préparation

II-A - Contexte

Pour nous concentrer sur la persistance et uniquement la persistance, nous prendrons en exemple un très très simple workflow séquentiel exécutant différentes tâches les unes après les autres sans contraintes externes comme un EventListener. Il s'agira d'un workflow de cuisson, prenant un œuf fermier ou non, et définissant un état cuit ou non cuit.
Voici le schéma de notre workflow.

Nous avons un délai d'une minute (délai nous permettant dans le cadre de cet article, de faire crasher l'application afin d'assister à la persistance) ainsi que deux codeActivity, affichant des messages dans la console afin de vérifier à quelle étape du workflow nous nous trouvons.

Voici son code complet :

public sealed partial class monWorkflow : SequentialWorkflowActivity
{
private OeufALaCoque oeuf;

public OeufALaCoque Oeuf
{
get { return oeuf; }
set { oeuf = value; }
}

public monWorkflow()
{
InitializeComponent();
}

private void codeDebut_ExecuteCode(object sender, EventArgs e)
{
Console.WriteLine("Cuisson démarrée");
oeuf.Cuit = true;
}

private void codeFin_ExecuteCode(object sender, EventArgs e)
{
Console.WriteLine("Cuisson terminée");
Console.WriteLine((oeuf.Cuit) ? "Oeuf cuit" : "Oeuf non cuit");
Console.WriteLine("Oeuf fermier? {0}", (oeuf.Fermier) ? "oui" : "non");
}
}

II-B - Base de données

Le cas le plus simple d'utilisation de persistance (et aussi celui conseillé par Microsoft), consiste en l'utilisation d'une base de données SQL Serveur, dans lequel nous créerons des tables qui seront utilisées pour stocker les données persistées.
Bien entendu il est possible de persister vos données dans un autre SGBD ou encore simplement dans des fichiers, mais dans ces cas-là, vous devrez vous-même développer votre service de persistance (voir le dernier chapitre de cet article). Si vous êtes pressés ou que tout simplement, vous souhaitez utiliser les outils que met Microsoft à votre disposition alors vous pouvez utiliser le SqlPersistanceService qui consiste en un service déjà prêt à être utilisé et qui ne nécessite que l'exécution de deux petits scripts SQL afin de préparer la base de données.
Les deux scripts qui nous intéressent sont SqlPersistenceService_Schema.sql (pour les tables) et SqlPersistenceService_Logic.sql (pour les procédures stockées) qui se trouvent dans le répertoire C:\Windows\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\

Exécutez ces deux scripts dans votre serveur de base de données MS SQL Serveur et vous devriez alors obtenir deux tables et les procédures stockées relatives :

La table la plus importante est la table InstanceState. C'est dans celle-ci que se trouveront les informations principales de vos instances qui ont été persistées. Vous y trouverez donc un GUID représentant chaque instance, son statut (running, suspended, terminated ou completed) ou encore sa sérialisation complète (state), ainsi que quelques informations supplémentaires sur le moment de la persistance.
La table CompletedScope ne contiendra pas comme pourrait le faire croire son nom, la liste des instances finies. Elle sera bien souvent vide selon le type de workflow que vous créerez, car elle ne sert qu'à stocker l'état des transactions à l'intérieur des workflows (voir les activités Compensatable).

Voici un exemple d'une instance de notre workflow persistée en base :

Maintenant que tout est prêt, nous allons voir comment l'utiliser sérieusement au sein de nos applications.

III - Persister un workflow

Revoyons rapidement comment nous avons appris avec les articles précédents à instancier un nouveau workflow et lui passer un paramètre.

Trois petites lignes suffisent avec lesquelles, nous créons un dictionnaire de données qui prend un object de type OeufALaCoque avec une clé nommée « Oeuf ». Ce nom est le nom de la propriété publique de notre classe de workflow (voir chapitre 2.1).
Ensuite nous créons une nouvelle instance à l'aide du runtime WF, en lui passant le nom du workflow que l'on veut créer et les paramètres que l'on veut transmettre. Enfin, nous démarrons l'instance.

Dictionary<string, object> dic = new Dictionary<string, object> {{"Oeuf", new OeufALaCoque(){Fermier=true}}};
WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof (DVP_WF_Persistance.monWorkflow), dic);
instance.Start();

Maintenant que vous avons de quoi faire tourner un workflow, nous allons devoir mettre en place un mécanisme de sauvegarde régulier de notre workflow. Ce mécanisme consiste en un service (définition: application ou code, tournant en arrière plan et de façon continue, durant tout le temps de vie d'une autre application, ici, notre runtime de workflow).
Ce mécanisme, vous pouvez soit le créer de toute pièce (voir le chapitre 5), ou alors utiliser un service prêt-à-l'emploi : le SqlWorkflowPersistenceService. Ce service est une classe qui ne requiert qu'une base de données et un ou deux réglages.

Pour le créer ou l'utiliser, il existe deux manières. La manière déclarative, qui consiste à l'utilisation de façon claire dans le code :

using System.Workflow.Runtime.Hosting;
...
SqlWorkflowPersistenceService stateservice =
new SqlWorkflowPersistenceService(
"Data Source=localhost;Initial Catalog=Developpez;Integrated Security=True", true,
new TimeSpan(0, 1, 0), new TimeSpan(0, 0, 30));

workflowRuntime.AddService(stateservice);

Code avec lequel, vous créez un service, en lui passant en paramètre une chaine de connexion pointant vers la base de données où sont stockées les tables de persistances. Vous passez ensuite un paramètre qui définit si le service cherche à décharger automatiquement les workflows qui sont inoccupés (idle) afin de libérer de la mémoire, et finalement, vous ajoutez deux paramètres, définissant respectivement la durée pour garder un workflow pour son propriétaire (cas où plusieurs applications utilisent les mêmes tables de persistance) et enfin le délai entre chaque « sauvegarde » du service de persistance.

Enfin, nous ajoutons ce service au runtime workflow avant que celui-ci ne soit démarré. Il est impossible d'ajouter un service à un moteur workflow déjà lancé.

Il existe un second moyen de déclarer et configurer un service de persistance. Il s'agit du fichier de configuration

<WorkflowRuntime Name="SampleApplication" UnloadOnIdle="true">
<Services>
<add type="System.Workflow.Runtime.Hosting.SqlStatePersistenceService, System.Workflow.Runtime,
Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
ConnectionString="Data Source=localhost;Initial Catalog=Developpez;Integrated Security=True"
UnloadOnIdle="true" LoadIntervalSeconds="30"/>
</Services>
</WorkflowRuntime>

Ce qui revient exactement au même que la méthode déclarative. Cela vous apporte l'avantage de pouvoir modifier le comportement du service sans avoir à recompiler à chaque fois votre application.

Que se passe-t-il s'il la persistance se fait toutes les 30 secondes et que pendant ces 30 secondes, une donnée du workflow est mise à jour et que l'application crash juste ensuite. Cette donnée sera-t-elle sauvegardée? La réponse est « Non ». Comment y remédier ? On peut simplement limiter ce cas au minimum en réduisant le temps de persistance LoadInterval au minimum (une seconde par exemple). Eh bien non, ça corrigerait ce problème, mais en entrainerait d'autres. En effet, cela signifierait que pour chaque instance, chaque seconde, plusieurs requêtes sont faites sur la base de données, entrainant très rapidement, une surcharge processeur, ainsi qu'une surcharge réseau et éventuellement un bouchonnement du serveur SQL. Pensez donc à trouver un bon compromis pour ce délai, selon vos besoins et surtout selon la criticité des problèmes qu'il pourrait entrainer par rapport au risque de perdre une donnée.

Je vous donne le code de la classe OeufALaCoque avant de compiler votre projet et l'exécuter.

public class OeufALaCoque
{
bool _cuit;

public bool Cuit
{
get { return _cuit; }
set { _cuit = value; }
}

private bool fermier;

public bool Fermier
{
get { return fermier; }
set { fermier = value; }
}

}

Rapidement, le service de persistance nous crie dessus et nous signale une exception :

Le message est clair, notre classe n'est pas taguée comme serialisable et la persistance ne peut se faire. Si vous réfléchissez deux secondes, cela doit vous paraitre tout à fait évident. En effet, la persistance n'est rien d'autre que le mécanisme de sérialisation/désérialisation d'un objet, et se fait, dans notre cas, dans une base de données.
Il faut donc que le workflow et les objets qu'il contient soient tous, sans exception, serialisables.

Ajoutez donc l'attribut [Serializable] sur votre classe et en toute logique, votre workflow devrait se dérouler parfaitement.

Vous pouvez, pour vérifier que la persistance fonctionne, lancer le workflow et profiter de l'exécution de l'activité Délai pour couper brutalement votre application. Vous alors vérifier qu'en base se trouve bien les informations de persistance de votre workflow.
Pour rappel, il y aura une ligne par instance de workflow, et donc autant de lignes que vous aurez lancé d'instances et qui auront eu le temps de persister au moins une fois.

IV - Récupérer un workflow persisté

Maintenant, que nous avons vu comment persister nos informations en base, nous allons voir comment il est possible de récupérer les instances et continuer leur exécution.

Le déchargement des instances en base était simple et vous allez voir que le rechargement l'est tout autant. En effet, il est possible via des méthodes appartenant au service de persistance, d'interroger les instances persistées et de les contrôler. Voici un exemple simple.

SqlWorkflowPersistenceService persistance =
(SqlWorkflowPersistenceService)
workflowRuntime.GetService(typeof (SqlWorkflowPersistenceService));
IEnumerable<SqlPersistenceWorkflowInstanceDescription> instances = persistance.GetAllWorkflows();
foreach(SqlPersistenceWorkflowInstanceDescription  insDesc  in instances)
{
// votre code sur les instances
}

On se sert du service de persistances pour récupérer les descriptions des instances persistées. À partir de là, il est possible d'afficher des informations sur celles-ci

Console.WriteLine("Instance n°{0} est en statut: {1}", insDesc.WorkflowInstanceId, insDesc.Status);

Ou de relancer une instance :

workflowRuntime.GetWorkflow(insDesc.WorkflowInstanceId).Load();

Pour finir, et pour justifier le fait de relancer une instance particulière, il est intéressant de savoir que vous auriez pu décharger « manuellement » une instance en appelant sa méthode Unload et en stockant au même moment son GUID dans une autre table de la base de données. Ceci vous permet de contrôler les instances persistées et savoir plus facilement à quoi elles correspondent (si vous avez pris la peine de stocker des informations pertinentes reliées au GUID sauvegardé).

Voilà, c'est tout ce qu'il faut savoir pour mettre en place rapidement et simplement un service de persistance au sein de Workflow Foundation.

V - Utilisez des versions différentes de votre workflow en même temps

Pour l'instant, nous nous sommes contentés de persister des instances d'une même version de notre workflow. Cependant, que se passe-t-il lorsque notre définition du workflow change ou bien lorsque la version de l'assembly qui contient le workflow change ?

A priori, tout se passera bien SAUF si des workflows en version 1 ont été persistés ! Avant d'aller plus loin, reprenons depuis le début avec notre workflow en version 1 et version 2.

Première étape est de s'assurer que la classe représentant notre workflow se trouve bien dans un projet de type librairie.

Ensuite, reprenons notre workflow dans sa version 1 :

Avec notre projet « DeveloppezPersistanceCore » en version 1.0.0.0. Ce qui, après compilation, conduira à une assembly « DeveloppezPersistanceCore.dll » en version 1.0.0.0.

Lançons notre application et générons quelques œufs, donc quelques workflows, et fermons l'application avec que ces derniers aient fini leur cycle de vie. Nous aurons donc en base de données des entrées de ce style :

Maintenant, procédons au changement de version de l'assembly de notre workflow. Pour cela, il faut retourner dans les propriétés de notre projet de type librairie (« DeveloppezPersistanceCore ») pour changer la version de 1.0.0.0 à 2.0.0.0.

Et modifions notre workflow en ajoutant une activité « delay » supplémentaire. Ce qui nous donne :

En recompilant notre solution, l'assembly « DeveloppezPersistanceCore.dll » sera recompilée en version 2.0.0.0.

Lorsque nous allons lancer notre application, les workflows générés auront comme version 2.0.0.0 et si nous tentons de relancer des workflows persistés en version 1.0.0.0, nous allons obtenir une exception du type « System.IndexOutOfRangeException » avec le message d'erreur suivant :

« Index was outside the bounds of the array. »

La raison est que notre workflow persisté a été sérialisé et lorsque vous tentez de le récupérer via la méthode « GetInstance », il est impossible d'accéder à l'assembly « DeveloppezPersistanceCore.dll » en version 1.0.0.0. Et d'ailleurs, le dé sérialisation ne peut qu'échouer étant donné que la classe qui représente notre workflow a changé avec l'ajout d'une activité « delay ».

La solution serait donc de donner à disposition aussi bien la version courante de notre assembly « DeveloppezPersistanceCore.dll » que les versions précédentes. Pour cela, il n'y a qu'une seule solution, c'est le « versioning » d'assemblies.

Première étape et prérequis : signer son assembly en donnant un nom fort

Pour donner un nom fort à son assembly, il suffit d'aller dans les propriétés de notre projet librairie. Rendez-vous dans l'onglet « Signing ».

Et donnez le nom que vous désirez à votre assembly :

Seconde étape : Mettre à disposition chaque version de votre assembly

Lorsque votre application tourne et qu'elle a besoin de dé sérialiser un workflow version 1.0.0.0, 2.0.0.0 ou x.0.0.0, il faut donner accès à l'assembly correspondante pour que la désérialisation fonctionne. Pour cela, on va par exemple mettre chaque version de notre assembly dans un sous-répertoire « Assemblies » de notre exécutable. Ainsi, on pourrait avoir quelque chose similaire à l'écran suivant :

Évidemment, j'ai décidé de renommer les anciennes versions de l'assembly et de les placer dans un sous-répertoire nommé « Assemblies », mais vous êtes tout à fait libre de placer votre assemblies où vous voulez et de le nommer comme vous le voulez.

Troisième étape : Référencer les anciennes versions de l'assembly dans votre projet

Pour l'instant, nous avons notre application qui tourne avec l'assembly « DeveloppezPeristanceCore.dll » en version 3.0.0.0. S'il faut dé sérialiser un workflow en version 1.0.0.0 ou 2.0.0.0, les assemblies « DeveloppezPersistanceCore_V1_0_0_0.dll » et « DeveloppezPersistanceCore_V2_0_0_0.dll » ne seront pas appelées automatiquement.

C'est au niveau du fichier de configuration que l'on va pouvoir spécifier l'emplacement de l'assembly à charger dans le cas où c'est une ancienne version à utiliser. Par exemple pour les versions 1.0.0.0 et 2.0.0.0, on aura :

<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="DeveloppezPersistanceCore"
publicKeyToken="a899c98ba2437d3c"
culture="neutral" />
<codeBase version="1.0.0.0" href="Assemblies/DeveloppezPersistanceCore_V1_0_0_0.dll"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="DeveloppezPersistanceCore"
publicKeyToken="a899c98ba2437d3c"
culture="neutral" />
<codeBase version="2.0.0.0" href="Assemblies/DeveloppezPersistanceCore_V2_0_0_0.dll"/>
</dependentAssembly>
</assemblyBinding>
</runtime>

Pour connaître le « publicKeyToken » de vos assembly, il existe un outil nommé « sn.exe » qui vous permettra de connaître cette information en le lançant de la manière suivante dans votre invite de commande :

sn -T DeveloppezPersistanceCore.dll

Pour plus d'information sur cet outil, je vous invite à vous rendre sur la page Microsoft MSDN dédiée à l'outil sn.exe.

VI - Pour aller plus loin

Il se peut que ce service de persistance ne vous suffise pas et que vous aimeriez bien agir d'une autre manière, plus complète, pour la persistance de vos workflows. La solution se trouve alors dans le développement de votre propre service de persistance. La solution qui sera utilisée sera de créer sa propre classe héritant de WorkflowPersistenceService et de redéfinir les méthodes qui nous intéressent particulièrement, à savoir SaveWorkflowInstanceState et LoadWorkflowInstanceState.

Commencez par créer une nouvelle classe nommée monServiceDePersistanceXML. Faites-le hériter de WorkflowPersistenceService et implémentez ses membres.

Vous obtenez le code suivant :

public class monServiceDePersistanceXML : WorkflowPersistenceService
{
protected override void SaveWorkflowInstanceState(Activity rootActivity, bool unlock)
{
throw new System.NotImplementedException();
}

protected override void UnlockWorkflowInstanceState(Activity rootActivity)
{
throw new System.NotImplementedException();
}

protected override Activity LoadWorkflowInstanceState(Guid instanceId)
{
throw new System.NotImplementedException();
}

protected override void SaveCompletedContextActivity(Activity activity)
{
throw new System.NotImplementedException();
}

protected override Activity LoadCompletedContextActivity(Guid scopeId, Activity outerActivity)
{
throw new System.NotImplementedException();
}

protected override bool UnloadOnIdle(Activity activity)
{
throw new System.NotImplementedException();
}
}

Il reste donc tout à faire! Commençons par créer nos méthodes de sérialisation et désérialisation XML.

Créez les méthodes Serialize et Deserialize comme suit :

private void Serialize(byte[] activite, Guid id)
{
var seriaz = new XmlSerializer(activite.GetType());
TextWriter writer = new StreamWriter(String.Format("{0}.xml", id));
seriaz.Serialize(writer, activite);
writer.Close();
}

private byte[] Deserialize(Guid id)
{
byte[] wf = null;
var seriaz = new XmlSerializer(typeof(byte[]));
TextReader reader =
new StreamReader(String.Format("{0}.xml", id));
wf = (byte[])seriaz.Deserialize(reader);
reader.Close();
return wf;
}

Il s'agit ici d'une simple sérialisation dans un format XML et j'ai volontairement choisi d'y stocker un tableau de bytes, perdant une partie de l'avantage XML, uniquement dans le but de vous montrer l'intérêt des méthodes RestoreFromDefaultSerializedForm et GetDefaultSerializedForm appartenant directement au service de persistance par défaut (WorkFlowPersistenceService).
Passons maintenant aux méthodes suivantes.

La première chose que l'on souhaite faire, c'est utiliser la bonne méthode, qui nous permettra de récupérer notre instance et agir sur cette dernière. La méthode qui nous intéresse est tout simplement SaveWorkflowInstanceState, qui est LA méthode, appelée à chaque point de persistance (=validation d'un état).

protected override void SaveWorkflowInstanceState(Activity rootActivity, bool unlock)
{
var guid = (Guid) rootActivity.GetValue(Activity.ActivityContextGuidProperty);
Serialize(GetDefaultSerializedForm(rootActivity), guid);
}

C'est assez simple, il nous faut, pour notre méthode de sérialisation, l'objet à sérialiser sous forme de tableau de bytes et l'identifiant unique de cette instance. Cette id, nous le savons, représente de façon unique une instance et nous permettra de la recharger par la suite.
Concernant la transformation en tableau de bytes, nous utilisons la méthode GetDefaultSerializedForm dont je vous parlais plus haut. Cette forme de stockage est le plus fiable et peut être utilisé pour de la sérialisation en binaire dans des fichiers texte ou simplement dans une base de données quelconque.

À l'inverse de cette méthode, nous avons la méthode LoadWorkflowInstanceState qui est appelée lorsqu'il est nécessaire de recharger une instance avant de la rendre au runtime de workflow pour que celui-ci continue son exécution.

protected override Activity LoadWorkflowInstanceState(Guid instanceId)
{
return RestoreFromDefaultSerializedForm(Deserialize(instanceId), null);
}

Cette fois-ci, nous désérialisons notre tableau de bytes avant de le retransformer en Activity pour enfin retourner ce nouvel objet à notre runtime.

Les méthodes SaveCompletedContextActivity et LoadCompletedContextActivity suivent la même logique et auront le même cœur de méthode.

Nous ignorons volontairement les méthodes UnlockWorkflowInstanceState et UnloadOnIdle car elles ne sont pas requises pour de la persistance simple.

Et c'est tout! Avec ces quelques lignes, nous avons créé notre propre service de persistance et cela nous permet de nous passer de base de données et de profiter de cette mécanique pour rendre notre application plus fiable (même les performances ne seront pas les mêmes).

VII - Conclusion

Il n'y a pas grand-chose à dire sur la persistance de Workflow Foundation si ce n'est qu'elle est requise pour quasiment tous les projets sérieux et que sa mise en place est extrêmement simple. N'hésitez pas à utiliser le service de persistance SQL déjà tout prêt, il est rodé et marche parfaitement bien.