Tech Days 2010 : Le langage Axum

Executive summary : Axum est un Domain-Specific Language basé sur C#, et qui permet de mettre en place des agents de traitement parallélisés, chacun ne pouvant modifier que les données qui appartiennent à son domaine, et communiquant obligatoirement avec les autres agents / domaines par des canaux définis à l’avance. Ce principe permet de pousser à son comble l’immuabilité, et donc d’éviter au mieux les problèmes de locks.

Session présentée par Yann Schwartz et Bruno Boucard. On commence par reprendre ce qui a été dit à la session d’hier sur la programmation parallèle, à savoir principalement qu’on ne peut plus augmenter la fréquence, et qu’il faut donc passer à la multiplication des cœurs. Ceci provoque bien sûr un changement de paradigme dans le développement.

J’en profite pour citer l’auteur de la phrase “The free lunch is over”, dont une précédente session a parlé : Il s’agit d’Herb Sutter.

La problématique de complexité en programmation parallèle

La grosse difficulté sur de la programmation parallèle est la même chose qui pousse à l’AOP : on mélange le code métier, fonctionnel, avec du code de gestion d’erreur, des évènements pour le fork / join, des constantes pour retrouver le nombre de processeurs, etc. Bref, ce n’est pas facile à maintenir. De plus, on joue sur des threads qui sont moins coûteux bien sûr qu’un processus, mais sur lesquels il faut faire très attention tout de même en termes de ressources. Du coup, il y a également un problème en termes de fiabilité de code, et ce problème augmente de manière exponentielle avec la complexité du traitement.

Même en .NET, langage un peu plus évolué que C++, tout de même, si on met en place les appels asynchrones avec des delegate, ça marche facilement sur des exemples simples, mais sur des cas plus complexes, il devient difficile de savoir par où on passe en termes de fonction, à part en mettant un debugger en place. Bref, en mode asynchrone, on perd l’intention fonctionnelle du code.

Nouvelles approches

Pour réduire ces problèmes, il faut prendre de la hauteur par rapport au système, de la même manière que lorsqu’on a délégué la gestion mémoire au langage avec les GC. On a donc des librairies de parallélisation qui permettent d’abstraire un peu la gestion des threads. Par exemple, Task Parallel Library de Microsoft.

La difficulté d’atomicité possède déjà une réponse connue de tous, à savoir la gestion transactionnelle. Pour éviter les locks, on part sur des fonctions avec des données immuables, des fonctions sans effets de bord, etc. C’est faisable avec des langages de type Haskell ou Clojure, mais avec des langages de type .NET où on peut faire des manipulations très larges sur les fichiers, etc., on ne peut pas toujours mettre en place le rollback. Si on pousse le concept au bout, on arrive sur des langages de type Erlang où tout est immuable, et on ne travaille que par recopie de données.

Pour des données, on utilise TPL, PLinq. Pour des tâches, TPL est adapté. Pour des flux de données ou toute forme de workflow, Axum est une bonne solution.

Le modèle acteur permet de ne pas se soucier de si le traitement est synchrone ou pas : chaque acteur a ses propres données, et ne communique que par messages avec des copies de données. Typique de Erlang, F#, et Axum. L’idée d’Axum est d’appliquer ce concept d’agent sur un langage objet, plus familier que du fonctionnel pur.

L’approche Axum

