Il y a déjà quelque temps qu’EndPoint Tv a publié une série de vidéo expliquant les bonnes pratique pour réaliser ses worklfow et ses activités custom. La bonne idée de Microsoft a été de nous les regrouper sur un seule page : http://code.msdn.microsoft.com/wf4BP.

Depuis le temps que je voulais regarder les deux première vidéos, j’ai profité de la publication de la troisième pour me faire une mâtiné popcorn avec la trilogie Workflow Foundation 4….

à chacun ses films cultes après touts ;).

De ces trois premières vidéos j’ai extrait 4 slides importantes

1 – “Be a Good Runtime Citizen”

CustomActivities_BestPractices0 Cette slide est certainement celle qui m’a fait le plus plaisir. Car chez moi, elle enfonce un porte déjà grand ouverte. Mais pour certain elle affirme haut et fort ce pour quoi je milite déjà depuis quelques temps :

- Les activités ne doivent pas contenir dans leur méthode Execute d’appel bloquant! Dans la vidéo il y a un bel exemple avec la méthode ReadLine de Console. Dans le même ordre d’idées j’ai publié un article sur cette page : [WF4] Coder une activité d’attente non bloquante sans bookmark.

- Les opérations d’entrées sorties doivent se faire hors du thread qui a programmé l’exécution de votre activité. On doit dont utiliser l’AsyncCodeActivity! Là encore je suis plutôt comptent d’avoir un article sur le sujet avec l’avertissement que j’avais fait sur la programmation dite parallèle et WF : [WF4] La programmation parallèle avec Workflow Foundation.

- Les types utilisés dans vos workflow doivent être sérialisables. Ce n’est que dans le cas où vos workflow n’utilisent pas la persistance que vos types peuvent être non sérialisables. Si vous voulez à tout prix tenter l’espérance, utiliser plutôt des extensions pour accéder à vos types non sérialisables et pensez à les marquer comme NonSerialized ;). Pour plus d’informations sur les extension : [WF4] Pas si compliquées les extensions ;)

 

2 - “Performance”

CustomActivities_BestPractices1

Ce slide sur les performance est très instructif sur la vision qu’a l’équipe Workflow Foundation sur le manière de tirer au mieux partie de son travail.

- Encore une fois il est sujet de l’optimisation de la metadata qu’il est préférable d’écrire soit même. L’intention et simple : se passer de la méthode par défaut qui se base sur la réflexion et qui serra donc moins performante qu’un code dédié à votre activité. Pour avoir un idée plus claire du sujet : [WF4] Optimiser la metadata produite par vos activités.

- La mise ne cache des délégués utilisés dans les callback. Là je me rends compte que j’était totalement passé à côté de cette axe d’optimisation :(. L’idée est simple, plutôt que d’instancier un délégué pour un callback sur chaque appel  à la méthode Schedule du contexte, on garde une instance interne à notre activité et on la réutilise. j’ai ajouté ici un petit exemple de séquence custom :

- Limiter les appels aux éléments de type Variable. Il en est de même pour les arguments. Voici un petit article présentant ce qu’il est possible de faire proprement :  [WF4] Optimiser l’emploi de vos arguments je trouve que celà vas aussi bien de paire avec les valeurs par défaut qu’il est préférable de donner aux arguments : [WF4] S’assurer de récupérer correctement ses arguments.

- Déclarer les Varaible au plus près de l’endroit où elles seront utilisées. Ceci est relativement simple : Dans un code C# ou Vb vous ne déclarez les varaibles que dans les méthodes où vous en avez besoin. Dans WF4 il en est de même. Si on veut faire du WF propre et performant, nos variables doivent avoir leur portée réduite au scope dans lequel elles seront utiles. Ne mettez donc pas toutes vos variables dans la première séquence de votre workflow. Mettez les là dans le scope où elles seront utilisées.

Voici un petit exemple avec une séquence custom : les délégués sont mis en cache dans deux variables m_CompletionCallback et m_FaultCallback.

/// 
/// Activity based on NativeActivity
/// 
public sealed class MySequence : NativeActivity
{
    public readonly Collection m_Activities;
    private Int32 m_Index;

    private CompletionCallback m_CompletionCallback;
    private FaultCallback m_FaultCallback;

    /// 
    /// Constructeur
    /// 
    public MySequence()
    {
        this.m_Activities = new Collection();
    }

    /// 
    /// Collection d'activités de la séquence
    /// 
    public Collection Activities
    {
        get { return this.m_Activities; }
    }

    /// 
    /// Execute
    /// 
    /// WF context
    /// 
    protected override void Execute(NativeActivityContext context)
    {
        // Initialisation
        this.m_Index = 0;

        // Caching callback delegates
        this.m_CompletionCallback = new CompletionCallback(this.MyCompletionCallback);
        this.m_FaultCallback = new FaultCallback (this.MyFaultCallback);

        // Schedule next activity
        this.ScheduleNext(context); 
    }

    /// 
    /// Schedule next activity in activities collection
    /// 
    /// 
    private void ScheduleNext(NativeActivityContext context)
    {
        if (this.m_Index < this.m_Activities.Count)
        {
            context.ScheduleActivity(
                this.m_Activities[this.m_Index],
                this.m_CompletionCallback,
                this.m_FaultCallback);

            this.m_Index++;
        }
    }

    public void MyCompletionCallback(NativeActivityContext context, ActivityInstance completedInstance)
    {
        this.ScheduleNext(context);
    }

    public void MyFaultCallback(NativeActivityFaultContext faultContext, Exception propagatedException, ActivityInstance propagatedFrom)
    {
        throw propagatedException;
    }


    /// 
    /// Register activity's metadata
    /// 
    /// 
    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        if (this.m_Activities != null
            && this.m_Activities.Count != 0)
        {
            foreach (Activity a in this.m_Activities)
            {
                metadata.AddChild(a);
            }
        }
    }
}

