March 2009 - Messages

Schema et accès aux données
25 March 09 04:32 AM | Nico | avec no comments

Cet article a pour objectif de décrire notre approche à propos de l'accès aux données.

En préambule, un petit aparté sur Entity framework, qui s'inscrit dans la continuité de l'histoire douloureuse des ORM de Microsoft.

Microsoft a sorti en 2008 la V1 d'Entity Framework. Elle a suscité de nombreuses réactions, plutôt négative de la communauté. Trop intrusif, trop de code à écrire, trop compliqué à utiliser, trop d'inconvénients pour peu de bénéfices. Microsoft a écouté, et a décidé de construire la V2, en transparence, et en communiquant au fur et à mesure de son évolution. Ca se passe ici, sur le blog de l'équipe d'EF, et la Wish list (77 pages de Forums !) s'est arrêtée au 15 janvier.

Malgré cela, l'approbation de la communauté n'est pas encore gagné.

Un article évoque la gestion du ChangeTracking, fonctionnalité inhérente à tout ORM qui se respecte. Ils ont plus ou moins décidé qu'ils allaient fournir des API pour que les développeurs implémentent eux-mêmes leur gestion du ChangeTracking: que de choses à connaître, et que de code à écrire.

Autre exemple, la gestion des Foreign Keys. Apparemment, certains voudraient voir les FK dans leurs entités, et d'autres non, car ils considèrent que l'entité est polluée par une FK. D'où une gestion de FK hybride, les Independant Associations apportées par .Net 4.0; encore quelque chose de compliqué et d'artificiel qui nécessitera apprentissage.

Alors que l'usage du DataSet simplifierait les problèmes, ils évitent soigneusement de l'utiliser, avec des arguments discutables.

Sami Jabber a parfaitement raison quand il écrit à propos d'Entity FrameWork: "Un modèle conceptuel de données qui permet d'abstraire du XML, du relationnel, de l'objet et des DataServices (pour Astoria) a un nom, cela s'appelle de la magie noire".

Notre vision est nettement plus simple, pragmatique, compréhensible tant pour sa lecture, que pour son utilisation, souple, dynamique, peu intrusif, et requiert l'écriture d'un minimum de code.

Des données relationnelles sont persistées quelque part (un fichier, une base de données ? peu importe !), et il serait pratique d'y avoir accès "facilement". Inversement, des données relationnelles sont en mémoire, et on souhaite les persister quelque part (un fichier, une base de données ? peu importe !), sans trop d'efforts. J'insiste sur le caractère relationnel des données, car c'est le coeur du problème, ou plutot le coeur de la solution.

Plus précisément, on souhaite écrire le moins de code possible (évitons le SQL) et ne pas être intrusif vis-à-vis des Entités que l'on manipule.

Et s'il n'y a pas de solution magique, je maintiens qu'il y a plus à gagner à utiliser le DataSet qu'à ne pas le faire. Et pas seulement pour le ChangeTracking, mais pour beaucoup d'autres; serialisation, dynamisme, filtre, tri, Merge, gestion événementielle.

Et s'il y a quelques défauts, essayons de les gommer, plutôt que de jeter le bébé avec l'eau du bain.

  • Intellisense: toujours difficile à concilier avec le dynamisme, nous apportons une solution. C'est du typage fort pour le code, côté serveur, et du typage dynamique pour le transport et le Binding, le mariage idéal des 2 mondes.
  • Validation: les ExtendedProperties permettent de stocker des attributs complémentaires et manquants. Des validateurs standards (Min, Max, Regex), mais aussi Custom avec des Commandes de Validation. 
  • Navigation: quelques fonctions génériques de navigation, en fonction du typage fort, mais aussi du typage souple, permettent de naviguer de façon extrêmement conviviale et évite la manipulation verbeuse du DataSet qui fait souvent peur au développeur.

Aspectize Entity Designer est l'Outil de Design du modèle conceptuel des données, c'est à dire les Entités et les Relations. De ForeignKey, il n'est point question; nous insistons sur ce point, la Relation existe à part entière, c'est fondamental pour l'approche. Sa cardinalité est définie, mais elle n'a pas d'implication sur le modèle. Cela permet de garantir l'indépendance des Entités. Peu importe si ma Category est liée à un Product ou à autre Chose, cela n'influe pas la nature de mon Entité Category.