Axum est un DSL, basé sur .NET. On a donc bien sûr accès à toute la BCL. Axum est encore en incubation et on ne sait pas si il sortira, et le cas échéant, sous quelle forme (intégrée à C# ou F#, etc.)

Les concepts d’Axum sont les suivants :

  • Domaine : périmètre des données partagées par les agents. Les données peuvent être partagées, mais il faut le faire de manière explicite.
  • Agents : unité de traitement isolé.
  • Channel : modalité d’échange des messages entre agents.
  • Schéma : pour définir le contenu des messages.

La difficulté pour le parallélisme est de décomposer les traitements. L’approche d’Axum est de facilité la modularisation et la composition. L’application est découpée en domaines, accessibles par les agents du domaine. Les communications sur les channels sont asynchrones.

Demo

Impossible de noter le code, donc je laisse de côté, et je vous renvoie aux webcasts si vous êtes intéressés.

Note : la RC de Visual Studio 2010 est sortie cette nuit.

La démo n’arrive pas au bout à cause d’un problème d’effet de bord sur le Console.WriteLine. L’intervenant explique qu’il a fallu annoter la CLR dans Axum pour savoir quelle fonction a un effet de bord. Il faudrait à priori mettre les opérations dans un ordre différent, mais la correction n’est pas montrée. C’est un peu gênant pour la crédibilité de la solution, même s’il ne faut pas oublier qu’on est sur de la technologie en incubation.

Explications supplémentaires

Un agent peut être déclaré en lecture, en écriture ou en ne faisant aucun accès à la donnée. Du coup, c’est Axum qui se charge des éventuels ReaderWriterLock.

Le schéma ressemble un peu à un DataContract dans WCF, sauf qu’on est obligatoirement sérialisable, et largement immuable.

Le protocole d’échange de messages définit l’ordre attendu des messages, comme les transitions d’un workflow, et supporte des contraintes. Ceci permet de limiter les risques d’erreurs fonctionnels sur les envois de message par les agents. Par exemple, ils ne vont pas pouvoir envoyer un message de progression après avoir lancé un message de complétion.

Des opérateurs spécifiques permettent de mettre en place des communications plus évoluées, comme du broadcast (communication type Publish & Subscribe, où on envoie le message à tout le monde), du forward (on demande à passer à la suite), du forward-once (appel de la suite, mais en mode Fire & Forget), etc.

Limitation importante du réseau de calcul : pour l’instant, il ne peut fonctionner que sur un AppDomain.

Conclusion

Très puissant, et très bonne approche pour résoudre des problèmes complexes, mais personnellement, ça me convainc de plus en plus qu’une parallélisation est très intéressante pour un développeur si elle peut être décomposée de manière fonctionnelle. Quand il faut aller dans des solutions aussi complexes, il y a d’abord une étape de modélisation qui est complexe.

http://blog.polom.com

Posted in Retours | Leave a comment

Tech Days 2010 : Session plénière jour 2

Executive summary : La plénière de la deuxième journée se consacre plus à Office 2010. Rien de révolutionnaire, mais beaucoup de petites nouveautés (ce qui, personnellement, me convainc plus). PowerPivot, en particulier, est réellement impressionnant en termes de performances.

Je me suis demandé si ça valait le coup de prendre des notes sur les plénières, vu qu’il s’agit d’informations courtes et très peu fouillées, mais finalement, il y a quelques petites choses qui sont intéressantes à découvrir. Je ne mets pas tout ce qui a été annoncé, loin de là, mais seulement les points qui m’intéressent particulièrement.

Recherche fédérée

Eric Vernié fait une rapide démo sur de la recherche fédérée entre Bing, des Sharepoint externes accédés par DirectAccess. On peut du coup utiliser la recherche de Windows 7 et regrouper dans la fenêtre de sortie, avec la prévisualisation, des documents venant d’un peu partout.

Ce genre de choses peut être très pratique pour des développeurs si vous avez besoin de chercher dans un knowledge base qui serait dispatchée dans plusieurs sources au sein de votre entreprise, voire dans des filiales.

DirectAccess est un moyen de placer votre ordinateur nomade dans le réseau de l’entreprise par l’intermédiaire de n’importe quelle connexion utilisateur, et sans passer par un VPN.

PowerPivot

OLAP en mémoire : le but est de ne plus avoir besoin de l’IT pour produire un cube, mais de le faire en local, en intégrant des données potentiellement externes. Pascal Bellaud fait une démo sur un portable avec 100 millions de lignes, et ça réagit bien.

Le management dashboard permet de garder le contrôle, malgré qu’on a passé de la responsabilité de génération du cube à l’utilisateur final.

Personnellement, je pense que PowerPivot est une des technologies qui va le plus changer en profondeur la BI chez les éditeurs. Tout ce qui est fait en SQLServer est très bien, mais ce sont juste des évolutions. PowerPivot va plus loin : c’est vraiment une autre façon d’envisager le partage de la donnée avec les utilisateurs finaux. Bref, une technologie qui méritera d’être testée au plus vite… Je vais récupérer la beta d’Office 2010 sur un stand si possible (elle est nécessaire pour faire fonctionner PowerPivot).

Outlook et Exchange

Bon, je ne m’y connais pas du tout sur ce domaine, alors je vais faire le candide, et vous rapporter ce qui m’a interpellé de manière purement visuelle :

  • Messages vocaux transformés en texte
  • Boîte de messages déplaçables à chaud
  • Intégration de type “réseau social” des utilisateurs

La présentation du ruban dans Outlook comme étant très attendue a fait franchement rigoler dans la salle. Peut-être était-ce de l’humour de la part de Microsoft, mais quand l’orateur principal indique que le ruban apporte “une forte amélioration de productivité”, je pense qu’ils sont sérieux, et personnellement, ça me laisse sans voix…

Personnellement, j’ai un peu de mal à voir comment le modèle de Twitter peut s’appliquer de manière intelligente à l’entreprise. Déjà, dans la vie personnelle, ça ne me paraît pas particulièrement utile, et je préfère des articles rédigés où on trouve de l’information dans le temps que des messages qui se perdent à peine lu. En milieu industriel, il est à priori encore plus important de compter sur la stabilité de l’information. Mais peut-être que je n’ai tout simplement pas encore bien pris conscience de l’intérêt de séparer ces différents types d’information. En même temps, si c’est le cas et que l’aspect positif du micro-blogging est de bien séparer l’utile du futile, autant ne pas lire ce dernier…

SharePoint et Silverlight

Microsoft met le paquet sur SharePoint. Une webpart permet désormais d’intégrer du Silverlight de manière très simple, en lui passant un .xap. On revient à ce qui a été présenté hier en démo, à savoir la consommation des listes SharePoint sous forme de services.

Office 2010

Les grosses améliorations sont sur PowerPoint :

  • Montage vidéo intégré
  • Retouche vidéo
  • Ajout de signet, c’est-à-dire un marqueur temporel sur une vidéo permettant de déclencher des actions
  • Détourage intelligent des images
  • Travail collaboratif
  • Support de OpenOffice en sortie

La conclusion de l’orateur est très bien vue : “PowerPoint a toujours permis aux utilisateurs de faire n’importe quoi, mais là, ils pourront faire n’importe quoi encore plus rapidement”.

Office Web Applications : versions light des applications Office fonctionnant dans le navigateur, et pas seulement IE. Office Mobile 2010 permet aussi de faire de l’édition de documents Office sur un smartphone, y compris un iPod.

On passe très peu de temps là-dessus, mais c’est pourtant extrêmement important, car cela retourne l’argument qu’on avait souvent, à savoir que Office était bien trop gros pour des utilisateurs légers. Ils peuvent désormais utiliser cette version light.

Posted in Retours | Leave a comment

Tech Days 2010 : Team Foundation Server Basic

Executive summary : TFS Basic est un point d’entrée simplifié de Team Foundation Server. Il ne s’agit pas d’une version diminuée, car elle peut facilement évoluer vers un TFS complet, mais simplement d’une version plus facile à installer, et suffisante pour les petits projets, et qui peut s’installer sur des OS non serveurs. Bref, une excellente occasion de tester cette techno qui semble être devenue mature depuis 2008. Je vais aller chercher le CD sur le stand demain… En plus d’être facile à installer, elle ne fait aucun compromis grave sur la gestion de code source ou le build. Il faut savoir, au passage, que si vous êtes partenaire Gold, vous avez certainement des licences d’utilisation.

Session présentée par Florent Santin, technique, et Blaise Vignon, plus orienté marketing.

Introduction

Microsoft s’est lancé dans l’Application Lifecycle Management (VSS n’est pas du tout ça : ce n’était qu’un gestionnaire de code source, et encore diraient certains) il n’y a pas si longtemps que ça, en 2005. Il s’agissait de la mise sur le marché de produits que Microsoft utilisait en interne.

La présentation se fait à partir d’une machine sans rien du tout d’installé. TFS Basic est le même serveur que Team Foundation Server, sans limitation, mais simplement avec une installation fortement simplifié. Auparavant, il fallait une journée ou deux pour installer le produit. Maintenant, on peut installer le produit en trois clics, dont un double.

Sur la version 2008, il manquait le haut de gamme et le bas de gamme était pris par SourceSafe, Excel, etc. Sur la version 2010, Microsoft chercher à occuper toute la gamme, avec en particulier TFS Basic pour les petits utilisateurs et la version normale pour le milieu et le haut de gamme (processus réparti à l’international). TFS Basic est très adapté aux petites équipes :

  • Contrôle de code source
  • Automatisation des builds
  • Gestion de cas de test

Du coup, on laisse de côté les fermes de serveur, la gestion du reporting, etc.

TFS Basic s’installe sur une machine client, et peut utiliser SQLExpress comme base de données. La deuxième phase est la configuration, où il installe le service Windows de scheduler, les deux bases de données nécessaires, le site web, et un point d’entrée dans la base pour les projets. C’est intéressant car on peut fortement faciliter le portage sur un TFS non Basic. Les tâches sont personnalisables pour faire du Scrum, même sur Basic. On peut même personnaliser pour une entreprise avec un processus donné.

On a, dans TFS Basic, une vraie console d’administration graphique qui simplifie les opérations de maintenance courantes.

Très important : TFS Basic peut être étendu jusqu’à avoir un TFS complet.

Clients de TFS

Plusieurs logiciels communiquent avec le serveur TFS :

  • Visual Studio, évidemment, depuis lequel on peut créer un projet, s’attacher sur un workspace avec copie locale, etc.
  • Excel avec un plugin installé automatiquement et qui permet à un chef de projet de mettre en place des tâches.
  • Un plugin de Project (on parle bien de MS Project et pas MS Project Server), qui permet à un profil plutôt chef d’équipe de dispatcher les ressources et de vérifier l’état d’avancement de manière très granulaire.
  • Un site web qui permet de remplir les bugs ou les tâches comme sur un Bugzilla.
  • Test & Lab Manager : la version de Visual Studio pour les testeurs, leur permettant de mettre en place des scenarii de tests et des campagnes.

Gestion de code source

Toutes les fonctions de base sont là :

  • Archivage
  • Extraction
  • Etiquetage

Mais les fonctions avancées ne sont pas absentes, même si on est sur une version Basic :

  • Mise sur étagère (livrer un développement, mais dans une zone tampon)
  • Développement parallèle
  • Archivage par règle / atomique
  • Gated checkin : possibilité de conditionner une livraison au passage de certaines règles, comme la validation de tests unitaires ou le respect de règles de code.

Contrairement à SourceSafe, toute l’intelligence est sur le serveur. Un truc sympa est que, lors de la validation du code, on a le choix entre toutes les tâches en cours sur lesquelles on peut valider. Et une fois le code correspondant validé, cette tâche est considérée comme réalisée. Les intervenants insistent, à raison, sur l’importance de la mise en correspondance entre du code et un work item, pour pouvoir retrouver l’historique d’une fonction, d’une correction de bug, etc.

On dispose également d’une fonction Annotate, qui est l’équivalent du blame en CVS / SVN : on affiche, bloc par bloc, l’auteur des modifications sur le code même.

Le merge visuel des branches est impressionnant : on peut glisser-déposer des changesets sur une branche depuis une autre.

Build

La génération des livrables est également au rendez-vous, avec toutes les options intéressantes (intégration continue, à heure fixe, à la demande, gated check-in, etc.)

Un agent de build est installé comme service Windows, de façon que le build soit indépendant de Visual Studio. On peut amender le workflow de définition du build avec de nombreuses actions pour qu’il se charge de lancer des tests, envoyer des mails, générer un fichier .msi, etc. Le tout paraît très adaptable, et c’est apparemment le Workflow .NET qui est utilisé, ou à tout le moins son designer.

Il faut passer par la console graphique d’administration (nouveauté) pour installer l’agent de build, et la KB977226 est nécessaire pour la beta2 quand elle est utilisé sur un OS français.

Quelque chose de très intéressant est que, avec l’association forte qu’on a entre les work items et le code, et la gestion historisée des builds, on peut donner la liste des corrections contenues dans un build donné par rapport au précédent. Encore une fois, pour quelqu’un comme moi qui s’intéresse surtout à ce qui va apporter de la productivité à une équipe de développement, c’est le genre de fonction qui ne saute pas aux yeux, mais qui est pourtant à elle seule un facteur de décision pour partir sur TFS.

Conclusion

Vu que nous avons notre propre plateforme de build et de gestion de code source, je me demandais si nous avions vraiment un avantage à passer sur TFS, mais cette version Basic m’a définitivement convaincu que ça valait le coup, ne serait-ce qu’en commençant en parallèle sur un petit projet.

http://blogs.developpeur.org/azra pour plus de détails sur les astuces de Florent Santin.

Voilà pour la première journée. J’ai les doigts usés, mais je tiens le rythme pour l’instant !

Posted in Retours | Leave a comment

Tech Days 2010 : WCF RIA Services

Executive summary : WCF RIA Services est un framework assez extraordinaire, dans le sens où, en règle générale, un framework correspond à un tiers. Ici, on ajoute une couche composite en deux parties (client / serveur) autour des services WCF, pour mieux faire communiquer un client, typiquement Silverlight, avec des services de données (souvent Entity Framework, mais potentiellement n’importe quel fournisseur de données, même sous forme de services, comme le montre un exemple avec SharePoint). Cette approche permet par exemple, de définir une seule fois des règles métier qui seront appliquées sur le serveur mais aussi sur le client. Réellement impressionant. A tester, parce que porteur d’un vrai potentiel de productivité.

Vu que les gens se sont rendus compte que les places sont parfois chères, c’est le rush, et tout le monde est assis un quart d’heure avant le début de la session. Les intervenants meublent donc en répondant à des questions générales sur Silverlight (déploiement, etc.). Le plugin Silverlight est désormais, selon Microsoft, présent sur 50% des PC grand public. Le déploiement ne sera à priori jamais poussé par Windows Update. Ca permettrait de monter très vite à un niveau de pénétration énorme, mais il y a apparemment des problèmes légaux (j’imagine que ce que l’orateur voulait dire est qu’un concurrent installé de Silverlight – pas de nom : même Apple tire dessus, alors on ne va pas en rajouter – péterait certainement un câble auprès de la Commission Européenne si Microsoft faisait ça).

Buts de RIA Services

Les buts de ce framework sont de simplifier le développement RIA, d’améliorer la productivité métier et de masquer la plomberie. En particulier, pour le métier, il serait intéressant de ne pas dupliquer les règles métier simples de validation, tout en ne faisant pas obligatoirement un aller-retour serveur. Donc, faire quand même une validation côté client, mais sans dupliquer le code. Ca paraît intéressant…

Démo

Les intervenants montrent une historique des applications n-Tiers, avec l’évolution des couches et des liens entre chaque, et on passe à la première démo.

On commence par un modèle Entity Framework, et on crée des services sur ce modèle pour le récupérer sur Silverlight. Le but de RIA WCF Services crée un Domain Service, qui va englober tout ceci. On ajoute donc ce type d’élément en le faisant pointer sur le modèle EF. On peut choisir les entités à exposer et si elles doivent être éditables ou pas.

RIA Services génère des requêtes qui sont appelables par le client. [EnableClientAccess] indique à Silverlight que ces fonctions lui sont accessibles. RIA Services n’est pas limité à EF comme base de fonctionnement, mais peut s’accommoder d’autres services fournisseurs de données. Le but est d’encapsuler tout ce qu’il faut pour Silverlight. Cette couche permet aussi de ne pas avoir à recalculer les proxies lors du changement des services. C’est annoncé comme un gain de productivité. Personnellement, je ne pense pas que ça soit prioritaire.

Le DomainDataContext est le pendant côté client du DomainService côté serveur. Ce contexte client est instancié comme n’importe quelle classe, et on peut s’en servir pour faire du binding, mais il faut aussi appeler Load() en lui passant une Query générée automatiquement sur le contexte, par exemple context.Load(context.GetMoviesQueries).

Il y a enc ore plus simple : quand on crée un contexte dans Silverlight, on dispose de DataSources qu’on peut glisser-déposer sur la surface de conception, un peu à la manière de la création d’une fusion Word ou d’un reporting SQLServer.

Plus loin avec RIA Services

On va maintenant rajouter des fonctions comme le tri, la notion de données maître / données filles, un DataPager, etc. Bref, plein de choses intéressantes à partir du moment où on peut vraiment les utiliser industriellement. Par exemple, il faudra bien voir que le gestionnaire de page descend bien jusqu’à l’instruction TOP dans la base de données, ou au moins est supporté par un cache serveur, sinon ce n’est pas la peine. La démonstration montre que c’est bien le cas, en tout cas entre le client Silverlight et le serveur WCF. Le fait de placer le pager suffit.

Bizarre : encore une fois (ce coup-ci pour la possibilité de créer des grilles de Layout directement dans le designer Silverlight), les intervenants présentent comme une nouveauté de Visual Studio 2010. A vérifier, mais je suis à peu près sûr que ça existait en VS2008.

Toutes ces fonctionnalités sont intéressantes, même si on a toujours une gêne à voir la facilité avec laquelle ça se met en place. Du coup, le risque est que les développeurs comprennent de moins en moins ce qui se passe derrière et du coup aient de plus en plus de mal à travailler sur les quelques cas où le designer ne peut plus faire le travail. Il faut quand même en laisser suffisamment pour que le développeur garde le contrôle du code, même si il est généré. Encore une fois, ceci est un point de vue personnel, et je ne suis peut-être qu’un vieux grognon…

On peut mapper sur IsBusy pour que le contexte nous renseigne sur le fait qu’il est occupé à aller chercher de la donnée côté serveur. L’affichage de cette petite boîte d’attente est bien sûr customisable en XAML, même si elle est déjà très propre.

Enfin, la démonstration montre la possibilité d’édition. RIA Services gère pour nous du tracking d’objet, de façon à ne renvoyer que les modifications atomiques de données, même dans une liste complète qui lui avait été passée avec un pager. On rajoute un bouton, et on peut ajouter un appel à SubmitChanges() sur le contexte. Il faut en plus bien sûr s’abonner aux retours de cette fonction pour vérifier qu’il n’y a pas eu de problème d’écriture sur le serveur (locks, erreurs, accès concurrents, etc.)

Validation de données

RIA Services pousse les règles extraites du modèle d’entité vers le client, de façon que Silverlight puisse afficher des erreurs de saisie. On peut par exemple mettre en place un décorateur [Required] sur le modèle côté serveur, et on a automatiquement le message côté Silverlight qui apparaît. Donc, non seulement les contrôles de validation ont été générés automatiquement sur les champs disposés sur le designer, mais en plus la règle a été descendu sur le client. Il y a aussi l’attribut [RegularExpression] ou [StringLength] qu’on peut utiliser, et bien sûr customiser avec des messages d’erreur customisés.

On peut également écrire des règles complexes en .NET et elles seront aussi portées vers le client : vu que c’est du code .NET, on peut les véhiculer automatiquement sur le tiers client.

Ca paraît anecdotique à première vue, mais en y réfléchissant un peu, je peux compter de nombreux cas de validation où ce genre de duplication nous a amené à ne pas valider côté client, et donc à avoir une application moins réactive. Pour tout ce qui est validation simple, ça pose rarement des problèmes, mais dès qu’on arrive sur du vrai métier, il y avait un choix qui est fortement simplifié par cette approche. Je me rappelle m’être demandé un jour si ça ne vaudrait pas le coup de se baser systématiquement sur des fichiers XML de règles métier qu’on enverrait sur le client en début de session. Avec cette solution, on règle le problème, et de manière plus élégante et globale.

Sécurité

Une remarque préalable est que ces services ne sont pas limités à une base de données, mais à n’importe quel POCO ou NHibernate ou même des services Azure. De même, le client préférentiel est Silverlight, mais ça peut être ASP.NET, AJAX ou même XML.

Note : Expression Gallery expose des styles contenus dans Style.xml qu’on peut simplement poser dans le projet pour que ce soit plus professionnel.

Le modèle par défaut propose un lien login sur la feuille de base, qui prend en compte la création d’utilisateur avec un formulaire et les MembershipProvider d’ASP.NET. On peut également mettre en place l’authentification intégrée Windows, et Silverlight peut envoyer les crédentiels.

[RequiresRoles] permet de rajouter des droits par rapport aux profils, etc. Par défaut, les utilisateurs sont stockés dans une base de données incorporée au projet, et on y a accès par la configuration de l’application.

On peut également personnaliser la fenêtre d’authentification. Voir le blog de David Rousset (“Davrous”, ou même “Yodaman”) pour la façon de faire. Il a eu la gentillesse de poster un commentaire avec l’adresse du billet correspondant. Pour rentrer l’avatar, par exemple, on peut utiliser le drag & drop qui est désormais géré par Silverlight, ou même le pilotage de la webcam.

Les trois dernières minutes de la session (c’est chronométré) vont servir à montrer du Sharepoint. Sharepoint expose les listes disponibles sous forme de services REST. Du coup, on peut utiliser les RIA Services pour récupérer ce contenu, en référençant l’URL de type .svc dans RIA Services comme un domaine. Par contre, dans ce cas précis, RIA Services ne masque pas l’asychronisme et il faut gérer soi-même l’appel Begin et la récupération du retour asynchrone du serveur. Il est à noter que l’Intellisense fonctionne dans tous les cas ci-dessus, y compris les objets venant des listes Sharepoint.

Conclusion

Visual Studio peut également déployer sur SharePoint à partir de la version 2010. Démonstration très impressionnante. Maintenant, la même question que plus haut : est-ce que ce n’est pas trop simple ? La bonne nouvelle, de mon point de vue, est qu’on est de moins en moins Microsoft-centrique. L’interopérabilité commence gentiment à arriver dans les faits.

Note : je rajoute ça ici après coup, parce que je ne me rappelle plus qui en a parlé. Scott Guthrie a annoncé que la RC de Visual Studio 2010 avait réglé les problèmes de performance des deux betas, et qu’on avait déjà suffisamment de steak pour lancer ça sur un netbook. L’assemblée a paru dubitative, mais personnellement, je suis convaincu : j’ai la beta sur un EeePC, et ça marche bien. Ce n’est pas la foudre, c’est clair, mais pour des petits programmes de test des technologies, ce n’est pas plus loin que des grosses solutions sur ma bécane de guerre au bureau. Donc si la RC va effectivement bien plus vite, c’est tout bon pour moi.

[Edit]

Je rajoute à la main les commentaires, suite migration de mon ancienne plateforme de blog sur celle-ci.

David Rousset :

Bonjour,

Merci pour cet excellent compte-rendu. Davrous, c’est moi Smile et le tutorial sur la personnalisation du formulaire d’authentification WCF RIA Services/SL4 se trouve ici : blogs.msdn.com/…/…ion-de-wcf-ria-services.aspx

Bye,

David

Posted in Retours | Leave a comment

Tech Days 2010 : Entity Framework 4

Executive summary : Entity Framework est le framework de mapping O/R de Microsoft. On passe directement de la version 1 à la version 4, mais les améliorations semblent le justifier. On est désormais au niveau de NHibernate, et peut-être plus pour certaines fonctionnalités, mais je ne suis pas un spécialiste de l’un ou de l’autre. Une des grosses nouveautés est l’approche Model First, permettant de faire le cheminement inverse de ce qui se fait traditionnellement en EF, et partir du modèle objet pour générer une base de données. Malheureusement, Microsoft semble retomber dans ses travers et avoir mis en place une fonctionnalité géniale qui ne pourra pas être utilisée par des applications industrielles, qui ont un cycle de vie, avec des versions. Le script SQL de création de base de données est bien généré, mais rien pour suivre les modifications. Bref, de mon point de vue, bien tenté, mais le problème reste entier…

La session est présentée par Matthieu Mezil et Alexandre Equoy. Le but est de réaliser une introduction à Entity Framework, et de voir les nouveautés, de type Model First, ou l’intégration dans des applications n-Tiers.

Mapping Objet-Relationnel

Le mapping O/R consiste à mettre en correspondance un modèle métier sur un modèle tabulaire de base de données. Le codeur utilise donc un modèle objet pour accéder aux données, ce qui représente un niveau d’indirection utile par rapport à du SQL en dur.

L’Entity Data Model fait le lien, en EF, entre le modèle de stockage et le modèle conceptuel métier. Une nouveauté d’EF4 est l’approche Model First : on n’est plus obligé de faire du bottom up en partant de la base et en remontant le modèle. On peut donc désormais faire l’inverse, mais on peut aussi faire du Plain Old CLR Object (POCO).

Demo

On ajoute un ADO.NET Entity Model dans Visual Studio 2010. Comme souvent, on se base sur Northwind comme base de données d’exemple. Les intervenants mettent en avant la capacité de singularisation ou de pluralisation des noms, mais j’ai du mal à croire que ça fonctionne en une autre langue que le français.

Une fois le modèle établi, avec un type par table, on ajoute une notion d’héritage, en reliant les clés. Un premier modèle simple est réalisé, et on montre des nouveautés d’EF4 : si on sélectionne des membres, on peut extraire ces champs dans une nouvelle entité, et les supprimer de la précédente. Il faut alors les relier dans la nouvelle entité sous forme de type complexe. Autre nouveauté : on peut mapper des complex types sur des procédures stockées, et même générer automatiquement des types de retour pour prendre en compte la sortie de la procédure.

Nouveautés

  • Intégration des clés étrangères.
  • Lazy loading : on n’a plus besoin de charger manuellement la liste des éléments associés en 1-N, et elle sera chargée seulement lors de la première utilisation.
  • Approche ModelFirst : très intéressant, car on peut partir d’un modèle objet, et laisser EF4 générer la base qu’il faut pour que ça passe. Ca peut être très intéressant, mais il serait intéressant de voir comment se passe la montée en version de la base (et surtout, comment on la porte chez un client). Ajout après coup : une question est posée là-dessus à la fin de la session, et il semblerait qu’on en soit pour nos frais : il faudra faire à la main. Comme souvent chez Microsoft : c’est super d’avoir de bonnes idées comme ça, mais si on ne les pousse pas au bout, ça ne sert à rien !
  • POCO : objets .NET utilisés comme entités, et on n’est plus obligés d’avoir de l’héritage d’un objet EF.
  • Inclusion de la validation.

Les intervenants lancent une démonstration de l’approche Model First. On crée toujours un EDM dans Visual Studio, mais à vide, et on ajoute les entités une par une. On peut ensuite lancer la commande de génération de la base de données. Le script DDL est alors créé, et pourra être rejoué pour mettre en place d’autres bases.

L’approche POCO consiste à mettre en place une librairie de classe complètement à part. Ces objets ne doivent pas avoir à hériter ou implémenter des classes d’EF4. Cela permet de mettre en place EF4 sur des architectures déjà existantes, ou aussi de ne pas être dépendant d’EF4, et donc plus facilement portable sur une autre technologie. Je ne sais pas si c’est une nouvelle politique de Microsoft, mais les intervenants ne parlent pas des technologies concurrentes (NHibernate, etc.), voire laissent à penser qu’il n’y en a pas.

Note : plusieurs fois depuis ce matin, les présentateurs font voir la fonction de génération d’une classe non connue comme une nouveauté, mais ceci existait déjà en VS2008.

Template T4

L’idée est d’augmenter la productivité en utilisant des templates qui exploitent les métadonnées d’EF4 pour générer du code. En fait, Microsoft a ouvert les modèles qui servent d’habitude à générer le code EF. Typiquement, on ne pouvait pas auparavant customiser la génération de code EF : on était obligé de prendre ce qui avait été créé, et si on avait besoin de mieux, de l’appeler ou de l’hériter depuis une autre classe. On peut désormais modifier directement le template utilisé pour générer le code, avec ainsi une très grande souplesse pour de la validation, des ajouts de fonctionnalité, etc.

Scenarii n-Tiers

Le gros problème dans ces architectures est que même si on se trouve sur des tracking entities (entités qui tracent les modifications qui leur sont apportées pour les reporter ensuite dans la base de données), le saut d’un tiers leur fait perdre le contexte. La nouvelle approche d’EF4 est les self-tracking entites pour contrer cette limite. Microsoft fournit la template pour mettre ceci en place.

RIA Services et ADO.NET Data Services sont des solutions qui s’utilisent beaucoup plus facilement avec EF. RIA Services est très lié à Silverlight, et Data Services est limité au mode REST, donc ce sont deux technologies finalement limitées, du coup. La solution proposée par Matthieu Mezil est d’utiliser WCF pour pointer sur de l’EF. Comme le code WCF est souvent le même, l’idée est de mettre en place des templates pour les opérations CRUD sur des services WCF.

Retour sur les templates

Note : T4 n’est pas limité à EF, mais peut être utilisé pour tous les cas de génération de code.

Toutes les couches WCF (services, contrats de service, éventuellement mocks pour les services, etc.) sont très ressemblantes entre chaque entité. Il est donc intéressant de les générer…

Il y a un bouton directement dans VS2010 pour régénérer toutes les sorties des templates T4 lors d’une modification des modèles. Les templates peuvent s’appliquer également à du XAML, ou n’importe quel type de sortie.

Saga EFX sur MSDN pour plus d’information.

Conclusion

Les intervenants reconnaissent que le temps de formation est bien plus important qu’en ADO.NET, mais le ROI est selon eux au rendez-vous. Toute cette technologie est à tester, et il y a des concurrents, typiquement NHibernate, sur lequel on peut greffer ActiveRecord.

Posted in Retours | Leave a comment

Tech Days 2010 : AOP en .NET (Programmation orientée aspects)

Executive summary : il y a plusieurs façons de faire de l’AOP en .NET, et Unity n’est pas nécessairement celle la plus mise en avant dans cette session, qui fait une démonstration plus longue sur PostSharp. Les tisseurs peuvent être de type statique (modification de l’IL généré, que ce soit avant, pendant ou après la compilation) ou dynamique (modification du comportement de l’application à la runtime). PostSharp est une solution du premier type. Un aspect est composé d’un greffon, soit la partie qui réalise le travail, et des points d’attache, c’est-à-dire les endroits du code où on vient appliquer le code de l’aspect.

Cette session est présentée par Romain Verdier de FastConnect, et une autre personne dont je n’ai pas eu le temps de noter le nom. Heureusement, le premier commentaire sur ce blog est de Romain, et il me précise qu’il s’agit de Yann Schwartz. Merci à lui et désolé à Yann de ne pas avoir été assez rapide pour noter son nom dès le début.

On commence par une introduction sur l’abstraction croissante des langages informatiques : on est parti du langage machine pour abstraire vers du structurel, du procédural, de l’objet, bref de plus en plus modulaire et de haut niveau.

Concepts de l’AOP

Le principe de Separation Of Concerns se dérive en principe de Single Responsibility (une classe fait une chose et une seule). C’est le cheval de bataille de l’AOP : une classe doit s’occuper d’un aspect du code, et d’un seul. Si on pollue une classe avec des traces, du transactionnel, etc., ça diminue la lisibilité et la facilité de maintenance.

L’AOP est très pratique pour ce qui est traces de code. C’est même le cas standard d’exemple, car c’est du code qui pollue le métier, donc c’est très intéressant de l’extraire. Mais il y a aussi la nécessité de gérer la sécurité qui est une bonne candidate à l’AOP. De manière générale, tous les besoins transversaux sont concernés.

Typiquement, sur une fonction métier avec une gestion transactionnelle et des traces, on se retrouve avec une méthode potentiellement longue où le fonctionnel ne représente finalement qu’une portion légère du code, et surtout mélangé dans tout le reste.

Le speaker insiste sur le fait que l’AOP ne va pas à l’encontre de l’OOP (Programmation Orientée Objet) mais plutôt la complémente si on trouve des cas transversaux qui s’applique.

L’aspect est composé d’un Advice (greffon, qui contient la partie fonctionnelle, bref ce que l’aspect est censé réaliser), et de pointcuts (points d’action, soit les endroits où l’aspect doit être injecté, et qui ne sont pas n’importe quel endroit du code, mais seuls ceux bien identifiés par le langage).

Mise en pratique

On reprend bien sûr le bon vieil exemple des traces. C’est sûr, ce n’est pas le plus original, mais ça a le mérite de bien exposer le problème. Une classe Log est appelée à chaque début de méthode. C’est gênant non seulement parce qu’on pollue le code, mais aussi parce qu’il y a risque de les oublier. L’advice, dans ce cas, est le fait de tracer le nom de la méthode, et le pointcut utilisé le début de toute méthode.

Une fois ces deux caractéristiques définies, il manque un moyen de mettre tout ceci en fonction : c’est le tisseur, qui permet d’injection les greffons aux points d’action définis. Il existe de nombreux tisseurs pour .NET, et l’inconnue principale de cette session, pour moi, était de savoir si Microsoft allait supporter un tisseur particulier, ou proposer le sien.

Certains tisseurs sont basés sur un véritable langage, d’autres sur des décorateurs de code (lorsque le langage supporte l’introspection), d’autres encore sur des fichiers tiers (XML, typiquement) ou des DSL (Domain Specific Language, avec des fichiers externes utilisant un mini-langage de définition). Quasiment tous les frameworks en .NET utilisent une modélisation objet des aspects dans le même langage qu’ils ciblent. C’est intéressant car un développeur C# continue à utiliser son langage de prédilection, même si cela peut parfois induire un peu de confusion, car on se retrouve avec un code qui n’est pas directement exécuté.

Le tissage peut être dynamique (exécuté lors de la runtime) ou statique (en précompilation, en compilation, et le plus souvent en post-compilation, avec des tisseurs qui interviennent directement sur le code IL), voire du tissage hybride (c’est souvent un choix de points d’entrée qui, lui, est statique, puis l’accroche de code sur ces hooks qui, elle, est statique).

On passe à la démo

Le framework de Microsoft pour l’AOP est Unity. Mais avant ça, il existait un moyen standard de faire de l’appel distant d’objet, et c’est le remoting. Ce concept est de moins en moins utilisé (on a plus tendance à faire des applications multitiers avec des services web, par exemple), mais il peut servir pour mettre en place de l’AOP. Par contre, il faut du coup dériver de MarshallByRefObject. Ensuite, on pose toutes les fonctions sur lesquelles on veut passer dans l’objet Remote, de façon qu’il implémente la même interface que l’objet d’origine, mais en ajoutant ce que l’aspect doit réaliser en plus du code métier. C’est la méthode du pauvre, mais qui peut être intéressante pour des besoins simples.

Une deuxième philosophie consiste à créer dynamiquement une classe qui hérite de la cible tout en surchargeant les fonctions avec les aspects entremêlés. Par exemple, Castle utilise cette méthode. Unity également, mais ce dernier framework peut également utiliser la précédente. Le tisseur se charge de générer automatiquement un proxy vers cette nouvelle classe. Au passage, il est intéressant de constater que ce genre d’appel est finalement très proche du principe d’appel des mocks et des stubs pour les tests : on génère dynamiquement des objets avec un comportement déduit de manière logique. La limite de cette approche est que la fonction ciblée doit être virtuelle.

Bref, on a le choix entre deux méthodes avec chacune sa limitation. C’est pourquoi Unity propose les deux méthodes, en fonction de ce qu’on souhaite réaliser. Il y a d’ailleurs un autre problème pour l’instant : on n’a plus la possibilité d’appeler directement la construction de l’instance, et il faut passer par une factory. Du coup, on retrouve des frameworks d’IOC (Inversion Of Control) dans les candidats à l’AOP. Un container Unity est typiquement utilisé pour faire de l’inversion de dépendances, et du coup, de l’AOP. La configuration peut être faite par code, ou par des fichiers XML (Spring.NET, par exemple).

Et pour un vrai exemple

Le but de la fin de la session est de faire voir, après les exemples simples, un cas un peu plus industriel. C’est le problème de toutes les sessions, surtout qu’elles ne durent qu’une heure : comment faire voir les concepts, mais pas avec des exemples trop simplistes ?

Romain Verdier prend la main pour présenter PostSharp et le tissage statique. Cet outil est dédié à l’AOP, contrairement à Castle Windsor ou Unity qui sont des frameworks d’inversion de dépendances, de manière plus large. Le tissage statique est moins souple, mais peut aller beaucoup plus loin que le tissage à la runtime, car il peut aller n’importe où dans le code.

C’est parti pour l’exemple : une grille bindée sur une BindingList<Instrument>, où Instrument est un POCO, qui doit du coup implémenter INotifyPropertyChanged, ce qui force à ajouter un évènement qui sera déclenché à chaque fois qu’une propriété est modifiée. On crée une fonction OnPropertyChanged qui vérifie que l’event est bien associé à des listeners (en testant la nullité) et en l’appelant en passant le nom de la propriété qui a changé. Du coup, on se retrouve avec autant d’appels que de propriétés, ce qui pollue le code. De plus, l’association à la chaîne de propriété modifiée étant manuelle, on n’est pas à l’abri d’une erreur de recopie, ou d’un oubli de mise en place de l’évènement lors de l’ajout d’une nouvelle propriété.

Remarque : dès que j’ai une connexion internet, je télécharge PostSharp et je vous poste les exemples de code si j’arrive à les retrouver. Sinon, ils seront certainement sur le site de Microsoft. De manière générale, je n’ai pas les moyens sur ces compte-rendus de faire des captures de code ou d’écran.

L’idée est de créer un aspect, qui est un attribut dans PostSharp. On place l’attribut, ici NotifyPropertyChanged, sur la classe ciblée. On n’est pas obligé de faire directement sur la classe : on peut instruire PostSharp pour qu’il applique ceci à toutes les classes correspondant à un filtre donné. Cet attribut hérite de InstanceLevelAspect, qui vient de l’API PostSharp. On le décore avec un attribut [IntroduceInterface(typeof(INotifyPropertyChanged)] et [Serializable]. L’aspect lui-même implémente l’interface INotifyPropertyChanged à la place de la classe cible. On crée le même helper de levée d’évènement, sauf que le sender n’est pas this, mais this.Instance, qui est l’objet tissé. L’event est décoré avec [IntroduceMember]. Un autre décorateur est [OnLocationSetValueAdvice, MulticastPoints(Targets = …)] qui est placé sur OnSetValue, qui contient l’implémentation de l’aspect.

On voit bien dans le code généré le code ajouté par PostSharp : ça alourdit un peu l’IL, mais on est également plus rapide que si on était dynamique.

Un deuxième exemple, plus complexe, est lancé, mais il est un peu dommage de passer du temps dessus, car la session est en retard, et du coup, l’explication est un peu expédiée.

Je me rends compte que c’est difficile à lire sans exemple de code, mais vu que je ne peux pas télécharger PostSharp, ça n’est pas possible pour l’instant. Je vais quand même voir si je peux choper un wifi public quelque part…

[Edit]

Je reprends les commentaires manuellement, suite à la migration de mon blog sur cette nouvelle plateforme.

Romain Verdier :

Et bien, quel compte rendu précis ! Merci.

L’autre personne, c’est le fabuleux Yann Schwartz (http://blog.polom.com/)

En ce qui concerne les exemples de PostSharp, il y en a pas mal dans le répertoire “samples” de l’installation. Suffit de s’inscrire sur postsharp.org pour télécharger les bits de la version 2.0.

Posted in Retours | Leave a comment

Tech Days 2010 : Session plénière jour 1

Executive summary : Visual Studio amène des améliorations sur la sélection carrée, et une fonction permettant de retrouver les tests unitaires impactés par une modification de code, de façon à ne rejouer que ces tests pour gagner du temps. Il y a bien sûr plein d’autres choses, mais ce sont parfois les plus petites fonctions qui amènent les plus gros gains de productivité.

Vu les problèmes dans le métro, et la queue monstrueuse pour obtenir son badge, je me retrouve à la bourre à la première session plénière. Bon, j’ai loupé tout ce qui est Windows 7 et 2008 R2. Remarque, ça tombe bien qu’ils aient commencé par ça, ce n’est pas ce qui me passionne le plus. Visual Studio, par contre, ça m’aurait embêté de louper les annonces.

Premier point très intéressant selon moi, juste après la présentation de Pex : le suivi des tests impactés par une modification de code. Typiquement, vous avez 5000 tests unitaires, et ils vous prennent 20 minutes à effectuer sur votre machine. C’est tout de même dommage de devoir tout relancer alors que vous n’avez pas modifié énormément de code. Cette fonctionnalité vous permet de retrouver les tests sur lesquels vous avez potentiellement de l’impact, et du coup limiter fortement le nombre de tests à rejouer, à moins bien sûr que vous ayez tout réécrit dans le System Under Test.

Pour moi, ceci est une vraie avancée, peut-être pas nécessairement la plus complexe à mettre en place de la part de Microsoft, mais qui servira beaucoup à un développeur tel que ceux de mon équipe.

Par contre, une remarque sur Pex de manière générale : je pense, mais c’est un avis personnel, que c’est une très mauvaise pratique de laisser générer sa couverture de code par un outil. La blague que faisait le présentateur de Microsoft en disant que, malgré la mise en place de frameworks de test dans Visual Studio, personne n’en faisait, et que du coup Microsoft avait mis en place un outil pour les aider, est assez révélatrice. Le problème est que les développeurs ne pensent pas en termes de testabilité. Ajouter un outil est très intéressant pour augmenter la couverture de code, mais se baser sur cet outil pour générer des tests sans les architecturer me paraît dangereux. Cela retardera encore plus la prise de conscience des développeurs de l’importance des tests, car ils considéreront toujours que c’est un à-côté du code. Voire même que c’est une tâche bas de gamme puisqu’on peut l’automatiser.

Il faudra aussi tester les limites de Pex en soi. D’expérience, je sais qu’une couverture de code même à 100% ne veut pas dire que le code n’a plus de bug. Donc, l’intérêt de ce genre d’outil dépend de son intelligence, et je ne parle pas seulement en terme de parcours de la complexité cyclomatique. Vu que l’outil est inclus dans la Beta 2 de Visual Studio 2010, je ferai bientôt une petite étude là-dessus.

Un autre point, qui paraîtra aussi certainement comme un simple détail pour quelqu’un qui a assisté à la séance, mais qui me semble vraiment intéressant du point de vue de la productivité d’un développeur : les améliorations sur la sélection carrée dans Visual Studio 2010. La sélection d’un bloc carré de code (avec ALT + la souris) existait déjà en VS2005 et VS2008, mais la nouvelle version est plus intelligente, et supprime deux des limites qui étaient les plus criantes :

  1. Le remplacement de code automatique : si vous utilisez une sélection carrée et que vous tapez du code, vous remplacer le code sur toutes les lignes par ce que vous tapez. Très pratique !
  2. Copier-coller avec suppression intelligente des espaces : si vous sélectionnez un carré avec des lignes qui n’ont pas toutes la même taille et que vous les copiez dans d’autres lignes, vous vous retrouvez souvent avec des espaces en trop. Maintenant, c’est automatiquement supprimé. Il me reste à tester si c’est quand on copie dans une chaîne ou si c’est systématique, et surtout si on peut alors débrayer ce comportement, car il y a des fois où c’est utile de le garder dans l’autre sens, typiquement pour de la mise en page.
Posted in Retours | Leave a comment

Tech Days 2010 : Compte rendus en direct

Executive summary : je suis aux TechDays pour les trois jours, je vous propose de profiter de mes notes si vous voulez savoir ce qui s’est dit sur des sessions que vous n’avez pas pu suivre.

Bon, pas tout en fait en direct, les compte-rendus, parce que je n’ai pas le temps d’aller au hotspot wifi public dans la journée, mais je vais essayer de publier le plus vite possible ces notes sur les différentes sessions des TechDays auxquelles je serai allé.

Je suis sur place les trois jours, et vu que je prends de toute façon toujours des notes, autant les mettre un peu mieux en forme, essayer de faire ressortir ce qui me paraît important, et partager ça. Comme je le disais dans mon billet d’introduction, mon point de vue est strictement industriel : pas question de vous faire une revue des nouveautés flashy. Ce qui m’intéresse, pour ma société comme pour moi-même, sont les technologies qui peuvent faire gagner du temps à un développeur.

Une remarque préliminaire, du coup : ne me jetez pas la pierre si je considère une technologie comme peu intéressante alors que ça vous paraît passionnant ou promis à un brillant avenir. Je me place toujours du point de vue d’un éditeur de logiciels, et du coup, avec des priorités qui ne sont pas celles de tous.

Deuxième remarque : si vous ne vous sentez pas de tout lire, je placerai en haut de chaque article un “executive summary” avec quelques lignes pour reprendre le plus intéressant. De toute façon, les billets sont vraiment juste des notes jetées sur ordinateur, car je n’ai pas le temps de détailler toutes les sessions. L’idée est juste d’aider des gens à prendre quelques connaissances par ci par là. Par contre, j’ai essayé que les “executive summary” soient les plus lisibles possible, même par des lecteurs qui ne connaitraient presque pas le sujet.

Bonne lecture, et surtout n’hésitez pas à m’envoyer questions ou corrections par mail.

Posted in Retours | Leave a comment

Performances d’une application prévalente en .NET avec le framework Bamboo

Introduction

Lors d’un précédent article, je vous ai présenté le cycle de vie d’une application prévalente. Nous avons constaté qu’il était possible de mettre en place un système de mise à jour des objets métier en se basant sur une migration en deux temps, et que ceci pouvait se faire sans expansion incontrôlée de la mémoire utilisée pour la sérialisation des données.

Dans le présent article, je vais vous exposer quelques retours sur la performance de chargement, d’exécution et de snapshot, en me basant pour mes exemples sur la même application exemple que celle présentée précédemment. Pour ceux qui ont lu l’article, vous ne devriez pas vous sentir dépaysés : vous verrez simplement que j’ai étendu la fenêtre vers la droite pour ajouter des fonctionnalités de test et des affichages de benchmarks. Pour les autres, je vous conseille vivement de lire le premier article avant celui-ci, sauf si vous connaissez déjà la prévalence. Un lien pour les fainéants (ne le prenez pas mal : j’en fais partie, et il paraît que c’est l’ingrédient de base d’un bon programmeur).

greenshot_2010-01-26_22-59-27

Note : Je profite de cette capture d’écran pour vous donner un conseil lorsque vous benchmarkez une application : placez votre système de test à l’intérieur même de l’application, et si possible carrément dans l’interface graphique. On a souvent tendance à ne pas vouloir polluer le code de l’application avec des tests. Mais il y a de nombreuses bonnes raisons de procéder de la sorte :

  • Vous ne polluez rien tant que vous ne validez pas votre code dans votre gestionnaire source, pour des petits tests. Et pour des grosses campagnes de benchmarks, vous pouvez tout à fait créer une branche.
  • En plaçant votre code de test à l’intérieur même de l’application, vous pouvez tomber sur des impacts de performance sur des points que vous auriez pu négliger si vous l’aviez extériorisé. Le cas typique est un moteur de processus métier que vous optimisez, sans vous rendre compte que ce qui prenait du temps était en fait le chargement dans une grille de données.
  • Enfin, cette approche vous permet de livrer votre benchmark en production, et c’est souvent très utile pour prouver à un client que, vraiment, c’est son réseau ou son serveur qui est en question et pas votre logiciel.

Comme vous l’avez deviné sur la capture d’écran, on va regarder ce qui se passe dans le cas de création en masse de données prévalentes, puis lors de leur chargement et de leur sauvegarde. On jettera bien sûr un œil à la taille occupée sur disque, et inévitablement, on en arrivera à poser la comparaison avec une base de données. Pour les plus pressés, vous pouvez sauter directement à la conclusion.

Configuration utilisée

La configuration utilisée n’est véritablement pas un foudre de guerre, même si le disque dur utilisé, assez rapide, peut influer positivement sur les résultats. La prévalence a surtout besoin de CPU pour la sérialisation, de RAM bien sûr car tout le métier est contenu en mémoire à un moment donné. Mais la rapidité du moyen de stockage est aussi importante pour l’étape de snapshot et pour le stockage des logs de commande, surtout lors d’exécutions en masse.

CPU : AMD 64 X2 4400+ à 2.2 GHz (un seul core utilisé)

RAM : 2 Go, PC3200 DDR SDRAM, noname, 5 Go/s en lecture, 1,6 Go/s en écriture (Everest)

HD : Western Digital Raptor 10K RPM (donc vieux, mais toujours plutôt bon)

Chargement en masse

La première chose à tester, si on suit l’ordre d’utilisation de l’application, va être de valider le comportement de création de données en masse. Rappelez vous que chaque création d’objet passe par la création d’une commande sérialisée qui est ensuite exécutée pour générer elle-même des objets qui se retrouveront à terme eux aussi sérialisés par le moteur de prévalence. Cette deuxième étape est en général gérée en arrière-plan, et avec une gestion différée par le moteur de prévalence. Par contre, la première étape est synchrone, et prend évidemment plus de temps que si on ajoutait simplement les objets en mémoire.

On lance le test pour 10000 personnes avec 25 réservations chacune. Pour l’instant, je n’augmente pas la taille des objets métier : les personnes sont remplies avec un GUID 32 pour le nom et pareil pour le prénom, tandis que les réservations prennent une personne en paramètre, ainsi que deux dates. Aucune compression n’est activée.

Stopwatch Chrono = Stopwatch.StartNew();
for (int Index = 0; Index < 10000; Index++)
{
    Work.Personnes.Personne NouvellePersonne = (Work.Personnes.Personne)(new Commandes.CreationPersonne(Guid.NewGuid().ToString(), Guid.NewGuid().ToString()).Execute());
    for (int IndexResa = 0; IndexResa < 25; IndexResa++)
        new Commandes.CreationReservation(DateTime.Now, DateTime.Now.AddDays(3), NouvellePersonne).Execute();
}
Chrono.Stop();
MessageBox.Show(Chrono.Elapsed.ToString());

Résultat : 55 minutes pour la création des données, 18 Mo de snapshot, et l’application met désormais un peu plus de 200 secondes à se charger (comparé à un affichage quasi instantané avant remplissage).

Est-ce que c’est bon ? On peut poser le problème dans les deux sens. Il y a bien sûr des problèmes :

  • Une application qui met 3 minutes à se charger, ça n’est clairement pas bon.
  • 55 minutes à attendre pour la création de 10 000 entrées, même complexes, c’est beaucoup trop dans l’absolu.

Mais comme toujours, il faut contextualiser :

  • Une application prévalente est faite pour rester en mémoire, et ne se relancer qu’à la suite d’une catastrophe. Si votre serveur répond bien pendant un an avant le premier crash, et que vous mettez trois minutes en plus du boot pour que votre application soit en ligne, honnêtement, ça ne pose aucun problème.
  • 260 000 objets créés (10 000 personnes avec 25 réservations attachées à chaque fois), ça fait quand même un taux de 80 objets métier à la seconde. Si vous comparez à des INSERT loggés en base, on n’est pas très loin. Evidemment, en mode bulk, ça va aller beaucoup plus vite, mais on va voir plus bas que la prévalence peut aussi gérer un mode similaire.

<remarque mode=’grognon’>
Si le premier point vous donne envie d’écrire dans les commentaires “oui, mais moi, mon application, elle doit avoir un SLA de 99,999% et on ne peut pas se permettre un arrêt de 3 secondes”, merci de vous abstenir : ce pourcentage est à peu près équivalent à celui des entreprises qui exigent ce niveau alors qu’elles n’en ont pas besoin, simplement parce que ça fait bien et qu’elles ont du fric à jeter par la fenêtre. Les cas particuliers n’intéressent que leur propriétaire.
</remarque>

BULK INSERT en mode prévalence

Cette petite saute d’humeur étant passée, reprenons sur la remarque du mode bulk. La façon de faire que je vous ai présentée ci-dessus n’est tout simplement pas adaptée au traitement en masse, de la même manière que quand vous devez entrer des milliers de lignes dans une base de données, il ne faut pas multiplier les INSERT, mais plutôt utiliser les fonctions de BULK COPY pour insérer vos données sans exploser les logs. On va donc essayer de réaliser la même chose en prévalence :

Stopwatch Chrono = Stopwatch.StartNew();
new Commandes.CreationMasse().Execute();
Chrono.Stop();
MessageBox.Show(Chrono.Elapsed.ToString());

pour l’appel. Quant à l’implémentation de la commande, voici le code :

using System;
using System.Collections.Generic;

using Bamboo.Prevalence;

namespace PrevalenceQuery.Commandes
{
    [Serializable]
    public class CreationMasse : ICommand
    {
        public CreationMasse()
        {
        }

        public object Execute(object system)
        {
            for (int Index = 0; Index < 10000; Index++)
            {
                (system as Work.Ensemble).ListePersonnes.Add(new Work.Personnes.Personne(Guid.NewGuid().ToString(), Guid.NewGuid().ToString()));
                PrevalenceQuery.Work.Personnes.Personne DernierePersonneAjoutee = (system as Work.Ensemble).ListePersonnes[(system as Work.Ensemble).ListePersonnes.Count - 1];
                for (int IndexResa = 0; IndexResa < 25; IndexResa++)
                    (system as Work.Ensemble).ListeReservations.Add(new Work.Reservation.Reservation(DateTime.Now, DateTime.Now.AddDays(3), DernierePersonneAjoutee));
            }
            return null;
        }

        public object Execute()
        {
            return Prevalence.Racine.Moteur.ExecuteCommand(this);
        }
    }
}

Bref, un truc beaucoup plus intelligent plutôt que de créer une commande d’exécution prévalente pour chaque objet.

Résultat : l’application met toujours autant de temps à se charger après coup, ce qui est logique car il faut désérialiser autant de données, à savoir 18 Mo. Par contre, côté temps passé pour la création… 1,25 secondes !

Ce problème-là étant résolu, revenons sur le temps de chargement.

Quand même, trois minutes pour charger l’appli !

Eh ben oui, ça fait long… Et on a beau être dans le cas d’une application qui est censée ne redémarrer que lorsque le serveur tombe, c’est quant même bizarre, non ? Depuis quand on met 200 secondes pour désérialiser 18 Mo de données ?

Il doit bien y avoir un truc. Et en cherchant un peu, l’os est le chargement des réservations dans le combo. Tant qu’on est à quelques milliers, ça se passe bien, mais ensuite, ça se gâte. Modifions le code à la barbare pour valider ceci :

Reservations = (List<Work.Reservation.Reservation>)(new Commandes.ListerReservations().Execute());
for (int Index = 0; Index < 1000; Index++)
    EchantillonReservations.Add(Reservations[Index]);
listeReservations.DisplayMember = "Description";
listeReservations.DataSource = EchantillonReservations;

Résultats : l’application démarre désormais en 15 secondes ! Et la prise de snapshot finale sur la sortie est de l’ordre de 10 secondes, ce qui est cohérent.

Mais une fois que tout est en mémoire…

Maintenant qu’on s’est attardé sur les points à gérer dans la performance d’une application prévalente, on va regarder un aspect extrêmement positif de la situation : tout est désormais en mémoire, et les manipulations métier les plus complexes devraient aller au plus vite.

En plus, comme brièvement abordé dans le précédent article, on va pouvoir se servir de la puissance de Linq To Objects pour réaliser tous les traitements possibles et imaginables. Prenons le cas suivant :

private void btnCalculMetierComplexe_Click(object sender, EventArgs e)
{
    Stopwatch Chrono = Stopwatch.StartNew();
    decimal SommeMetier = Reservations
        .Where(Resa => Resa.UID.Split('a').Count() > 3)
        .Bareme(Resa => Resa.Description.Length, Resa => Resa.DateDebut)
        .Sum(Valeur => Valeur);
    Chrono.Stop();
    MessageBox.Show(SommeMetier.ToString() + " " + Chrono.Elapsed.ToString());
}

Il faut bien sûr pour que cela fonctionne l’accompagner de la fonction d’extension ci-dessous :

using System;
using System.Collections.Generic;
using System.Linq;

namespace PrevalenceQuery
{
    public static class Extensions
    {
        public static IEnumerable<decimal> Bareme<T>(this IEnumerable<T> Liste, Func<T, decimal> BaseCalcul, Func<T, DateTime> DateApplication)
        {
            foreach (T Atome in Liste)
            {
                int[] Coefficients = new int[] { 0, 4, 9, 12 };
                DateTime DateCoefficients = DateApplication(Atome);
                if (DateCoefficients.DayOfWeek == DayOfWeek.Wednesday) Coefficients = new int[] { 0, 5, 10, 15 };
                else if (DateCoefficients.DayOfYear > 210) Coefficients = new int[] { 0, 3, 8, 20 };

                decimal Resultat = 0;
                decimal ValeurReference = BaseCalcul(Atome);
                if (ValeurReference < Coefficients[0]) Resultat = -1;
                else if (ValeurReference < Coefficients[1]) Resultat = 0;
                else if (ValeurReference < Coefficients[2]) Resultat = 1;
                else if (ValeurReference >= Coefficients[3]) Resultat = 2;
                else Resultat = 3;
                yield return Resultat;
            }
        }
    }
}

 

Le résultat et le temps de calcul :

greenshot_2010-01-26_22-35-25

Oui, une seconde et demie pour un calcul sur 250 000 réservations, avec un barème sur une fonction qu’on peut encore faire en SQL, mais au prix d’un sacré boulot, et d’une capacité de maintenance… comment dire… quasi nulle.

L’exemple ci-dessus a beau reproduire une manipulation métier assez complexe (le calcul d’une somme, sur certaines réservations, d’un barème qui s’applique sur un champ avec des coefficients eux-mêmes dépendant de la date portée par un autre champ de la même réservation), le code est simple et aisément maintenable. Je vous laisse imaginer l’équivalent en SQL, avec des TO_CHAR(DateDebut, ‘Q’) pour indiquer que vous voulez vous baser sur le trimestre. Je passe bien entendu sur les problèmes de calendriers. En .NET, un appel de fonction et vous passez dans une autre culture ou une autre zone de temps. Quiconque a fouraillé une fois avec la gestion des codepages ou de l’UTC en SQL pleurerait de bonheur s’il pouvait faire aussi simple.

Et encore, ceci n’est qu’une petite fonction. Imaginez le gain de temps absolument fabuleux sur toutes les applications de calcul complexe lorsque toutes vos données sont en mémoire. Alors, bien sûr, vous pouvez remonter toutes vos données en mémoire depuis une base de données, mais en passant par le cauchemar du mapping O/R, et avec, quoi qu’on en dise, une adaptation même minime de votre métier à l’aspect tabulaire. Mais justement : pourquoi stocker en base pour remonter en mémoire dès qu’on a besoin des données ? C’est là le principe de base de la prévalence : autant tout stocker en mémoire tout le temps.

Conclusion

Vous l’avez compris : une application en mode prévalent ne change en rien d’une application avec base de données. Vous devez toujours faire attention à la façon de créer des données, ainsi que de les extraire.

Par contre, une fois ces problèmes résolus, vous pouvez exploser des temps de calcul comme vous ne l’imaginiez même pas possible avec des données en base. Et ce avec une simplicité de code extrême. Je ne parle pas des avantages inhérents à Linq en termes de typage fort. Là encore, c’est un énorme point fort pour la maintenance de vos projets.

[Edit]

Je reprends manuellement les commentaires, suite à la migration de mon ancienne plateforme de blog vers celle-ci.

takuan :

Bravo pour cette présentation. C’est une des seules source en français sur la prévalence.
La prévalence en elle-même est intéressante mais le temps de chargement (15 secondes dans votre démo) pour “seulement” 250 000 éléments(10 000 personnes * 25 réservations/personne) est trop long.
Il faudrait pouvoir charger juste les noms des 10 000 personnes et ensuite charger les réservations à la demande. Mais je ne sais pas si cela est possible (s’il y a un moyen je serais curieux de le connaitre).

Dans tous les cas merci pour l’effort.
Bonne continuation.

Posted in .NET, Prevalence | Tagged , | Leave a comment

Gestion du cycle de vie d’une application prévalente en .NET

Résumé de l’article

La prévalence est une méthode peu connue consistant à remplacer la sérialisation dans une base de données par une sérialisation transparente des données dans des fichiers binaires, avec un système de logs et de snapshots pour pallier à tout redémarrage de la machine hôte. Le présent article cherche à savoir si cette méthode est industrialisable, et pose les principes de modification des objets métier. La conclusion sur ce point est qu’une application prévalence supporte très bien des montées en version, en deux phases ou bien par l’insertion d’un code de migration des données au démarrage. Le fait que le framework purge les données non utilisées garantit que l’application ne gonflera pas démesurément au fur et à mesure des versions. On aborde très rapidement la problématique de montée en charge (un article suivra sur ce point), et on finit en montrant le fort intérêt qu’une solution comme Linq To Objects peut avoir dans le contexte d’une application prévalente, pour remplacer le langage de requête SQL qui n’a plus de sens dans ce type de logiciels.

Introduction à la prévalence

Pour ceux qui ne connaissent pas (et personnellement, je me suis toujours étonné que cette technologie ne soit pas plus utilisée), la prévalence consiste à stocker les données d’une application en mémoire au lieu de les mettre en base de données. Logique : les serveurs sont aujourd’hui allumés en permanence, avec des redondances d’alimentation, et tout ce qu’il faut. Et puis, lorsqu’ils ne disposaient que de 64 Mo pour mettre 1 Go de données en mémoire, on était bien obligé de passer par le disque… Mais aujourd’hui où le moindre serveur digne de ce nom embarque 32 Go de RAM, pourquoi s’embêter à sérialiser des données sur  un disque. Pour la gestion des index ? Mais une bonne hashtable fait aussi bien ! Pour garder ses données suite à une grosse panne électrique ? Bon, là d’accord… Mais la prévalence stocke également vos données sur disque ! La grosse différence de paradigme est que pour une base de données, la localisation principale des données est sur le disque, et elle propose des mécanismes pour monter des bouts de données rapidement en mémoire. Le principe de la prévalence est d’inverser ce paradigme : les données sont plus utiles en mémoire, donc on va principalement les stocker et les manipuler en mémoire. Par contre, on va également tracer en arrière plan sur un disque le log de modification de ces données, et de temps en temps, prendre un snapshot de l’état de la mémoire, de façon à pouvoir remonter une application en mémoire en cas de catastrophe. Bien sûr, ce genre d’opération prend du temps, mais comme on le disait plus haut, sur des vrais serveurs bien sécurisés et redondés, c’est censé arriver une fois tous les dix ans. Bref, avec un peu de chance, ça n’arrivera pas durant l’exploitation de votre application.

Ressources sur la prévalence

Bon, je ne vais pas non plus répéter ce qui a déjà très bien été écrit sur la prévalence. Pour les sceptiques, une page a été écrite spécialement pour vous sur le site de référence prevayler.org : http://www.prevayler.org/old_wiki/PrevalenceSkepticalFAQ.html. Et pour apprendre en quelques pages comment fonctionne Bamboo, qui est le framework de référence pour la prévalence en .NET, je vous engage à voir http://www.eggheadcafe.com/articles/20031019.asp ou directement les ressources du site http://bbooprevalence.sourceforge.net/.

Présentations des enjeux

Il faut quand même que je vous fasse voir quelques bouts de code de démonstration de la prévalence pour fixer les idées, surtout que le but de cet article est de poser les conditions d’industrialisation de la prévalence. Personnellement, j’aime bien tester des nouvelles technologies et faire joujou avec, mais j’ai toujours tendance à me méfier des articles où tout est facile. Souvent, au moment de mettre vraiment en place une solution technologique, on commence à réfléchir deux minutes, et on se rend compte que ça risque de tourner à quelque chose de plus difficile que prévu. J’ai déjà fait deux-trois tests sur la prévalence, et quelques mois après, je me suis dit « comment on gère le cycle de vie de l’application ? », « et est-ce que la sérialisation de tous ces objets ne va pas me bouffer mes performances ? », « et combien de temps ça prend réellement de remonter 2 Go de données sur un serveur qui vient de redémarrer ? », « Et comment ça se passe si je dépasse la mémoire autorisée, surtout que j’ai beau avoir 4 Go, ASP.NET n’en utilise que 1,5, si je me rappelle bien ? », etc. Bref, posons tout ça en présentant d’abord l’exemple (bateau) sur lequel on va travailler.

Présentation d’un prototype

Je ne vais pas atteindre le niveau le plus bas de l’originalité en proposant une gestion d’annuaire téléphonique, mais ce sera à peine mieux : une gestion des réservations. Ca peut être n’importe quoi, un hôtel, un port de plaisance, ou même un cimetière…

Je fais exprès au plus simple (moche, diront certains) en WinForms. Le but est de voir ce qu’on va mettre dans les objets et comment est censé se comporter ce prototype. Bref, voici la GUI de l’application :

1

Deux objets métier seront utilisés, à savoir des “Personne” et des “Reservation”. Le code source pour ces deux objets est également on ne peut plus simple :

using System;
using System.Collections.Generic;
using System.Text;     

namespace PrevalenceQuery.Work.Personnes
{
    [Serializable]
    public class Personne
    {
        private string _UID;
        public string UID
        {
            get { return _UID; }
        }     

        private string _Nom;
        public string Nom
        {
            get { return _Nom; }
        }     

        private string _Prenom;
        public string Prenom
        {
            get { return _Prenom; }
        }     

        public string NomComplet
        {
            get { return string.Concat(_Prenom, " ", _Nom); }
        }     

        public Personne()
        {
        }     

        public Personne(string iNom, string iPrenom)
        {
            _UID = Guid.NewGuid().ToString("N");
            _Nom = iNom;
            _Prenom = iPrenom;
        }
    }
}

 

using System;
using System.Collections.Generic;
using System.Text; 

namespace PrevalenceQuery.Work.Reservation
{
    [Serializable]
    public class Reservation
    {
        private string _UID;
        public string UID
        {
            get { return _UID; }
        } 

        private DateTime _Date;
        public DateTime Date
        {
            get { return _Date; }
        } 

        private Personnes.Personne _Personne;
        public Personnes.Personne Personne
        {
            get { return _Personne; }
        } 

        public string Description
        {
            get { return string.Concat(_Personne.NomComplet, " (", _Date.ToShortDateString(), ")"); }
        } 

        public Reservation()
        {
        } 

        public Reservation(DateTime iDate, Personnes.Personne iPersonne)
        {
            _UID = Guid.NewGuid().ToString("N");
            _Date = iDate;
            _Personne = iPersonne;
        }
    }
}

Première remarque : ces objets sont notés Serializable, ce qui est logique car la prévalence a besoin de sérialiser les objets métier pour les stocker sur le disque dur, en cas de panne. Ceci est fait automatiquement, sans que vous ayez à intervenir. Toutefois, le moteur de prévalence ne stocke pas bêtement tout ce qui traîne en mémoire dans votre application, mais seulement les objets rattachés à un ensemble commun, et qui sert de base pour la structure objet de l’application.

Détaillons du coup cette classe de stockage de données :

using System;
using System.Collections.Generic; 

namespace PrevalenceQuery.Work
{
    [Serializable]
    public class Ensemble
    {
        public List<Reservation.Reservation> ListeReservations = new List<Reservation.Reservation>(); 

        public List<Personnes.Personne> ListePersonnes = new List<Personnes.Personne>(); 

        public Ensemble()
        {
        }
    }
}

 

Là encore, la classe est sérialisable, et elle contient tout simplement une référence aux deux listes d’objets que nous allons gérer dans l’application. On peut considérer que cette classe représente la totalité des objets qui seront stockés en mémoire et, de manière transparente, dans le disque dur, même si on verra plus loin que les commandes métier sont également sérialisées.

En plus de cette classe de base des objets métier, il faut une classe pour la gestion du moteur de sérialisation lui-même, de façon à lui signifier qu’il doit pointer sur la classe de base. Cette classe, dans le présent prototype, s’appelle Racine, et le code source est repris ci-dessous :

using System;
using System.IO; 

using Bamboo.Prevalence;
using Bamboo.Prevalence.Util; 

namespace PrevalenceQuery.Prevalence
{
    public class Racine
    {
        public static readonly PrevalenceEngine Moteur; 

        static Racine()
        {
            Moteur = PrevalenceActivator.CreateEngine(typeof(PrevalenceQuery.Work.Ensemble), Path.Combine(Environment.CurrentDirectory, "Persistence"));
            new SnapshotTaker(Moteur, TimeSpan.FromMinutes(1), CleanUpAllFilesPolicy.Default);
        }
    }
}

On note en tout premier la référence aux librairies Bamboo, qui est le gestionnaire de prévalence que j’utilise pour ce prototype. Il y en a apparemment un autre qui existe en .NET, sous le nom de XPrevail, mais je ne l’ai pas testé. Apparemment, il n’y a plus d’activité dessus depuis 2008. En même temps, il en va de même pour Bamboo… Comme je vous le disais, ce n’est pas une technologie qui mobilise les foules, malgré les énormes avantages qu’elle peut avoir.

La classe Racine possède un constructeur statique qui va charger le moteur de prévalence dès qu’il y en aura besoin, et ce avec les paramètres suivants :

  • Le type de la classe de base Ensemble, qui contiendra toutes les données de l’application référencées dans une seule instance.
  • Le chemin du répertoire utilisé pour la persistence, en l’occurrence ici le répertoire courant suivi de Persistence. L’avantage de ce genre de stockage est que, pour supprimer le contenu complet des données métier de votre application, c’est très simple : vous supprimez le répertoire. Très pratique en débuggage.

Il y a une deuxième ligne de commande dans le constructeur statique, à savoir la mise en place d’un moteur de Snapshot, basé sur ce moteur. Dans notre cas, il est réglé pour prendre un cliché de l’état mémoire toutes les minutes, et fonctionner avec le mode par défaut de gestion des fichiers.

Gestion des commandes

En général, lorsqu’on explique cette histoire de snapshot, une première question vient à l’esprit : « si un cliché de l’application est pris toutes les minutes, est-ce que cela signifie que tout ce que je fais dans la dernière minute avant qu’une application plante est perdu ? ». La réponse est non, et je reviens à ce que je disais précédemment sur les commandes. Elles aussi sont sérialisées. Un peu comme dans un système de base de données où les commandes sont enregistrées dans le log, pour être rejouées en cas de problème. Les commandes sont sérialisées dès leur création par le moteur Bamboo, et pourront donc être rejouée sur un snapshot remonté en mémoire après un arrêt brusque de l’application. On peut également imaginer un système de type MSMQ pour continuer à stocker les commandes sérialisées pendant que l’application redémarre.

Voici le code source d’une commande :

using System;
using System.Collections.Generic; 

using Bamboo.Prevalence; 

namespace PrevalenceQuery.Commandes
{
    [Serializable]
    public class CreationPersonne : ICommand
    {
        private string _Nom;
        private string _Prenom; 

        public CreationPersonne(string iNom, string iPrenom)
        {
            _Nom = iNom;
            _Prenom = iPrenom;
        } 

        public object Execute(object system)
        {
            (system as Work.Ensemble).ListePersonnes.Add(new Work.Personnes.Personne(_Nom, _Prenom));
            return (system as Work.Ensemble).ListePersonnes[(system as Work.Ensemble).ListePersonnes.Count - 1];
        } 

        public object Execute()
        {
            return Prevalence.Racine.Moteur.ExecuteCommand(this);
        }
    }
}

 

Encore une fois, on voit bien que l’objet est sérialisable. En plus de ça, il hérite de l’interface ICommand définie par le framework de prévalence Bamboo, et qui demande l’implémentation des fonctions d’exécution de la commande. La première fonction prend en paramètre un objet nommé system, et qui n’est autre que l’instance de classe Ensemble, point de fédération des objets métier, qui est gérée par le moteur de prévalence. La classe Racine, quant à elle, expose de manière statique le moteur de prévalence, de façon à pouvoir appeler facilement ExecuteCommand en lui passant l’instance de l’objet ICommand.

Le code de la fonction d’implémentation proprement dite de la commande est assez simple, à savoir dans notre cas exemple de création d’une personne, qu’on ajoute dans la liste statique une instance de Personne construite avec les paramètres qui avaient été envoyés lors de la création de l’instance de commande, puis qu’on renvoie cette instance en sortie de la commande.

Pour déclencher les commandes, rien de plus simple :

private void btnCreerPersonne_Click(object sender, EventArgs e)
{
    new Commandes.CreationPersonne(txtNom.Text, txtPrenom.Text).Execute();
}

 

Voici pour l’introduction à la prévalence et au prototype qui va nous servir de base d’étude. Maintenant, passons à la partie intéressante de l’article, à savoir comment gérer industriellement une application basée sur la prévalence. Est-ce que les avantages compensent suffisamment les inconvénients, que faut-il tester, et quels sont les problèmes auxquels on ne pense pas initialement, mais qui peuvent se révéler dangereux ?

Cycle de vie de l’application

Une application vit et se développe au fur et à mesure de ses versions. Dans notre application de réservation avec une seule date, un client aurait tôt fait de nous demander de pouvoir plutôt utiliser un système avec une date de début et une date de fin plutôt que de rentrer des séries d’enregistrements avec une seule date.

Modifions donc la classe de réservation pour prendre ceci en compte. Comment être le plus compatible avec l’existant ? J’ai déjà des données avec une date de réservation, et je ne souhaite pas avoir à les migrer. La proposition retenue est de rajouter une DateFin qui sera à null pour les instances déjà existantes, et qui sera remplie pour celles nouvellement créées. Dans le cas où on trouve une DateFin à null, on affichera une date de fin égale à celle de début.

private DateTime? _DateFin = null;
public DateTime DateFin
{
     get { return _DateFin == null ? _Date : (DateTime)_DateFin; }
}

 

Du coup, la description change également un peu :

public string Description
{
     get
     {
          string Duree = _Date.ToShortDateString();
          if (_DateFin != null) Duree += "-" + ((DateTime)_DateFin).ToShortDateString();
          return string.Concat(_Personne.NomComplet, " (", Duree, ")");
     }
}

Question bête : est-ce que ça marche avec la prévalence ? Réponse : oui, en tout cas avec Bamboo. La déserialisation ne se soucie pas du fait qu’un nouveau membre ait été rajouté, vu qu’il a une valeur par défaut. On peut donc mettre à jour notre interface graphique avec un deuxième champ, et vérifier la fonctionnalité plus complète :

2

Bref, ça marche dans ce cas de membre rajouté. C’est une bonne nouvelle, car c’est quand même 90% des évolutions du Modèle Conceptuel de Données. Maintenant, il faut tout de même faire quelques petites remarques sur le fonctionnement précis de ces modifications.

Tout d’abord, on n’a pas modifié le nom de Date, qui auparavant correspondait à la date réservée, et désormais à la date de début. Du coup, le code n’est pas parfaitement propre : un développeur qui ne connaît pas l’historique de l’applicatif va se demander pourquoi on a un  DateFin et pas un DateDebut, alors que le premier champ est visiblement utilisé pour le début des durées de réservation.

Si on cherche à renommer le champ, il est évident que la désérialisation ne pourra pas se retrouver dans les données binaires enregistrées. Essayons de modifier le champ et la propriété  pour avoir :

private DateTime _DateDebut;
public DateTime DateDebut
{
     get { return _DateDebut; }
}

 

Lorsqu’on lance l’application, pas de plantage, mais les dates de début sont toutes à la valeur par défaut, et les données ont été perdues :

3

Au passage, attention à bien sauvegarder votre répertoire Persistence lorsque vous faites ce genre de manipulation, parce que même en revenant en arrière sur la modification de code, les données ont été perdues, et pour poursuivre, j’ai été obligé de restaurer les fichiers de persistence générés par le framework Bamboo.

D’ailleurs, par curiosité, voici le contenu du répertoire de persistence :

4

On voit bien les snapshots ainsi que les logs de commandes, gérés dans deux types de fichiers de sérialisation bien différents.

Imaginons maintenant une deuxième modification de l’application, dans laquelle on en viendrait à supprimer un champ, par exemple le prénom dans la classe Personne. Là encore, tout se passe bien. Bon, c’est relativement rare qu’on supprime de la donnée, mais c’est toujours un cas de plus qui se passe bien en prévalence. On obtient juste les résultats tronqués dans l’interface :

5

Encore une fois, attention lorsqu’on revient en arrière dans le code : la prévalence s’est réellement débarrassée de la donnée. Si on remet le code de Personne exactement comme avant et qu’on relance à nouveau l’application sans restaurer le répertoire de persistence, on s’aperçoit que les données ont été effacées :

6

Notez l’espace au début des noms complets : il se trouve là car les prénoms ont été rétablis et il faut les séparer du nom de famille, mais tous les prénoms sont vides. C’est très intéressant, car cela veut dire que la sérialisation a supprimé les données qui n’étaient plus utilisées. Premier enseignement intéressant sur ce moteur… En effet, cela veut dire qu’il y a nettoyage au fur et à mesure et qu’on ne risque pas, après plusieurs années de modification d’une application, de se retrouver avec des sérialisations d’une taille fortement supérieure au contenu réel de l’application. Seul ce qui sert est gardé.

Restaurons donc à nouveau les données pour tenter une dernière chose : si les données sont nettoyées, pourquoi ne pas utiliser cette fonctionnalité pour, en deux étapes, migrer notre Date en DateDebut ? On commence par ajouter DateDebut dans Reservation, et modifier la propriété Description pour profiter de ce qu’elle est appelée pour toutes les instances pour remplir le champ DateDebut :

private DateTime _DateDebut;
public DateTime DateDebut
{
     get { return _DateDebut; }
}

public string Description
{
     get
     {
         // On profite que cet appel soit systématiquement réalisé au lancement de l'application 
         // pour faire migrer la valeur de Date dans DateDebut 
         _DateDebut = _Date;  

         string Duree = _Date.ToShortDateString();
         if (_DateFin != null) Duree += "-" + ((DateTime)_DateFin).ToShortDateString();
         return string.Concat(_Personne.NomComplet, " (", Duree, ")");
     }
}

 

On lance l’application, et normalement, notre DateDebut est désormais bien remplie. Il faut ensuite modifier à nouveau le code pour supprimer le champ _Date et la propriété associée, et remplacer toutes les occurrences par DateDebut. Et ça marche !

7

Bref, nous avons donc fait évoluer le code de notre application et les données à la suite. Il faut bien sûr faire attention à parcourir toutes les instances, mais ce genre de manipulation test ouvre la voie à des migrations plus complexes, qu’on pourrait par exemple mettre en place lors du chargement de l’application en parcourant toutes les données à migrer. Le framework se chargeant de nettoyer la persistence des données qui ne sont plus en correspondance avec des champs existants, cela veut dire qu’on peut se permettre ce genre de migration en deux phases sans risquer de faire gonfler la taille globale de la sérialisation.

Propositions pour une migration complexe de modèle

Le but de ce blog est de se placer systématiquement d’un point de vue industriel. Dans les paragraphes suivants, on a montré qu’il y avait moyen de réaliser un changement simple de modèle, en lançant l’application en phase intermédiaire. Mais comment réaliser des migrations plus complexes, en modifiant également les interactions entre les objets ? Deux approches sont possibles :

  • Mettre en place tous les champs supplémentaires dans les objets, réaliser le code de migration lors du lancement de l’application, puis supprimer tous les champs devenus inutiles. C’est l’extension de ce qui a été montré plus haut, et ceci n’est recommandable que dans les cas où on peut maîtriser assez simplement la migration. Il sera tout de même utile de mettre en place un plan papier avant de coder.
  • Dans les cas les plus complexes, il peut être intéressant de réaliser la migration en un seul coup, en ajoutant une seconde classe de support des racines d’objets métier, voire en dupliquant carrément le moteur de prévalence. Ces deux approches ont l’avantage de séparer radicalement les données avant et après la migration, et ainsi de rendre plus compréhensible la migration des données.

Dans un cas comme dans l’autre, il est bien évidemment fortement recommandé de sauvegarder le répertoire de persistence et de tester la migration, car si des données sont perdues, elles le seront de manière irrémédiable, le moteur de prévalence purgeant les données devenues inutiles, comme on l’a vu précédemment.

Montée mémoire en mode prévalent

Dans l’application prototype qui a été montrée, un problème sur des gros volumes est que, lors du démarrage du moteur de prévalence, toutes les données sont désérialisées en mémoire. Tant qu’on est sur des petits volumes, pas de problème, mais si on passe sur des fortes charges, cela peut prendre du temps. C’est par exemple une bonne raison pour ne pas faire une migration en deux passes, mais plutôt charger un deuxième ensemble de prévalence, puis libérer le premier à la suite du processus de migration.

L’expérience montre qu’à partir du chargement de 100 000 objets métier (de taille raisonnable) sur un ordinateur à peu près récent, une lenteur de démarrage commence à être plus que sensible, presque gênante. La désérialisation étant binaire, le processus reste très rapide, mais il faut en tenir compte.

Une solution dans le cas de données très nombreuses peut consister à scinder les données en domaines, qu’on chargera dans des moteurs de prévalence différents, et ainsi de ne pas avoir une grosse attente lors du chargement. Typiquement, dans une application industrielle sur le même domaine métier que le prototype décrit ici, on pourrait ne charger que les personnes pour un utilisateur se chargeant des relations clients, et charger le domaine de réservation uniquement lors du premier appel d’un utilisateur se chargeant de ces données. Il faut toutefois se rappeler que les applications en prévalence sont censées rester longtemps en mémoire, donc l’effet de cache jouera à plein dans ces cas.

Serveur obligatoire

Lorsqu’on utilise une base de données, l’application est naturellement décorrélée du métier, car elle ne se charge que du stockage des données. Dans le cas de la prévalence, la persistence se fait au niveau des objets métier eux-mêmes. C’est incontestablement l’avantage majeur de cette technologie, mais il s’accompagne d’une obligation supplémentaire, à savoir que le métier doit absolument être séparé de la GUI, sauf bien sûr dans le cas particulier d’une application autonome. Bon, les applications client-serveur ne sont plus légion, mais il faut tout simplement en être conscient si vous voulez utiliser la prévalence : vous serez obligé d’utiliser un serveur applicatif pour le métier, et de grouper vos traitements derrière des services web, du remoting, ou n’importe quelle autre technologie vous permettant de séparer le métier de plusieurs instances clientes.

Arrêt de l’application pour migration

De ce point de vue, pas de réponse extraordinaire de la prévalence. Lorsque vous mettez en place une migration de modèle, vous êtes obligés d’arrêter l’application.

En soi, ce n’est pas très différent de ce qui se passe avec une base de données. Oui, on peut très bien mettre à jour une base de données tout en la gardant en ligne, mais honnêtement, c’est vraiment limité à des besoins très spécifiques où la mise hors ligne pour quelques heures est considérée comme coûtant trop cher par rapport au surplus de risque. Non, je vous parle simplement d’une application de gestion de données que des gens utilisent dans la journée, par exemple. Maintenant, si on repose le problème de la sérialisation, potentiellement, lorsque vous allez redémarrer l’application, vous pouvez attendre quelques heures sur des gros ensembles de données. Il vaut donc mieux bien planifier vos migrations, et comme on le disait auparavant, pourquoi ne pas scinder les données en plusieurs moteurs de prévalence indépendants lorsque cela est possible ?

Et SQL là-dedans ?

Un des reproches fait à la prévalence tient au fait que les bases de données disposent d’une technologie avancée de requêtage des données, et que la prévalence n’a rien à proposer d’équivalent à SQL. Il y a quelques années, ce reproche m’avait laissé perplexe : SQL et les bases de données ont été inventés pour rendre moins dramatique la perte de performance due à la migration des données de la mémoire vive à une mémoire disque beaucoup plus lente. Si on rendait les données à la RAM, quel besoin avait-on de chercher une technologie de requêtage ? Il suffisait de faire ces requêtes en code, ce que n’importe quel programmeur fait mieux qu’une requête SQL.

J’ai compris plus tard lorsque j’ai voulu mettre en place un ETL pour des données XML. Cette technologie, très utilisée dans la société pour laquelle je travaille, consiste à décrire en XML une liste d’actions de transformations qui s’applique sur des données XML accompagnées de leur XMLSchema associé, et enrichi de nos métadonnées. Après des mois et des mois de développement, nous avons quelque chose qui convient parfaitement à nos besoins, et qui est à peu près capable de s’optimiser dynamiquement, en choisissant quelle action doit être menée en base de données, quelle action doit être menée sur le DOM, etc.

Note : Au passage, que pensez-vous être le plus rapide sur 10 000 lignes : une opération Sort XSL avec le parseur .NET, ou bien le tri par index d’une base de données ? Beaucoup de gens pense que la base de données est bien plus optimisée pour ce genre de travail. Et bien ce n’est pas toujours le cas, et je vous encourage à tester.

Pour revenir à nos moutons, cette technologie de requêtage sur des données en mémoire a représenté un gros investissement, ce qui veut dire que quelque chose de simple comme de transformer de la donnée présente en mémoire, sans avoir à se soucier de l’extraction, représente tout de même beaucoup de travail. Mais au fait, lorsque je parle de requêter des données en mémoire, ça ne vous fait pas penser à quelque chose ? Linq, bien sûr !

Linq To Objects à la rescousse

Lorsque Linq est apparu, en tant que créateur de l’ETL en mémoire dont je vous parlais auparavant, j’étais… comment dire… un peu écœuré. J’étais bien content de mon petit framework, et voilà que sortait une technologie, incorporé directement à .NET, et qui allait envoyer directement mon aimable bricolage à la poubelle. J’étais capable d’optimiser les tris, les variables calculées, les restrictions sur des formules, les jointures ? Linq faisait dix fois mieux partout, en générant des requêtes paramétrées, et surtout en étant capable de transformer tout ce qu’on lui demandait en du bon SQL fonctionnel et rapide.

Note : pour garder un peu la tête haute, je précise que mon API supportait Oracle, ce que Linq ne fait pas nativement, bien qu’il existe des librairies pour ça. Je profite d’ailleurs de cet article pour lancer un appel sur ce sujet à des gens ayant testé ce genre d’API : lesquelles avez-vous testées ? La qualité et les performances sont-elles au rendez-vous ?

Bref, il est temps de moderniser mon API et de la baser sur Linq. Et c’est dans la versatilité de Linq qu’apparaît tout l’intérêt de cette technologie pour les situations de prévalence. Autant Linq To SQL est une excellent candidat au remplacement d’un ETL, autant Linq To Objects est une extension naturelle à la prévalence, en permettant à l’utilisateur de requêter ses objets métier avec une performance et une souplesse à mon avis sans pareil. Un exemple tout bête :

var resultat = from Reservation in (system as Work.Ensemble).ListeReservations
               where Reservation.Personne.Nom.StartsWith("G")
               where Reservation.DateDebut.CompareTo(DateTime.Now.AddDays(2)) < 0
               select Reservation.UID;

Ce code n’a bien évidemment pas nécessité une seule modification sur les objets métier utilisés dans le prototype présenté.

Tout n’est pas absolument rose

Arrivés à ce point de mon monologue (si certains ont réussi à tenir), vous devez vous demander ce qui cloche. Ben oui, je vous présente une technologie connue depuis de nombreuses années, et pourtant étonnamment utilisée par aucun éditeur de logiciel, ou en tout cas pas ouvertement, alors qu’elle paraît révolutionnaire… Il y a bien SAP qui parle de mettre la base de données en mémoire, mais après enquête rapide, ça n’a rien à voir avec de la prévalence : on garde la bonne vieille structure tabulaire de base de données, et on la met en RAM au lieu des disques durs. La belle affaire : la moitié des administrateurs réseau ont eu l’idée de monter les fichiers de base de données sur des RAMDISK lorsque la performance était vraiment capitale. Bon, j’exagère, et SAP semble mettre un peu de travail pour améliorer effectivement le fonctionnement d’une base de données utilisant la mémoire, mais rien de similaire à la prévalence.

Alors, pourquoi est-ce que la prévalence n’est pas plus utilisée ? Honnêtement, je suis bien en peine de trouver une réponse valable. Il y a bien sûr avant tout la frilosité de personnes auxquelles vous dites que les données vitales de son entreprise vont être dans une mémoire volatile, alors qu’il ne rêve à rien tant qu’un système de sauvegarde absolument infaillible. Exactement le même genre de recul spontané que l’on observe sur les technologies Cloud : « alors mes données vont être dans les nuages, c’est ça ? ». Ou bien avec les network computers : « et mes données, physiquement, elles sont où ? ». Tout ça met du temps à s’estomper. Ceci n’est d’ailleurs pas une critique : j’ai moi-même de nombreux a priori sur ces technologies.

Il y a aussi, il ne faut pas se voiler la face, le problème du SQL. On a vu que Linq permettait  de remplacer le SQL par quelque chose de plus souple et qui fonctionne en mémoire. Oui, mais lorsque l’application legacy contient 1000 requêtes SQL dont certaines bien complexes ont mis des jours et des jours à être optimisées, il est compréhensible que l’idée de tout réécrire pour des gains de performance doive être soigneusement pesée.

Il est vrai également que, si on ne fait pas attention à la qualité du code de migration, on se retrouve avec des erreurs du genre :

8

Eh oui, j’aurais du faire plus attention dans ce cas à mes structures d’objets, car deux modifications en parallèle m’ont amené à utiliser un nom commun, et le résultat était que le moteur a vu un changement de type sur un même champ. La sérialisation plante alors, et il vaut mieux revenir à des anciennes données par sécurité.

C’est là qu’on se retrouve confronté à un autre problème potentiel : les données sont stockées de manière sérialisée en binaire, et donc absolument illisible si on perd le programme associé. Avec une base de données vieille de dix ans, on peut retrouver, au prix d’efforts, des données intéressantes même si le programme qui allait avec n’est plus trouvable. Oubliez ça avec la prévalence : vos données sont liées intimement à votre programme, et les fichiers que Bamboo créent ne vous seront d’aucune utilité sans l’applicatif correspondant.

Alors, on y va ?

Bon, j’ai essayé d’être le plus honnête possible avec les aspects négatifs de cette technologie, mais comme moi, vous vous demandez peut-être encore pourquoi ce n’est pas plus utilisée… Peut-être tout simplement qu’il s’agit d’une des ces technologies maudites dont l’histoire de l’informatique et de l’électronique est friande ? Rappelez-vous le Betamax, Next, OS/2, et toutes ces autres technologies superbement architecturées et révolutionnaires… Elles n’ont pourtant pas connu la gloire. Je pense que la prévalence est partie sur la même voie : peut-être trop en avance ? Mais peut-être aussi qu’il manque tout simplement la killer application qui va rendre cette technologie incontournable… Donc, à vos claviers !

Posted in .NET, Prevalence | Tagged | Leave a comment