3 - “Naming”CustomActivities_BestPractices2

Arg!!! les conventions de nommages. C’est généralement le petit truc qui passe mal quand on parle de bonnes pratiques. Dans le cas présent, il n’y a pas grand chose à redire. On reste sur les grandes lignes qui s’appliquerait à tout autre code autre que WF. Microsoft nous dit simplement d’oublier WF3 et de faire du workflow comme on fait déjà le reste.

- Les activités n’ont pas besoin de suffixe (ni de préfix). On leur donne un nom lié à l’action qu’elle exécuterons. On reste simple.

- Les Arguments n’ont pas besoin de suffixe (ni de préfix). Comme pour une méthode normale.

- Les Varaibles n’ont pas besoin de suffixes (ni de préfix). Comme pour les Arguments.

- Ajouter le suffixe “Scope” aux activités qui ont pour objectifs de décorer les activité qu’elle est amené à contenir. Par exemple : CorrelationScope, CancellationScope.

4 - Properties versus Arguments versus ActivitiesCustomActivities_BestPractices3

Là il y a un vrai match ;)

En fait on nous a mis ici le doit sur un aspect qui fait mal quand on n’y prend pas garde : proprerty, argument et activity n’ont pas la même manière de “vivre” au sein de votre activité :

- L’argument n’est évalué qu’une seule fois. Il est donc inutile dans une activité de l’utiliser dans un test pour savoir si son expression a changé.

- L’activité peut être réévaluée à plusieurs reprises dans votre activité cutom car on peut programmer son exécution via le contexte autant de fois qu’on le souhaite.

- La propriété type CLR (celle que vous écrivez tout les jours dans vos classes). Son utilisation est à envisager dans le sans “paramètre fixé pour un workflow”. Toutes vos instances du même workflow auront toujours la même information.

Pour illustrer ces propos, Leon Welicki a codé une activité de type While dans laquelle la condition est contenue dans une propriété de type Argument puis Activity. L’execution de ces deux codes donne un comportement facile à comprend. Dans le premier cas, l’expression contenue dans l’argument ne pouvant être exécuté qu’une fois, la boucle while est sans fin. Dans le second cas, l’activité contenant l’expression de test est réévaluée à chaque appel. La boucle se termine normalement lorsque la condition contenue dans l’expression n’est plus vrai.

Sur ces exemples Leon Welicki  a été un peu charrier par Ron Jacobs car ses callback n’étaient pas mis en cache ;)

La propriété CLR n’est bien entendue pas présentée dans cet exemple car elle ne peut pas contenir d’expression.

 

Pour le moment, seules trois vidéos de Leon Welicki et Ron Jacobs y sont disponibles à cette adresse http://code.msdn.microsoft.com/wf4BP. Les deux suivantes ne devraient plus tarde à venir.

PS : En petit bonus pour ceux qui n’auront pas le courage de suivre ces vidéos en anglais : Leon Welicki a dit qu’il y aura bien un livre blanc des bonnes pratique WF4, mais il ne donne pas de date de sortie pour le moment… wait and see ;)