Nous pouvons ajouter de nombreux attributs :
- MustPersist: permet de dire si certaines Entités ou Champs ne sont pas sauvegardés. Pratique, quand certaines entités ne servent qu'au calcul ou a l'IHM. A noter que les Relations peuvent également être non persistence.
- Validators: permet de définir un Validateur, écrit en .Net, qui sera toujours appelé lors d'un CommandBinding configuré sur cette commande.
- Triggers: permettent de définir des commandes qui seront automatiquement appelées lorsque l'on impactera une Entité, en fonction du State (Add, Delete, Update).

Un des attributs les plus intéressant est sans doute le Temporel, qui permet de définir automatiquement la dépendance au temps d'une donnée: dans mon exemple, le prix du produit est historisé, et est donc stocké dans une autre table. L'intérêt de l'attribut Temporel est que cela est complètement transparent d'un point de vue logique: j'ajoute même un autre champ qui me permet d'avoir le prix courant, sans être obligé d'interroger systématiquement l'historique des prix.

EntityManager est notre composant d'accès aux données. Il permet, via une API extrêmement simple de faire tout ce qu'on veut pour récupérer des données et les sauvegarder.

IDataManager dm = EntityManager.FromDataBaseService("MyDataService");

Pour récupérer une Category à partir de son Id: 

Category category = dm.GetEntity<Category>(id);

ou tous les Product associés à une Category, selon la relation CategoryProduct (on pourrait imaginer avoir plusieurs relations entre les mêmes entités):

dm.GetAssociated<Product, CategoryProduct>(id);

Accessoirement, c'est la même API pour naviguer dans les données en mémoire, à partir du DataSet (ce qui évite la manipulation verbeuse):

List<Product> products = category.GetAssociatedInstances<CategoryProduct>();

Et pour la sauvegarde, c'est encore plus simple. La seule méthode Save, sauvegarde toutes les lignes du DataSet en fonction des changements:

dm.Save();

L'énorme avantage est de tirer parti à la fois du typage fort proposé par Entity Designer, et du typage dynamique géré par le DataSet.

Ainsi, le chargement d'une Category peut s'écrire également:

dm.LoadData("ADWData.Category[Id = id]");

ou avec les Product associés:

dm.LoadData("ADWData.Category[Id = id].CategoryProduct.Product");

mais aussi ajouter un nouveau Product à une Category:

uiService.AddRow("ADWData.Category.CategoryProduct.Product");

Il est du coup, extrêmement facile d'écrire des services d'accès aux données; nous verrons plus tard que nous n'avons même pas à écrire ces services d'accès aux données, car ils peuvent se déduire du DataBinding. L'Application charge toute seule les données qu'elle affiche, en fonction des DataBinding configurés sur les contrôles.

Difficile de faire plus simple; aucune intrusivité, tous les scénarios d'accès aux données Lazy ou non sont couverts, de façon extrêmement limpide et le DataBinding se charge du reste !

Nouvelle vidéo
04 March 09 03:25 PM | admin | avec no comments

A l'occasion des Techdays, Microsoft et Brainsonic m'ont proposé de réaliser une présentation en images. Après la vidéo de démonstration l'année dernière, je me suis plutôt concentré sur l'approche, avec quelques slides de support pour illustrer le propos.

 


 

Approche
01 March 09 07:22 AM | Nico | avec no comments

Suite à notre soirée Alt.Net, qui a suscité quelques échos dans la blogosphère (ici, ici et ), et qui était un exercice très intéressant pour nous, voici quelques explications écrites qui complètent nos slides, et résument notre approche.

 

Il est difficile de prévoir. Cela peut être plus facile quand on construit un pont ou une route, car ce sont des éléments mécaniques qui sont régis par des contraintes qui sont connues d'avance. Mais cela est beaucoup plus difficile quand on construit un Système d'Informations, parce qu'il répond à un besoin issu de l'imagination des utilisateurs, qui ne cesse d'évoluer avec le temps et en fonction des usages de l'existant.

L'agilité permet de réduire la portée des prévisions, qui sont d'autant plus floues qu'elles sont lointaines. Avec une meilleure tolérance aux changements, il est plus facile de travailler en cycles courts afin d'augmenter la qualité de l'application, qui est, par définition, proportionnel au recoupement entre ce qui est attendu et ce qui est réalisé. On rentre alors dans un cercle vertueux, qui consiste à limiter les évolutions en cycles extrêmement courts, ce qui va augmenter les chances de coïncider avec les besoins attendus, et éviter les dérives.

 

 

