Un brillant essai de Laurent Liger et Olivier Coanet.
Où il sera question de toute l'actualité .NET, de Domain Driven Design, d'Entity Framework, de Linq, de lambda expressions, de F#...
Microsoft TechDays - Day 1
Session Plénière
/Olivier/ La session plénière c'est le grand show d'ouverture, avec les caméras de partout, les effets de scène et les personnalités. C'est aussi le moment pour faire le point sur les nouveaux outils Microsoft et les sessions de la journée. Le truc à ne pas rater et en même temps totalement inutile.
On y apprend en gros que :
- les technos mise en avant cette année sont Visual Studio 2008, SQL Server 2008 et Windows Server 2008,
- les nouveaux projets des entreprises sont majoritairement réaliser en .NET : 39% contre 35% pour le Java (je ne cautionne absolument pas ces chiffres ^^),
- les besoins en compétence .NET sont toujours important, d'ailleurs il y avait 600 offres d'emplois sur le salon.
/Laurent/ Mine de rien, on était plus de 5400 personnes le premier jour dans la salle, on a eu droit à une petite vidéo enregistrée la semaine précédente par le futur retraité le plus riche du monde, monsieur Gates, qui regrettait de ne pas pouvoir être présent pour cet évènement qui est le plus grand d'Europe de ce genre et vient juste derrière la PDC (Professional Developers Conference) des États Unis.
Domain Driven Design
/Laurent/ Le concept du DDD (à ne pas confondre avec l'outil linux) date du début des années 2000, au moment ou les modèles d'architecture en couche d'applications n'était pas encore répendus, Eric Evans se posait déjà la question de savoir si cela n'allait pas poser de nouveaux problèmes et si le découpage proposé était vraiment la meilleure solution. Cette conférence nous a donc présenté le DDD qui est un découpage différent de celui adopté majoritairement maintenant.
Actuellement la couche domaine a en effet tendance à être dépouillée au maximum pour ne plus être qu'une structure de donnée, épurée (trop) de comportement métier, ce dernier étant uniquement relégué au niveau de la couche services. La proposition du DDD est de recentrer le développement sur la couche domaine, de lui rendre l'intelligence qu'elle a perdue : c'est elle qui doit faire appel aux services et non l'inverse.
Dans le paradigme DDD, les clients ne manipulent donc presque que des objets de la couche métier, alors que dans la quasi-totalité des développements effectués à l'heure actuelle, les clients utilisent des services pour agir sur les données.
Mais... c'est un tableau qui me fait peur : si la couche métier est au centre, il faut la modifier constamment pour l'adapter aux nouveau comportements que l'on souhaite ajouter. Dans le cas d'un développement .Net, il faudrait redéployer l'assembly associée à cette couche partout pour assurer la compatibilité des anciennes applications.
Il s'agit d'ailleurs d'un faux problème à la base : il suffit de s'assurer dans un découpage traditionnel que les objets métiers conservent les règles métier qui sont les leurs. Si un objet à besoin de manipuler des données, rien n'empêche d'identifier les différentes opérations à effectuer, de créer une interface offrant ces opérations, et enfin d'injecter un service plus tard implémentant cette interface. Bref tout est déjà possible avec un découpage normal d'une application. Il faut juste faire attention à ne pas tomber dans un modèle "anémique".
Vous l'aurez compris, je n'adhère pas avec la vision pessimiste qu'avait l'auteur du DDD.
Pour les curieux, un lien vers le livre fondateur de ce concept : http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1202846134&sr=8-1
/Olivier/ La session était intéressante, ce qui n'est pas étonnant étant donné que l'auteur n'est autre que Sami Jaber, un des fondateurs de DotNetGuru (http://www.dotnetguru.org/). J'ajouterai aux remarques de Laurent que Sami a soulevé des problématiques intéressantes auxquelles je vous invite à réflechir : "faut-il mettre en places des architectures dans lequelles les développeurs n'ont pas de question à se poser et où chaque problématique a déjà été pensée ?" (avec les questions sous-jacentes : "est-ce souhaitable du point de vue du développeur ?" et "est-ce vraiment réalisable ?"). Sami a également mis en avant que les nouveaux outils liés à la couche de présentation (qu'ils soient côté Web avec Silverlight et les composants AJAX ou côté client lourd avec WPF) n'améliorent pas la productivité des développements et qu'il y a encore des progrès à faire dans ce domaine.
Entity Framework et Linq
/Olivier/ Voilà une session qui ne m'a pas du tout convaincu sur le mappeur objet-relationnel à la sauce Microsoft, j'ai nommé Entity Framework ou EF. Dans sa version actuelle il est limité, intrusif et ne fonctionne vraiment que dans le mode où les objets métiers sont générés à partir de la base. Et attention, quand je dis générés c'est avec plein des classes de bases et d'attributes bizarres. Enfin je ne m'inquiète pas pour son succès, nous sommes dans l'univers Microsoft : tout le monde va utiliser le mappeur de Microsoft. Le reste n'existe même pas :)
/Laurent/ Oui c'est ça le pire quand on fait du .Net, on a l'impression que les gens de Microsoft ne tirent pas les leçons de l'expérience du monde Java (pour les frameworks qui gravitent au tour en tout cas, le langage c'est autre chose ;)) : c'est un peu comme repasser d'EJB 3.0 à EJB 1.0, on se demande quand même s'ils sont au courant de projets comme NHibernate pour ne citer que lui.
Heureusement, les intervenants de la session eux on fait un tout d'horizon des frameworks de persistance existants sur le marché pour nous présenter un petit benchmark (trop petit, dommage). On constate donc que la majorité des outils d'ORM sur le marché ont une vitesse équivalente, même si ce n'est pas le cas de leurs fonctionnalités. Malheureusement les intervenants n'ont pas comparé les fonctionnalités et ni les limitations de ces frameworks de persistance. Car ce qui est généralement le critère le plus important lors de l'adoption d'un mappeur O/R c'est sa capacité à supporter l'applicatif existant (code et base de données) et rarement ses performances.
Linq anvancé
/Olivier/ La killer feature du Framework .NET est vraiment Linq. Le problème c'est que dans la pratique (et en tout cas dans un futur proche) la seule version qui me semble utilisable est Linq to Objects. Pour ceux qui ne connaissent pas Linq, c'est un ensemble de méthodes similaires à du SQL accessibles directement à l'intérieur du langage. Le tout est de disposer d'une interface IQueryable<T> et ensuite vous pouvez effectuer des requêtes dessus du style :
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var numberGroups =
from n in numbers
group n by n % 5 into g
select new { Remainder = g.Key, Numbers = g };
foreach (var g in numberGroups) {
Console.WriteLine("Numbers with a remainder of {0} when divided by 5:", g.Remainder);
foreach (var n in g.Numbers) {
Console.WriteLine(n);
}
}
Evidement il existe de nombreuses implémentations de IQueryable<T> qui fonctionnent différement : Linq to Objects qui effectue les requêtes sur des collections en mémoires, Linq to SQL qui génère des requêtes SQL pour récupérer une représentation objet des tables, Linq to Entity qui permet de requêter les objets dans Entity Framework et bien d'autres encore... Dans la pratique, Linq to SQL n'est intéressant que pour des petits projets utilisant une base MS SQL Server, car le provider Linq to Oracle n'existe pas, Linq to Entity n'est utile que pour les utilisateurs de EF. C'est dommage car certaines fonctionnalité de Linq sont très intéressantes : par exemple si une méthode de service retourne un IQueryable<T> on peut très facilement ajouter en AOP des conditions dans la requête (pour filtrer en fonction du périmètre utilisateur par exemple). Vivement Linq to NHibernate pour que je puisse mettre tout ça en pratique !
/Laurent/ En parlant de filtre, Olivier me fait penser à une autre partie intéressante de la présentation : Les lambda-expressions. Derrière ce nom barbare se cache un concept venu des langages fonctionnels : on peut enfin utiliser les fonctions comme des objets de premier ordre du langage C#.
Un lambda expression représente une fonction, mais elle peut être :
- soit compilée en IL (c'est une sorte de delegate anonyme encore plus facile à écrire que ce que permettait C# 2.0)
- soit sous la forme d'un arbre d'expressions
Mais voyons plutôt ça avec un exemple ayant un rapport avec Linq to SQL (ça tombe bien ;) ) :
-façon delegate :
IEnumerable result = personnes.Where(
delegate(Personne p)
{
return p.Age == 30 && p.Prenom == "foo"
}
);
- façon Lambda :
IEnumerable result = personnes.Where(p => p.Age == 30 && p.Prenom == "foo");
Ces deux appels produisent le même résultat : ils renvoient toutes les personnes dont l'âge est égal à 30 ans et le prénom est "foo", mais il y a une grosse différence dans la façon dont le résultat va être récupéré : dans le cas du delegate, le code est compilé, c'est une fonction comme une autre qu'on a passé à la clause Where et la seule façon d'appliquer le filtre est de récupérer toutes les personnes que contient la base, et d'appliquer le test à toutes les instances ainsi récupérées en mémoire. C'est évidemment très couteux et c'est une très mauvaise solution.
Au contraire, dans l'autre cas, la clause where va traiter la lambda sous la forme d'un arbre d'expressions qui va être traité et analysé par le moteur Linq to SQL et transformé en un vrai filtre SQL, dans ce cas le filtre s'effectue au niveau de la base et le résultat est récupéré en un temps minimal !
Le choix de récupérer une lambda dans une forme plutôt qu'une autre se fait suivant la variable qui va la contenir :
Func<Personne, bool> lambdaFun = p => p.Age == 30 && p.Prenom == "foo";
Ce bout de code va permettre de récupérer la lambda compilée, elle sera l'équivalent d'un delegate.
Par contre si l'on écrit :
Expression<Func<Personne, bool>> lambdaTree = p => p.Age == 30 && p.Prenom == "foo";
Le compilateur va la récupérer sous sa forme d'arbre d'expression qui pourra être analysé, retravaillé plus tard, etc.. C'est ce que fait la clause Where de Linq to SQL ci-dessus : elle prend en argument la lambda comme un arbre qu'elle pourra analyser.
L'exemple exposé par Mitsu Furuta, responsable de cette session, était le suivant : essayer d'accéder à une propriété d'un objet dont on ne connaît pas le nom à la compilation. Le premier cas qui vient à l'esprit est d'utiliser la réflexion pour y arriver :
- on récupère le type de l'objet, puis les information de la propriété à laquelle on veut accéder, et enfin on utilise ces informations pour récupérer la valeur de la propriété sur l'instance de l'objet, soit :
public object GetValue(Personne object, string propertyName)
{
return typeof(Personne).GetProperty(propertyName).GetValue(object, null);
}
Le problème de cette méthode, c'est que la réflexion, est très coûteuse, et si on est dans une boucle, ça devient problématique.
La solution proposée grâce aux lambda est la suivante : on construit un arbre d'expression lambda qui utilise cette méthode, et on va ensuite parcourir cet arbre pour remplacer la réflexion par un appel de méthode standard. Il ne reste plus ensuite qu'à compiler l'arbre d'expressions lambda pour obtenir une fonction aussi rapide qu'un appel direct à la propriété.
J'ai trouvé cet exemple moins bon que s'il avait parlé de celui de Linq to SQL que j'ai présenté plus haut (eh oui, il n'était pas dans la présentation) car il est discutable : il y a d'autres façon moins "tordues" d'arriver à des performances semblables, et ce sans avoir eu à attendre le C# 3.0 ou 3.5. Il suffit de penser "Génération de code à la volée" pour trouver. Je vous laisse regarder cet article http://jaychapman.blogspot.com/2007/11/nhibernate-access-performance.html sur lequel je suis tombé il n'y a pas longtemps et qui résumera mieux que moi tout ça.
F# et la parallélisation
/Laurent/ Comme l'indique le sujet, cette session n'était pas une introduction au F#, mais traitait spécifiquement des problématiques de programmation parallèle et des améliorations apportés au langage F# pour faciliter la programmation asynchrone et concurrente.
En gros, sans notion préalable de la syntaxe et des concepts particuliers aux langages fonctionnels de la famille ML (http://fr.wikipedia.org/wiki/ML_(langage)), c'était la sieste assurée ;)
Le constat des concepteurs du F# : programmer à l'aide de threads est fastidieux, trop difficile (en tout cas plus que ça ne devrait l'être) et pas assez souple. Pourtant, il faut bien faire des programmes multi-threads si l'on veut pouvoir faire des appels asynchrones, ou concurrents. La solution habituelle est d'appeler une fonction qui s'exécutera sur une autre thread en lui passant en argument des fonctions de rappel (callbacks) pour indiquer la suite de l'exécution en cas de succès, ou en cas d'échec de l'opération que l'on veut exécuter.
C'est lourd ! On casse le rythme habituel de programmation en devant prendre en compte tous les problèmes pouvant se passer pendant l'exécution sur un thread séparé à la main : en effet, si une erreur se produit, il devient difficile de la faire remonter jusqu'à l'appelant.
Pour palier à ces inconvénients, le F# propose des constructions syntaxiques simples :
- pour effectuer un appel asynchrone bloquant : il suffit d'utiliser un opérateur d'affectation spécial, et le membre droit de l'affectation s'effectuera de façon asynchrone, et le résultat sera récupéré à la fin de l'exécution. Tout est transparent pour le programmeur : si une exception est levée au cours de l'appel, elle est remontée automatiquement dans le thread principal ! Un bonheur :)
- pour lancer des opérations en parallèle : on encadre les blocs de code par la construction "async { }", et on appelle ///
Async.Run(Async.Parallelfoo, bar))
/// l'exécution de "foo" et "bar" se fera en parallèle et un join() s'effectue à la fin des threads pour continuer sur le fil d'exécution du thread appelant.
Ouf !
Là j'ai déjà du perdre tout le monde ^^. Pour une explication avec du vrai code (à la fois plus clair et plus compliqué), vous pouvez regarder le blog de Don Syme, un des intervenants de cette séance : http://blogs.msdn.com/dsyme/archive/2007/10/11/introducing-f-asynchronous-workflows.aspx