Pour cela, on a besoin de faire une séparation beaucoup plus nette entre le technique et le métier. Aujourd'***, il y a trop de complexité dans le SI, il y a trop de code, parce que cette séparation est mal faite. Beaucoup trop de code technique est ré-écrit dans chaque projet, alors qu'une séparation plus nette permettrait de n'écrire certaines briques techniques qu'une seule fois. C'est le rôle de l'Architecture de proposer ces briques techniques réutilisables. Et réutilisable, de préférence sans écrire de code. En tant qu'adepte de l'approche DRY, isoler ce qui peut être défini tôt (le technique) de ce qui doit être défini tard (le métier) est un exercice primordial.

En partant du besoin métier couvert par le SI, et en essayant de rester le plus générique possible, essayons d'isoler quelques invariants techniques du SI, sans savoir si on va traiter de voiture, de contrat d'assurance ou des ventes de livres. C'est un peu comme si vous faisiez l'inventaire de votre cuisine, sans vous poser la question de savoir si vous allez faire le mois prochain de la soupe au pistou, des poissons en papillote ou une mousse au chocolat; vous avez besoin dans tous les cas d'ustensiles réutilisables, comme un four, des plats, des casseroles, et un bon presse-ail. Et vous n'allez pas les fabriquer à chaque fois que vous faites une nouvelle recette !

Pour revenir à nos octets, les besoins métiers peuvent se résumer simplement à:
  • Des Données Relationnelles
  • Des IHM, pour afficher et modifier par les input clavier et souris (et rien d'autre !) 
  • Des Traitements, essentiellement manipulation de ces données: calculs et validations. Et ils sont rares (en proportion du code écrit actuellement). On peut également ajouter les processus, qui sont des traitements, avec des états et des transitions.
Les Données Relationnelles correspondent à un schéma de pensée assez simple: des "choses" sont en relation avec d'autres "choses". Ces choses ne sont pas connues d'avance, elles sont spécifiques au métier, mais on sait qu'elles sont nommées et qu'elles ont des propriétés nommées et typées. Il y a également des méta-données techniques, mais au contraire des méta-données métiers (qui touchent à la nature des "choses"), les méta-données techniques sont connues d'avance: tout ce qui peut être technique peut être orthogonal et écrit une bonne fois pour toute (persistance, conversion, cardinalité, temporalité, multiplicité, validation), et complètement indépendamment de la nature de la donnée. On sait juste que l'on aura à faire avec des chaines de caractères, des dates, des identifiants (Guid), des boolean et des numériques.
 
Les IHM sont des contrôles configurés pour afficher les données nommées, et lancer des commandes en réponse à des événements des contrôles. Le contrôle, en tant que "contrôle", n'a pas à connaître le service qu'il appelle, c'est indépendant de sa nature; il pourra être WinForm, Html, WPF ou Silverlight (du moins en restant dans le monde Microsoft, qui offre déjà une palette assez large de possibilités) la définition de la commande associée - elle aussi justement nommée - ne relève pas de son design. De même, la donnée qu'il représente lui est inconnue, il ne connaît pas son format d'affichage ni sa validation.

Les Traitements sont des Services Stateless qui manipulent les données relationnelles. Le StateLess est un pré-requis indispensable à la montée en charge, et permet d'éviter beaucoup de problèmes de concurrence et de synchronisation. Si un Service a besoin de gérer un état, il le sauve dans une base de données (qui elle, sait très bien gérer la concurrence) et si une commande a besoin d'une donnée, elle la demande. A partir du moment où la donnée est nommée, il est facile de l'obtenir; ne pas se soucier de là où elle se trouve, ce n'est pas le rôle du service. Celui-ci sera configuré plus tard, pour s'exécuter au plus près des données dont il a besoin, pour assurer la performance attendue. C'est là tout le rôle et la valeur ajoutée de l'architecte. Définir les services et leur configuration en fonction du métier, est de loin la chose la plus difficile qui existe dans ce métier, ne la compliquons pas avec des considérations techniques.

Pour mettre en oeuvre tout cela, et faire cohabiter ce joli monde, on a besoin d'un certain nombre de ruses et astuces:
  • Proxy Dynamic pour pouvoir faire un appel dynamique de n'importe quelle commande, parce qu'elles ne sont pas connues d'avance. C'est donner de l'indirection, qui permet une séparation complète avec le physique. On ne sait pas où elle est la commande, peu importe. Une fonction ExecuteCommand pourra appeler n'importe quelle commande en fonction de son nom.
  • DataBinding pour permettre le transfert bi-directionnel des données entre la mémoire et l'IHM. Ce databinding est dynamique, car, là encore, les données ne sont pas connues toujours d'avance (elles le sont au moment où le développeur drag'n'drop sont TextBox dans son contrôle, mais cette situation peut arriver à tout moment, et bien au delà la mise en marche de l'application). Il doit également tenir compte du caractère relationnel des données, la navigation Parent-Child dans les données, est une fonctionnalité de base, mais il y en a bien d'autres comme le format ou la validation.
  • AOP: la technique des attributs est utilisée pour permettre une orthogonalité dans la nature des choses, ce qui permet de gérer des méta-données techniques. A la différence de l'AOP (les puristes vous diront qu'on ne fait pas de l'AOP, et ils auront raison tant qu'ils ne changeront pas d'avis :-), celui que nous utilisons est dynamique. 
  • Chargement dynamique, pour permettre l'ajout ou la modification de n'importe quel élément (contrôles, données ou traitements) à tout moment de la chaîne de développement. Ne pas confier la gestion du chargement à .Net, qui imposera des liens impératifs, ce que nous voulons justement éviter.
  • DataSet: la gestion des données relationnelles en mémoire, dynamique (on ne les connait pas d'avance), mais néanmoins typées, sérialisable, avec des fonctionnalités de Change Tracking, Filtre, tri, rien que ça. A l'aide d'une fonction GetData qui saura récupérer des données par leur nom relationnel, nous sommes capable d'adresser tout ce que l'on veut, sans les connaître d'avance.

Évidemment, la plate-forme .Net offre tout un tas de choses que nous avons considérablement enrichi et simplifié, pour qu'elles soient facilement utilisables, et nous ne pouvons pas résumer ici tous nos secrets de fabrication (le Hollywood Principle en fait partie). Et nous avons packagé toutes ces techniques pour qu'elles soient disponible pour ceux qui vont faire le reste.

Et le reste, c'est du métier. Et c'est là où le développeur commence son travail. A lui de définir l'organisation des données, les entités et leurs propriétés, les relations et les écrans. Il ne fait pas cela dans un but physique, mais purement logique. Les attributs techniques viendront plus tard. 

Il ne va pas commencer par écrire une couche d'accès aux données, ni faire un modèle objet, et encore moins s'occuper de la sécurité; il va s'intéresser au fonctionnel, c'est à dire à la nature des choses et leurs relations, au design des écrans et aux règles métiers, qui sont d'autant plus facile à écrire qu'elles sont peu nombreuses, répétons le. Et dès le premier jour, il aura un résultat visible et qui fonctionne.
 
Avec l'intelligence et la connaissance des outils, Visual Studio & Binding Studio (qui devraient n'être qu'un, mais Binding Studio n'aurait jamais vu le jour s'ils l'avaient été - wait and see ? - car Binding Studio a la particularité d'être fait avec Binding Studio, mais je m'égarre, ceci est une autre histoire):
  • Microsoft Visual Studio pour:
    • faire le design des entités et des relations, à l'aide de notre DSL
    • faire le design des écrans en WinForms ou en HTML (pour l'instant, WPF et Silverlight viendront plus tard).
    • écrire le code métier que sont les calculs et les validations
  • Aspectize Binding Studio pour:
    • configurer le DataBinding et le CommandBinding entre les IHM, les Données et les Traitements
    • configurer les services et tout ce qui a attrait au technique (log, trace, bouchons, ConnectionString, FileName, Error tracking, ...); à noter que cette partie est complètement dynamique, au sens où elle peut être modifiée à chaud par un administrateur.

Voilà, en quelques lignes, l'esprit de notre démarche.

Nous allons essayer d'être démonstratif dans les prochaines semaines, en illustrant comment on utilise concrètement nos outils pour la mise en oeuvre.
Classé sous : ,