Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide
Introduction

Pour ceux qui ne connaissent pas encore, Razor est un nouveau moteur de vues pour ASP.NET MVC 3. Il a été initialement fourni avec WebMatrix et il permet de remplacer le fameuse syntaxe <%%> avec un modèle de codage plus concis et clair grâce à l’utilisation du caractère @. De plus il fourni quelques fonctionnalités intéressantes pour les Master Page et en même temps vous n’allez pas perdre toutes les fonctionnalités auxquelles vous-vous êtes habitués, comme par exemple les helpers html. Le but est de rendre le designs des vues plus facile.

Plus compacte et plus facile à utiliser

Le but de Razor est de minimiser le nombre de caractères requis à l’écriture d’un fichier de vues et d’introduire une nouvelle manière de coder qui est fluide et rapide. Au contraire de ce qu’on connait déjà, le développeur n’a pas besoin de s’interrompre de coder pour dénoter explicitement les blocs serveur dans le HTML. Le parseur de Razor est assez intelligent afin d’inférer ceci à partir du code.

Razor n’est pas un nouveau langage.

Création d’un simple projet ASP.NET MVC 3

Si vous avez installé la version de ASP.NET MVC 3 RTM vous devez retrouver l’écran suivant après avoir cliqué dans le menu “Fichier/Nouveau projet…”:

image

Cliquez sur “OK”. Dans la fenêtre qui s’ouvre, le moteur de vues Razor est présélectionné par défaut :

image

Cliquez sur “OK” pour valider. Visual Studio génère la solution comme avant. La seule différence est que maintenant nous utilisons le moteur des vues Razor. La structure du projet au niveau du système de fichier est la même qu’avant comme vous pouvez le constater sur l’image ci-dessous:

image

Quelques points à noter :

  • L’extension des vues Razor est .cshtml pour les vues sous C# et .vbhtml pour les vues utilisant VB.NET.
  • Les fichiers qui ne peuvent pas être affichés directement par la requête (les master pages, les vues partiels, etc.) sont précédés par un caractère “_” au début de leur noms. Donc si vous essayez d’afficher _Layout.cshtml qui est une Master Page vous obtiendrez une erreur serveur.
Une vue d’exemple

Voici un exemple de vue Account/LogOn qui est créée avec le Template par défaut de Web application ASP.NET MVC 3. La première image est le code pour le moteur de vues ASPX. La seconde pour Razor.

imageimage

Bien que cela ne soit pas flagrant au premier coup d’œil, la syntaxe de Razor est beaucoup plus épurée. Imaginez les écrans un peu plus compliqués. Je vous assure que Razor facilite bien l’écriture et la lisibilité des vues.

La règle est très simple. Tout ce qui est précédé par le signe “@” est un code côté serveur. Si c’est une structure de langage un peu plus complexe, cela doit être entouré des caractères “{“ et “}”. Dans le post suivant, je constituerai un référentiel de base de la syntaxe de Razor, sans rentrer dans le détails.

Dois-je utiliser Razor ?

Le bénéfice qu’apporte l’utilisation de Razor n’a pas tellement d’effet sur des vues simples. Pour les vues complexes, vous allez remarquer qu’elles sont beaucoup plus lisibles qu’avant. A vous de voir Clignement d'œil

 

// Thomas

Comme certains d’entre vous ont certainement remarqué, aujourd’hui, il y a eu la sortie de la version finale de ASP.NET MVC 3. Pour le télécharger, vous avez 2 possibilités (la troisième est le code source) :

Il y a eu beaucoup d’améliorations et d’ajout de nouvelles fonctionnalité dont les principales sont les suivantes :

1. Nouveau moteur de vues “Razor”.

Le but principale est de rendre les vues plus claires.

Les ressources intéressantes à lire :

2. Le support pour les moteurs de vues multiples au sein du même projet.

Dorénavant vous pouvez utiliser plusieurs moteurs de vue au sein du même projet. Web Form View Engine (ASPX), Razor, Spark, NHaml, NDjango.

3. Les filtres d’action globaux (Global Action Filters)

Permettent de définir pré et post actions a exécuter qui s’appliquent à toutes les actions. Il suffit d’ajouter vos filtres dans la collection GlobalFilters.

4. Améliorations liés au JavaScript

MVC 3 permet une meilleure intégration avec JavaScript et prend avantage de fonctionnalités de HTML5.

Les helpers de Validation Ajax dans MVC 3 utilisent une approche “Unobtrusive JavaScript”. Cela évite d’injecter inline le JavaScript ce qui permet une séparation de responsabilités plus claire.

La validation client side est définie par défaut. L’appel à Html.EnableClientValidation n’est plus nécessaire comme dans les version précédentes de ASP.NET MVC.

5. Output caching

ASP.NET MVC 3 permet de mettre en cache les pages partielles ce qui permet de mettre en cache les fragment d’une réponse contrairement au mode MVC 2 ou le cache se passait au niveau de l’URL entière ou de l’action.

6. Modifications lié à l’injection de dépendance.

Le support d’injection de dépendance qui vous permet d’enregistrer votre Framework d’injection de dépendance pour qu’il soit directement utilisable par les Controllers, Views, Action Filters, Value Providers, Model Metadata Providers, Model Binders. Plus besoin d’implémenter votre propre ControllerFactory.

7. Amélioration du support de validation.

RemoteValidation – si vous décorez votre modèle avec cet attribut RemoteAttribute cela permet de le valider en effectuant un appel au serveur à partir du client (avec le plugin de validation JQuery).

IValidatableObject interface permet d’effectuer une validation au niveau du modèle.

IClientValidatable permet à MVC 3 de découvrir au run-time si le validator a un support pour la validation du côté du client.

L’attribut de validation [Compare] qui permet de comparer et valider 2 propriétés.

8. Divers améliorations (liste non-exhaustive)

ViewBag remplace ViewData ce qui permet le support dynamique pour le bind tardive des datas des Controllers aux Views.

Attribut AllowHtml qui permet une validation plus précise de la requête lors du bind de données aux modèles.

Le contrôleur Sessionless qui permet de contrôler si le SessionState est disponible sur le Contrôleur.

Nouveaux ActionResults : HttpNotFoundResult, RedirectPermanent, RedirectResult, RedirectToRoutePermanent, HttpStatusCodeResult.

Support de binding JSON. Les actionsresult peuvent maintenant recevoir les données encodées JSON et de le binder aux paramètres d’actions.

 

Il y a encore beaucoup d’autres fonctionnalités mais j’ai rappelé les plus importantes. Dans les prochaine série des blogs je vais me consacrer à décrire chaque fonctionnalité d’AS.NET MVC 3 dans les détails et en appliquant à un scénario bien réel.

A+

Thomas

Les actions results n’existent pas juste pour faire beau. En fait c’est une fonctionnalité puissante si elle est utilisée correctement. Dans les contrôleurs ASP.NET MVC je vois souvent ce type de code, qui de plus se répète à plusieurs endroits :

   1:  public virtual ActionResult Index() 
   2:  { 
   3:        if (!HttpContext.User.Identity.IsAuthenticated) 
   4:        { 
   5:              FormsAuthentication.RedirectToLoginPage();
   6:              return new EmptyResult(); 
   7:        }
   8:   
   9:        return View(); 
  10:  }

Personnellement j’ai quelques problèmes avec ça:

  1. Le code similaire est présent à plusieurs endroits. Ce n’est certainement pas le respect du principe DRY.
  2. L’utilisation de HttpContext. Bien que c’est une propriété du contrôleur du type System.Web.HttpContextBase qui est parfaitement mockable, il vaut mieux limiter son utilisation pour que les tests des contrôleurs soient plus faciles à réaliser.
  3. L’utilisation directe de FormsAuthentication qui n’est pas mockable donc pas de tests unitaires pour les actions qui l’utilisent.

Ce que j’aimerais, c’est, d’une, pouvoir tester mon action du contrôleur, et d’autre ne pas dupliquer ce code où j’en ai besoin. J’aimerais faire quelque chose comme cela :

   1: public virtual ActionResult Index()        
   2: {            
   3:     var view = View();            
   4:     return new LoginActionResult(view);        
   5: }
Ce possible en créant notre propre action result. Ce que j’aimerais c’est que si l’utilisateur n’est pas identifié alors je redirige vers la page de login, sinon je retourne la vue demandée. Regardons le code ci dessous :
   1: public class LoginActionResult : ViewResult    
   2: {       
   3:     public ViewResult ViewAfterSuccessfulLogin { get; private set; }         
   4:     
   5:     public LoginActionResult(ViewResult viewAfterSuccessfulLogin)        
   6:     {            
   7:         if (viewAfterSuccessfulLogin == null)                
   8:             throw new ArgumentNullException("viewAfterSuccessfulLogin", "Can't be null");                        
   9:         
  10:         ViewAfterSuccessfulLogin = viewAfterSuccessfulLogin;
  11:     }         
  12:  
  13:     public override void ExecuteResult(ControllerContext context)
  14:     {            
  15:         if (!context.HttpContext.User.Identity.IsAuthenticated)
  16:         {                
  17:             FormsAuthentication.RedirectToLoginPage();            
  18:         }             
  19:  
  20:         ViewAfterSuccessfulLogin.ExecuteResult(context);        
  21:     }    
  22: }

Rien de transcendent. Une petite clarification :

  1. Ligne 1 : On dérive notre classe de ViewResult, car ce qu’on veut exécuter c’est le traitement de la vue si l’utilisateur est identifié.
  2. Ligne 5 : On passe en constructeur la vue qu’on aimerais afficher si l’utilisateur est identifié. Je stocke sa valeur dans la propriété ViewAfterSucessfulLogin pour réutiliser plus tard.
  3. Ligne 13 : On override la méthode ExecuteResult ce qui permet à notre classe de traiter le résultat d’une action. Dans notre cas c’est l’affichage d’une vue.
  4. Lignes 15 : Vérification si l’utilisateur courant est identifié. Si oui, la vue passée en constructeur est traitée (ligne 20). Sinon, on est redirigé vers la page de login (ligne 17).

Voilà. Une petite amélioration de rien du tout mais qui clarifie le code des actions. De plus les tests unitaires sont plus faciles à réaliser. On peut tester que LoginActionResult est retourné ou de tester RedirectToRouteResult pour s’assurer que la redirection est correctement réalisée.

D’autres posts vont suivre sur l’ASP.NET MVC 3. Résolution 2011, se remettre à écrire plus de posts. On va voir combien de temps je vais la tenir Clignement d'œil

A+

Depuis la sortie de NDepend http://www.ndepend.com/ version 3.0 RTM au mois de février 2010, qui a d'ailleurs marquée une grande évolution en s'intégrant avec Visual Studio 2010, 2008 et même 2005, nous n'arrêtons pas d'avoir de nouvelles mises à jour fort utiles. C'est très encourageant. Ceci prouve que Patrick Smacchia et l'équipe de NDepend travaillait constamment sur l'amélioration de ce produit que tout bon développeur devrait avoir dans sa liste d'outils.

La première grande mise à jour datant d'il n'y a pas si long temps (Octobre), comportait l'incorporation de Context-Sensitive Help. Il s'agit d'augmenter l'apprentissage de l'outil grâce à l'aide contextuelle. Vous pouvez en apprendre d'avantage en lisant le post de Patrick ici : http://codebetter.com/blogs/patricksmacchia/archive/2010/10/11/software-learnability-increased-with-context-sensitive-help.aspx.

Je ne vais donc pas revenir sur cet aspect.

La fonctionnalité dont je veux m'intéresser est sortie au mois de Novembre avec la version 3.5 de NDepend et concerne la personnalisation des Reportings d'analyse. Je vais tester exactement ce point. Sachez que la version la plus récente est la 3.6, mais elle apporte pas mal d’optimisations sur le plan de performances dont nous n’allons pas nous intéresser dans cet article.

Tout d'abord je vais faire une analyse de mon projet avec la version 3.2, où cette fonctionnalité n'était pas encore présente. C'est un projet ASP.NET MVC 3 RC2 avec rien de particulier.

Tout d'abord, j'attache ma solution à un nouveau projet NDepend.

1_AttachingToVS

J'élimine ensuite les projets des tests unitaires car ils ne m'intéressent pas et je lance l’analyse avec l’option de construction de report.

2_Icone_Analyse

A la fin de l’analyse une page de report apparait. Bien que complète, un peu austère tout de même ! Quelques copies d’écran pour ceux qui ne connaissent pas (des extraits d’une longue page inétrminable) :

image

image

image

Et un exemple de requête CQL qui n’a pas été respecté par le code :

image

J’installe ensuite la version v3.5.0.5373 pour comparer les deux reports et je relance l’analyse.

Après l’analyse, le nouveau rapport s’affiche. La différence est flagrante. La nouvelle interface est ce qu’on appelle “User Firendly”. Un menu, bloc de résumé, les diagrammes, les métriques ainsi que les règles CQL tiennent pratiquement sur le même écran.

NDepend3.5_Summary

Bien entendu nous ne voyons que l’aperçu mais si on le souhaite nous pouvons aller dans les détails de chaque section. Les améliorations qui me plaisent personnellement sont indiqué sur l’image ci-dessus par des rectangles rouges numérotés de 1 à 5 :

  1. La zone de menu déroulant, qui permet un accès rapide à des sections qui vous intéressent. Concernant les règles “CQL Rules” vous avez tout de suite l’indication sur le nombre de règles réussies (en vert), celles qui ne sont pas respectées (en jaune) et en erreur (rouge).

Ci dessous le menu est déroulé. Vous n’avez même pas besoin d’aller dans les détails pour avoir une indications sur les règles qui ont réussi ou non.

4_NDepend_Menu

2. La deuxième zone présente le résume de l’analyse avec les informations de base. Une zone des liens d’aide est la bienvenue quand vous essayez de comprendre certains aspects de NDepend.

3. Un accès rapide à différents diagrammes qui s’ouvrent en popup.

4. Un résumé des métriques d’applications. Vous pouvez également afficher les statistiques d’application en cliquant sur le lien correspondant :

6_NDependAppStat

5. Le résumé de règles CQL. Bien entendu vous pouvez facilement naviguer vers les détails de chaque règle. Ici, j’affiche la règle qui a échoué concernant les conventions de nommage.

5_NDepend_Naming_Details

Comme-vous pouvez le constater, le nouveau système des rapports est bien plus agréable que l’ancien. L’information qu’on cherche est plus facile à trouver et la navigation est plus agréable. J’ai envie de consacrer plusieurs posts concernant NDepend et le refactoring. J’espère que je vais avoir le temps car à chaque fois les obligations professionnelles me détournent de mes objectifs de blogging Clignement d'œil

 

Sinon, Bonne année 2011 !

En guise d’introduction…

Ca fait long temps que je n’ai rien écrit. J’ai un peu été pris par des préoccupations professionnelles donc j’ai préféré de faire une pause plutôt que d’essayer de maintenir artificiellement mon blog à jour en postant des liens vers d’autres blogs ou de vous annoncer les dernières news que tout le monde a lu la veille sur le sites web américains ;) C’est pas mon style :)

Concrètement…

En étudiant le code d’un projet en ASP.NET MVC 2 sur lequel je travaille, j’ai remarqué que dans chaque service de domaine, un service de cache est injecté par le constructeur. Pour que vous-vous rendiez compte de quoi je parle, regardez ce petit schéma simplifié (classique) qui résume l’interaction entre les différents tiers :

image

Rien d’extraordinaire. Dans la pratique le Repository est injecté par le constructeur dans le service de domaine qui lui, à son tour est injecté par le constructeur au contrôleur ASP.NET MVC. Tout ceci se fait automatiquement grâce au conteneur DI Unity.

Et le cache dans tout ça ?

Ensuite vient la gestion du cache. Il y a un service qui permet d’ajouter et de supprimer les données du cache. Puisqu’on utilise Unity pour résoudre les graphes d’objet au runtime, la solution la plus simple consiste à injecter le service de cache par le constructeur directement dans le service. Je vous montre un extrait de code d’un tel service utilisant le cache qui appartient à mon tiers de domaine métier:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FooService : IFooService 
{
private readonly IFooRepository _repository;
private readonly ICacheService _cacheService;

public FooService(IFooRepository repository, ICacheService cacheService)
{
if (repository == null)
throw new ArgumentNullException("repository", "Service needs a non null container in order to initalize.");

if (cacheService == null)
throw new ArgumentNullException("cacheService", "Service needs a non null cache service in order to initalize.");

_repository = repository;
_cacheService = cacheService;
}
...

Donc lorsqu’Unity résout IFooService pour le contrôleur ASP.NET MVC il injecte automatiquement IFooRepository et ICacheService qui est notre service de cache. Ensuite le service de cache est utilisé de la manière suivante (example simplifié d’une méthode) :

1
2
3
4
5
6
7
8
9
10
11
12
13
public IEnumarable<string> GetFields(Type entityType) 
{
IEnumarable<string> fields = _cacheService.Get(entityType.FullName);


if (fields == null)
{
fields = _repository.GetFields(entityType);
_cahceService.Put(entityType.FullName, fields)
}

return fields;
}

La méthode est volontairement très simple mais il y a quelque chose qui me dérange :

  • le service intègre le code d’infrastructure concernant la gestion du cache. C’est contraire aux principes SRP et au OPC
  • normalement nous ne voulons pas gérer le cache de données au niveau du service mais au niveau du repository.
  • le service injecté par le constructeur devrait servir dans toutes les méthodes de l’application.

La gestion du cache étant un Cross Cutting Concern, elle ne devrait pas être mélangée avec le code métier. Je vais toujours insister que pour modifier la manière dont le cache est géré on est obligé non seulement de modifier le code à tous les endroits ou le service de cache a été injecté mais également si on a envie de modifier le côté fonctionnel on est obligé de retoucher les lignes responsables pour gérer le cache. Pas top ! (On me dit toujours que j’exagère avec le SRP mais bon, c’est parce que ceux qui me le reproche n’ont pas envie d’écrire du code propre sous prétexte que le code perde de son pragmatisme :)…)

Configuration dans Unity…

Tout ceci est configuré dans mon bootstraper de la manière suivante :

1
2
3
container.RegisterType<ICacheService, CacheService>();

container.RegisterType<IFooRepository, FooRepository>(new InjectionConstructor("connexionstring"));

Donc tout simplement on enregistre les mappings entre les abstractions et les classes concrètes. Lorsqu’on demandera le service FooService, Unity injectera la chaîne de connexion dans FooRepsoitory et passera l’instance au constructeur de FooService ainsi que l’instance de CacheService.

Refactoring…

Pour ma part j’ai envie de descendre la gestion du cache de données au niveau du Repository. La première tâche consiste à nettoyer le service de tout code qui contient la gestion du cache. Le constructeur est dorénavant épuré :

1
2
3
4
5
6
7
public FooService(IFooRepository repository) 
{
if (repository == null)
throw new ArgumentNullException("repository", "Service needs a non null container in order to initalize.");

_repository = repository;
}

Etant donné que le service de cache n’est plus injecté par le constructeur nous pouvons donc nettoyer toutes les méthodes de la gestion du cache. La méthode GetFields devient donc beacoup plus claire que précédemment :

1
2
3
4
public IEnumarable<string> GetFields(Type entityType) 
{
return _repository.GetFields(entityType);
}

Imaginez le service avec quelques méthodes de plus. Juste, sur une seule simple méthode nous avons supprimé 7 ligne de code, alors que si vous avez 5 ou 6 méthodes de plus, le gain sera beaucoup plus visible.

Regardons de plus prêt le repository d’origine :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public partial class FooRepository: ObjectContext, IFooRepository 
{
public FooRepository(string connectionString) : base(connectionString, "FooRepository")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
public virtual string[] GetFields(Type entityType)
{
return (from entityDescriptor in EntityDescriptors
where entityDescriptor.EntityType == entityType.AssemblyQualifiedName && entityDescriptor.Enabled == true
select entityDescriptor.PropertyFullPath).ToArray();
}
}
Comme vous pouvez le voir, le Repository est en fait une abstraction autour du containeur EF4. Pour simplifier l’exemple, je présente juste un constructeur, celui, qui est utilisé par Unity pour injecter la chaîne de connexion lors de la résolution du graphe d’objet.

Pour intégrer le cache, je n’ai pas du tout envie de modifier l’implémentation actuelle de mon Repository. Je vais donc utiliser la pattern Decorateur pour l’implémenter. Pour parler simplement, le décorateur que nous allons créer englobera l’implémentation actuelle sans la modifier. Voici son implémentation :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class CachingFooRepository : FooRepository 
{
private readonly FooRepository _innerRepository;
private readonly ICacheService _cacheService;

public CachingFooRepository(IFooRepository repository, ICacheService cacheService)
{
if (repository == null)
{
throw new ArgumentNullException("repository");
}

if (cacheService == null)
{
throw new ArgumentNullException("cacheService");
}

_innerRepository = repository;
_cacheService = cacheService;
}

public override IEnumarable<string> GetFields(Type entityType)
{
IEnumarable<string> fields = _cacheService.Get(entityType.FullName);

if (fields == null)
{
fields = _innerRepository.GetFields(entityType);
_cahceService.Put(entityType.FullName, fields)
}

return fields;
}
}

Les étapes pour fabriquer le décorateur (un décorateur peut être fabriqué de différentes manières, donc ne prenez pas ce que je présente ici comme LA MANIERE de le faire) :

  1. J’ai dérivé mon CachingFooRepository du repository de base FooRepository. Une légère modification dans FooRepository consistait à passer les membres en virtual afin qu’on puisse les surcharger. Cela me permet de surcharger uniquement les méthodes qui m’intéressent pour intégrer le caching.
  2. Le constructeur comme vous pouvez le constater prend en paramètre IFooRepository qui est une implémentation par défaut et le service de cache ICacheService. Ils sont sauvegardés ensuite dans les champs privés.
  3. On surcharge la méthode GetFields pour intégrer le cache est on délègue le reste du travail au Repository interne _innerRepository.
Quels sont les avantages ?

L’avantage principal est que la logique de gestion du cache est déconnectée de la logique du Repository et surtout de la logique du service de domaine. Nous pouvons faire évoluer l’un indépendamment de l’autre. Ce qui est surtout intéressant ce que le service FooService prendre en paramètre une abstraction IFooRepository. Donc nous pouvons injecter soit l’implémentation par défaut FooRepository soit CachingFooRepository (car il dérive de ce dernier) et cela sans changer mon service.

L’enregistrement dans Unity des décorateurs.

La seule nuance consiste à enregistrer correctement tout cela dans Unity. Si on enregistre FooRepository et CachingFooRepository en tant que mapping pour l’abstraction IFooRepository, alors lors de la résolution du FooService, Unity ne saura pas qu’elle implémentation injecter de IFooRepository. Pour cela nous devons tout d’abord enregistrer l’implémentation par défaut du Reposiutory :

1
container.RegisterType<FooRepository>(new InjectionConstructor("connexionstring"));

Remarquez qu’on ne mappe plus FooRepository vers l’abstraction IFooRepository. On enregistre juste le type et la manière dont la chaîne de connexion doit être injectée. Ensuite nous devons enregistrer le décorateur :

1
container.RegisterType<IFooRepository, CachingFooRepository>(new InjectionConstructor(new ResolvedParameter<FooRepository>(), new ResolvedParameter<ICacheService>()));

Cette fois ci, nous mappons notre décorateur vers l’abstraction IFooRepository. On indique également à Unity, comment le constructeur doit être résolu, en passant l’instance précédemment enregistrée de FooRepository et de notre service de cache. De cette manière le service FooService sera correctement résolu.

Conclusion

Comme vous pouvez le voir l’utilisation des décorateurs est très utile lors de l’implémentation de Cross Cutting Concerns comme le cache, le logging ou la sécurité. Vous respecterez de cette manière le principe SRP et OCP. Il y a cependant quelques désavantages dont je n’ai pas parlé comme par exemple l’augmentation du nombre de classes de décorateurs et la répétition du code qui gère le cache. Cette fois-ci le principe DRY (don’t repeat yourself) risque d’en pâtir. Cela n’est pas très grave si la gestion du cache doit être implémenté dans quelques repositories ou services. Dans d’autres cas il y a une bien meilleure technique dont je vais parler dans mon prochain billet (si j’ai le temps) qui est l’Interception. Un autre mangnifique aspect de la DI !

A bientôt !

Jusqu’à fin avril vous avez encore la possibilité de vous inscrire sur le site de prometric.com et de vous essayer à une des certifications Béta sur le nouveau Framework 4. c’est ce que j’ai fait pour la certification “071-519 : Pro: Designing & Developing Web Applications Using MS .NET Framework 4” que j’ai passé aujourd’hui. Pour savoir si vous êtes éligibles n’hésitez pas à consulter la page officielle : http://blogs.technet.com/betaexams/.

Pour ceux que cela intéresse voici le déroulement et le contenu :

  1. Tout d’abord vous commencez par un “Use Case”. Pour ma part j’ai eu le sujet sur ASP.NET MVC 2 ce qui m’a tout de suite enchanté :) Le use case portait sur les parties principales d’un projet; UI, Ajax, Sécurité, Accès aux données, déploiement, etc. Au total 13 questions pour le Use Case.
  2. Après le Use case vous passez aux questions standards j’en ai eu 36 au total.

Les sujets principales :

  • beaucoup de ASP.NET MVC 2
    • migration d’une application ASP.NET vers ASP.NET MVC 2
    • DataBinding
    • Authentification et Authorization avec les attributs
    • test des contrôleurs
    • test des repositories
    • Actions personnalisées et les filtres
    • Logging et caching
  • ASP.NET
    • quelques questions par rapport aux nouveautés de ASP.NET 4
    • questions standards comme dans les autres tests
  • WCF
  • EF4
  • jQuery
  • la bibliothèque MS AJAX
  • une question sur les services web REST.
  • des questions standards sur le déploiement, les sessions, web-farms, cache distribué etc.

Je m’en souviens pas plus de sujets. Le test n’était pas super difficile mais on ne sait jamais. En tout cas comme dans le cadre de tous les examens béta vous devez attendre les résultats pendant au moins 2 mois. On verra !

J’ai voulu laisser quelques commentaires à MS par rapport à certaines questions qui m’ont parues bizarres (WCF une technologie pour accéder aux données :S) mais lorsque j’ai voulu commenter, l’application de test s’est plantée et il fallait redémarrer l’ordinateur. A l’initialisation de cette application j’ai vu qu’elle était en JAVA ;) Oufffff :) J’ai laissé donc tomber ;) (je plaisante bien sur)

A+

AutoFixutre est une librairie qui crée les variables anonymes pour vos besoins lorsque vous créez un test unitaire. Son but est d’augmenter votre productivité et la lisibilité de vos tests quand vous faites du TDD (Test Driven Development).

Dorénavant vous ne dépenserez pas vos ressources mentales pour créer des objets avec les données pour vos tests, car AutoFixture peut le faire pour vous.

Par défaut AutoFixure se base sur le Constrained Non-Determinism (que je vais expliquer plus loin) mais vous pouvez modifier son comportement.

Sans rentrer dans les explications voici de quoi avait l’air mon test unitaire initial :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[Test] 
public void should_create_instance_with_correct_ctor_parameters()
{
var carrierMovements = new List<CarrierMovement>();
var deparureUnLocode1 = new UnLocode("AB44D");
var departureLocation1 = new Location(deparureUnLocode1, "HAMBOURG");
var arrivalUnLocode1 = new UnLocode("XX44D");
var arrivalLocation1 = new Location(arrivalUnLocode1, "TUNIS");
var departureDate1 = new DateTime(2010, 3, 15);
var arrivalDate1 = new DateTime(2010, 5, 12);
 

var carrierMovement1 = new CarrierMovement(departureLocation1, arrivalLocation1, departureDate1, arrivalDate1);

var deparureUnLocode2 = new UnLocode("CXRET");
var departureLocation2 = new Location(deparureUnLocode2, "GDANSK");
var arrivalUnLocode2 = new UnLocode("ZEZD4");
var arrivalLocation2 = new Location(arrivalUnLocode2, "LE HAVRE");
var departureDate2 = new DateTime(2010, 3, 18);
var arrivalDate2 = new DateTime(2010, 3, 31);


var carrierMovement2 = new CarrierMovement(departureLocation2, arrivalLocation2, departureDate2, arrivalDate2);

    carrierMovements.Add(carrierMovement1); 
carrierMovements.Add(carrierMovement2);

    new Schedule(carrierMovements).ShouldNotBeNull(); 
}

Sans comprendre exactement ce que fait le test, vous remarquerez que 90% du code de mon test constitue la création des instances d’objets et leur initialisation avec des données. Au final une ligne de code suffit pour tester que mon Entité Schedule se crée correctement.

J’ai du passer plus de temps à comprendre quels objets je dois instancier et comment les alimenter en données afin de réaliser mon test que d'écrire le test lui-même. Ma productivité est donc diminuée car je dois réaliser les tâches annexes et répétitives. Je vous ai montré un test mais suivant le comportement que je dois tester dans mon code de production je peux vouloir initialiser différemment mes objets de tests. Je vais me retrouver au final avec des tests illisibles à cause du code de l’initialisation et de la configuration du test.

Voyons de quoi mon test à l’air après l’avoir refactoré avec la magnifique bibliothèque AutoFixture:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Test] 
public void should_create_instance_with_correct_ctor_parameters_AutoFixture()
{
var fixture = new Fixture();


fixture.Register(() => new UnLocode(UnLocodeString()));


fixture.Register<IList<CarrierMovement>>(() => fixture.CreateMany<CarrierMovement>().ToList());


fixture.Register(fixture.CreateMany<CarrierMovement>);


var schedule = fixture.CreateAnonymous<Schedule>();

schedule.ShouldNotBeNull();
}

La différence avec le test précédent est énorme. Quand vous faites du TDD il est important d’aller vite et de se concentrer sur la fonctionnalité que vous allez tester. Avec AutoFixture la mise en place de mes objets est pratiquement immédiate et je ne me préoccupe pas de leurs initialisation avec les données. Mon test n’est peut-être pas plus facile à lire pour quelqu’un qui ne connait pas AutoFixture mais je vous assure, qu’une fois vous avez compris ces objets de base vous verrez que son utilisation est très simple est rapide.

L’objet principal est Fixture. Vous pouvez l’utiliser tel quel, le personnaliser, dériver, etc. C’est une classe de base pour l’objet Fixture. le but de Fixture est de créer une nouvelle instance de SUT (Système Under Test) et de toutes les variables nécessaires.

Au lieu de réfléchir et nous fatiguer sur la création et l’initialisation de nos objets en données nous utilisons les méthodes CreateAnonymous<T> et CreateMany<T> pour créer un objet ou une collection d’objets prêtes à tester.

Ces méthodes peuvent créer des instances de tous les types CLR tant qu’un constructeur public existe (par Reflection). Pour les type primitives (comme Int32) AutoFixture a des algorithmes pour la création des valeurs qui correspondent au Constrained Non-Determinism.

La méthode Register permet de personnaliser la génération des objets dont je vais parler dans un prochain post.

Constrained Non-Determinism

Vous pourriez dire STOP. Si la librairie génère des valeurs basées sur le non-déterminisme alors cela n’est pas conforme au fondement même du test unitaire qui EXIGE que chaque test doit être basé sur le déterminisme. J’en ai d’ailleurs parlé avec un développeur où on comparait brièvement la manière de fonctionner d’AutoFixture et de PEX.

Donc ?

Afin de comprendre comment fonctionne AutoFixture, il est important pourquoi la règle concernant le déterminisme des tests unitaires existe. Elle existe parce que nous voulons être sûr qu’à chaque fois nous exécutons notre test, nous vérifions exactement le même comportement comme nous l’avons fait lors de la dernière exécution du même test. Puisque nous utilisons également les tests pour se protéger contre la régression, il est important que nous soyons certains que chaque test vérifie les mêmes spécifications.

Constrained non-determinism ne change pas cela, car les algorithmes qui génèrent les valeurs s’assurent qu’elles soient conformes à l’entrée de l’Equivalence Class (technique qui permet de réduire le nombre de tests en groupant les entrées qui doivent produire les mêmes sorties ou qui exercent la même logique dans le système).

Conclusion

AutoFixture est certainement une bibliothèque intéressante à connaitre et à tester par ceux qui font du TDD. Depuis que j’ai commencé à l’utiliser je peux pas m’en passer. Je ferai encore quelques posts pour vous montrer d’autres cas d’utilisation.

Références

Vous pouvez télécharger AutoFixture sur CodePlex.

Pour plus d’informations n’hésitez pas à lire les posts du créateur d’AutoFixture Mark Seemann http://blog.ploeh.dk/.

 

 

P.S. J’ai un peu pris du retard sur l’écriture d’une application ASP.NET MVC d’exemple avec du DDD mais les prochains posts ne risquent pas de tarder. Je suis toujours en train de bosser dessus.

Ce post fait partie d’une série concernant le développement de l’application “Cargo Booking”. Pour la table des matières regardez ici .

Lors de mon post précédent nous avons introduit l’architecture que nous allons utiliser pour développer notre application. Pour aller plus loin nous allons avoir besoin de nous constituer un environnement de développement et de télécharger certains outils qui nous faciliterons la tâche. La liste d’outils contenue dans ce post n’est pas exhaustive, je la compléterai au fur et à mesure du développement de l’application de de l’écriture des cette série de posts. Je compte également expérimenter certaines solutions architecturales ce qui viendra certainement modifier ladite liste. Pour le moment j’ai identifié les outils suivants :

1. Environnement de développement (pour coder).

  • personnellement j’opte pour Visual Studio 2010. Vous pouvez utiliser n’importe quelle version disponible.
  • pour coder j’utilise également ReSharper version 5.0. Cela n’est pas nécessaire mais c’est bien pratique pour refactorer et naviguer rapidement dans le code. Une version d’évaluation est disponible sur le site de JetBrains.
  • j’utilise également NDepend pour l’analyse statique. Son utilisation n’est pas nécessaire non plus, mais grâce à cet outil vous pouvez améliorer la qualité de vos développements et rendre le refactoring beaucoup plus simple.

2. Tiers de domaine.

Dans ce tiers nous trouverons le cœur de notre application – notre logique métier. Ce tiers ne référence AUCUNE bibliothèque externe (à par le Framework.NET :)). Le code doit être le plus lisible possible même pour un non développeur. Il doit avoir un sens, une signification métier. Ce qui est important à noter ce qu’à partir de ce tiers AUCUN appel n’est fait à la source de données. Pour réaliser ce tiers, aucun outil particulier n’est nécessaire. Des simples classes C# (POCO) s’y trouveront.

3. Tiers de persistance.

Ce tiers contiendra une implémentation des “Repositories”, donc les éléments du domaine, dont la tâche principale est de créer une illusion que l’intégralité du modèle se trouve en mémoire. Les interfaces des repositories se limitent principalement aux méthodes de sélection (par ID, etc.) et d’insertion pour les objets métier principaux (aggregate roots).  Dans ce tiers nous trouverons également des mappers qui ont en charge le mapping entre les objets de domaine et des structures des bases de données. Les outils suivants nous seront très utiles :

  • NHibernate : vous pouvez le télécharger sur le site http://nhforge.org.
  • Fluent-NHibernate : pour permettre la configuration “fluent” à partir de votre code. http://fluentnhibernate.org/.
  • Entity Framework 4 : pour la version de notre application avec EF.

4. Tiers de présentation.

C’est dans ce tiers que nous trouverons la plupart des bibliothèques externes. Commençant par les outils IoC (Unity, StructureMap, etc.), la validation de données (xVal, Fluent validation, etc.), les extensions pour les views, etc. etc.

  • Containeurs IoC : à titre d’exemple, je vais en utiliser 2 : Unity et StructureMap. C’est pour ensuite vous démontrer un exemple d’implémentation de CSL (Common Service Locator) pour ceux qui ne voudrait pas se lier avec un IoC en particulier. Dans la pratique (vraie vie) cela n’a pas beaucoup d’utilité et il faut le dire ne sert pratiquement pas (à part le développement d’une API).
  • MvcContrib : contient quelques extensions très utiles pour les vues et les tests unitaires. Vous en trouverez bien d’autres dans cette bibliothèque mais je vais pas rentrer dans les détails.
  • Fluent Validation pour construire les règles de validation à l’aide de l’interface fluent et des expressions lambda.
  • On aura également besoin d’un outil qui s’appelle AutoMapper. Il nous permettra de faire un mapping objet à objet. A quoi cela va nous servir ? En fait, nous allons l’utiliser pour traduire nos objets de domaine en objets de vue. Afin de présenter les informations à l’utilisateur et des les collecter les objets de domaine doivent être simplifiés et agrégés en en forme d’objets plats de vues. Ce qu’on appelle le modèle de vue qui correspond à la lettre M dans le mot MVC.

Je compléterai plus tard cette liste si jamais je me trouve à utiliser d’autres outils que ceux présentés ici.

5. Tiers d’infrastructure.

Dans ce tiers nous allons nous occuper des aspects comme logging, caching et de la sécurité en général. Sans rentrer dans les détails, les outils que j’ai identifié pour le moment sont les suivants :

  • PIAB (Policy Injection Application Block), nous allons l’utiliser pour gérer le problème lié au logging et la gestion des exceptions façon AOP. En plus il s’intègre super bien avec Unity.
  • Log4Net nous servira pour implémenter le logging.
  • MEF (Managed Extensibility Framework) servira pour la création des plugins.

6. Tests

Plusieurs outils pour réaliser nos test unitaires, d’intégration etc.

  • Nous allons effectuer quelques expérimentations avec BDD. Pour cela je voudrais utiliser un framework qui s’intègre dans Visual Studio. Je pense que SpecFlow est pas mal.
  • La plupart d’outils BDD s’intègrent avec NUnit et ReSharper. Pour ceux qui ne dispose pas de R# cela peut s’avérer très lourd pour lancer les tests donc l’utilisation de MSTest intégré directement dans VS serait un plus. Je vais voir dans quelle mesure cela est possible, sinon nous allons utiliser NUnit.
  • Pour tester l’interface graphique WatiN marche très bien.
  • RhinoMocks pour créer nos stubs et mocks pour les tests unitaires.
  • SQLite pour nos tests d’intégration.
  • AutoFixture pour rendre nos tests clairs et maintenables.

Conclusion

Il y aura certainement d’autres outils. L’idée est de vous montrer comment ils peuvent être utilisés dans des applications réelles. Je vais certainement compléter cette liste au fur et à mesure de l’écriture de l’application. Je n’ai pas mentionné WF, WCF et tous les autres Frameworks MS que je compte également utiliser. Si vous avez d’autres idées n’hésitez pas à m’en faire part.

A bientôt pour la suite :)

Le sujet est ambitieux, mais ce post commence une grande série d’articles concernant le développement d’applications ASP.NET MVC en milieu d’entreprise en appliquant les bonnes pratiques de développement ainsi que le DDD (Domain-Driven-Design). Je pense que le développement d’une application de “A” à “Z” pourra pleinement démontrer certains choix architecturaux et technologiques. A mon avis cela sera plus bénéfique que des posts sortis du contexte sur un tel ou tel sujet. Afin de vous donner un aperçu, j’ai décidé de commencer par la table de matières.

Quelle application allons nous développer ?

J’ai décidé de porter de Java en .NET l’application d’exemple “Cargo Booking Application” qui est basée sur l’exemple utilisé dans le livre mythique d’Eric Evans “Domain-Driven Design: Tackling Complexity in the Heart of Software”. Ce n’est pas le premier portage de cet exemple et vous n’aurez pas de mal à en trouver sur internet. Mon but est de construire cette application depuis le début et vous montrer ce qui est important à prendre en compte pour que l’application réponde aux exigences de notre métier dans cet exemple qui est la réservation des cargos. Le but est d’appliquer le Domain-Driven-Design afin que la principale préoccupation soit la résolution efficace de la problématique du domaine métier et non l’application aveugle des technologies. Bien que le DDD est un sujet très large qui dans le monde réel nécessite une communication avec l’expert du domaine, une conception itérative du modèle et un découverte de langage unifié, cette application sera juste une petite partie d’un développement DDD que vous pouvez imaginer dans le monde réel. Pour cela nous allons implémenter l’application dans ASP.NET MVC 2. Nous allons nous également servir d’autres technologies que nous allons découvrir au fur et à mesure de l’avancement. Bien que la table des matières n’est pas figée voici une petite idée de ce qui va se passer (ça risque de changer) :

 

1. Introduction

1.1 L’architecture classique d’une application ASP.NET MVC.

1.2 Avant de commencer. Les outils dont on aura besoin.

2. Modèle du domaine

2.1 Introduction rapide au Domain-Driven-Design.

2.2 Définir le modèle du domaine est les règles métier.

2.3 Définir les Agrégats et les Repositories.

2.4 Evènements du domaine synchrones.

2.5 Validation de données.

3. Persister les données

3.1 Considération architecturales pour ne pas dépendre d’une base de données en particulier.

3.2 Utilisation d’un ORM.

        - NHibernate

        - Entity Framework 4

3.3 Mettre vos données dans le Cloud !

4. UI avec ASP.NET MVC.

4.1 Comprendre le MVC.

4.2 Comprendre les Vues et HTML Helpers.

4.3 Le Routing.

4.4 Le modèle de vue.

4.5 Les contrôleurs et les actions.

4.6 Les areas.

4.7 La navigation.

4.8 L’authentification.

4.9 Model binding.

4.10 AJAX (jQuery et non seulement).

4.11 Globalisation.

4.12 Validation de données.

5. Dependency Injection

5.1 Le concept. Pourquoi faire de l’injection de dépendance ?

5.2 L’utilisation d’un containeur IoC.

5.3 Les Framework existants pour ASP.NET MVC.

5.3 Implémenter CSL (Common Service Locator).

6. Tests unitaires, les tests d’intégration et plus

6.1 Tester le domaine façon BDD.

6.2 Tester les contrôleurs.

6.3 Tester les routes.

6.4 Tester UI.

7. Intégrer et exposer notre application

7.1 Workflow Foundation.

7.2 WCF.

7.3 Permettre de créer des modules avec MEF.

8. Performances

9. Intégration continue

10. Les aspects transversaux

10.1 La sécurité.

10.2 Le logging.

10.3 Le caching.

11. Le déployement

11.1 Utilisation avec les différentes versions de IIS.

12. Pour aller plus loin

12.1 Implémenter une base NoSQL.

12.2 Implémenter les événements du domaine d’une manière asynchrone.

12.3 Implémenter CQRS.

12.4 Implémenter CQRS avec Event Sourcing.

 

Il y a beaucoup de choses à voir :) Souhaitez-moi du courage :)

 

Le but final de notre application est de permettre ça

Et non ça :

Avec ce premier post je compte entamer une série consacrée au développement ASP.NET MVC (et MVC 2). Comme vous pouvez le constater, depuis l’apparition de la toute première version ASP.NET MVC a créé une sorte de “buzz” dans le développement web avec les outils de Microsoft. C’est vrai que ce Framework était un grand absent dans la palette d’outils de Microsoft et était attendu depuis une longue date. A son apparition, une forte communauté c’était formée autour de ce Framework est beaucoup de projets sont nés.

Vous-vous demandez pourquoi encore commencer une série des posts sur un sujet largement répandu sur le net ? 

Il y a justement autant de projets annexes plus ou moins connus qu’avant de commencer à coder votre application ASP.NET MVC il vaut mieux faire le tour de ces projets et voir ce qu’ils peuvent nous offrir afin de ne pas réinventer la roue. Ceci étant, dit, le but de cette série de posts ne pas l’apprentissage de base de comment on crée un modèle, une vue, un contrôleur, la validation, le routage, etc. Tout ceci est déjà très bien expliqué dans les tutoriaux officiels que vous trouverez ici : http://www.asp.net/learn/mvc/?lang=cs. Dans cette série j’insisterai plus sur un sujet de fond lié à l’architecture, les outils qui peuvent nous aider à coupler faiblement l’application l’utilisation de IoC par exemple, des pièges à éviter, la programmation AOP. Ce que j’essaierai de traiter se sont les bonne pratiques pour développer les applications business, maintenables et évolutives dans le temps. Les différents sujets seront introduit au fur et à mesure de l’évolution de cette série.

Nous commençons donc dans cette première partie par la définition de l’architecture applicative.

Postulat de départ

Les conseils que je vous donne ne sont en aucun cas à appliquer d’une manière stricte. Vous pouvez le prendre en compte ou ignorer, cela dépendra beaucoup de votre business et de la finalité de votre application. Ceci étant dit, lorsqu’on commence un nouveau projet ASP.NET MVC, l’application de bonnes pratiques dès le départ peut vous faciliter la vie par la suite.

Nous essayerons de créer une architecture faiblement couplée, facilement maintenable et hautement évolutive.

Nous testerons différents projets OpenSource autour de ASP.NET MVC afin de vous donner une idée de possibilités qui s’offrent à nous (je ne donne pas la liste des projets car je ne sais pas dans quel ordre je vais le traiter).

Séparer les préoccupations et les responsabilités (SoC)

La première étape dans la construction de notre application hautement maintenable et évolutive et la séparation des préoccupations métier et des responsabilités respectives. En English vous avez certainement entendu le terme (SoC : Separation of Concerns). Si vous avez commencer à suivre les tutoriaux sur la page officielle vous remarquerez que dans le code des exemples l’accès aux données est directement effectué à partir des contrôleurs en utilisant EF. C’est une très mauvaise idée. Si vous voulez modifier une partie de votre application vous risquez d’introduire des bugs dans une autre partie de l’application. Si vous voulez par exemple de modifier la logique de validation vous pouvez introduire des bugs dans le code d’accès aux données de votre contrôleur. Vous violez le principe SRP!

Pour en savoir plus sur le principe SRP, n’hésitez pas à lire mon post [Design Patterns] Partie 1: SRP: Single Responsibility Principle.

Il y a plusieurs raisons de modifier une telle application. Si par exemple vous décider de modifier l’implémentation de la couche d’accès aux données ceci n’est pas possible sans modifier l’application en entier. Ce n’est pas de la responsabilité du contrôleur de récupérer directement de données de la source de données. Il manque au moins deux couches entre les contrôleur et la couche de données. Le fait de procéder ainsi peut nous conduire à ce qu’on voit très souvent lorsqu’on développe avec les WebForms, où la plupart de la logique métier et accès aux données sont directement embarqués dans le code-behind des forms. Bien que l’utilisation d’ASP.NET MVC encourage la séparation de responsabilités pour la Vue, cette séparation n’est pas évidente entre le Modèle et le Contrôleur. C’est là, où rentre en compte la bonne architecture des couches applicatives.

Architecture – couches applicatives et tiers applicatifs

Le découpage en couche applicative est certainement le concept de base dans le développement des applications mais qui est très souvent négligé. J’ai souvent travaillé sur des projets où il y avait trop ou pas assez des couches applicatives. Avant de définir les couches de notre application, posons nous la question ce quoi une couche applicative ?

Pour moi une couche applicative est un regroupement logique de code qui ont les mêmes préoccupations. Un tiers est typiquement une frontière physique comme le serveur et le client par exemple. Un tiers peut contenir une ou plusieurs couches applicatives. Les couches qui se trouvent dans le même tiers vont typiquement communiquer d’une manière très étroite.

L’architecture que je vais proposer n’est pas une révolution en soi mais elle est éprouvée est marche très bien dans le développement ASP.NET MVC en utilisant les concepts DDD (Domain Driven Design). L’architecture de base peut se présenter de cette manière:

Architecture1

Tiers de présentation : Dans ce tiers nous avons trois couches et un utilitaire. Lorsqu’une requête http arrive, la première couche invoquée par ASP.NET MVC est le contrôleur (Controller). Le rôle du controleur est de récupérer les modèles du domaine par le biais des  services du domaine (Domain Services) dans le tiers de domaine. Il passe ensuite ces modèles à la vue sous la forme de View Model (modèle de vue). La conversion entre le modèle de domaine est le modèle de vue est en charge du mapper.

Dans le cas où une vue contient un formulaire les modèles de vue sont également utilisés. Ils contient de diverses données qui proviennent du modèle de domaine pour alimenter les contrôles comme par exemple les DropDownLists etc. Les mappers se chargement également de la conversion entre ces deux modèles.

Quand la data est postée le modèle de domaine est mise à jour.

Tiers de domaine : C’est le tiers le plus important dans le sens où tout dépend de lui.  Les services de domaine (Domain Services) fournissent l’accès aux données sous la forme du modèle de domaine (Domain Model). Le modèle de domaine contient la logique du métier pour laquelle l’application est construite. La communication entre la logique de notre application et le tiers de persistance se passe par les biais des Repositories.

Tiers de persistance : C’est le tiers où le données sont persistées. Les données peuvent être persistées dans une base de données relationnelle, des fichiers XML, web services, etc. La conversion entre les modèle de domaine est les mode de stockage de données est assuré par les Data Mappers. Les Data Mappers sont placés dans ce tiers mais ils peuvent être considérés comme un service.

Tiers d’infrastructure : Ce tiers contient les services potentiellement utilisés par tous les tiers comme la sécurité, le logging et le caching.

Conclusion

Cette architecture est très simple. Il peut exister des variantes et nous allons on parler dans de prochains posts lorsqu’on va introduire DI/IoC où la dépendance entre les couches va changer. Nous allons voir que cela a un grand impact sur la maintanibilité et l’extensibilité de notre application.

Dans les prochains posts nous allons rentrer plus en détails dans chaque couche et chaque tiers afin de détailler leur fonctionnement. Nous allons jouer avec l’architecture suivant les différents scénarios. Je vais préparer un post qui présentera tous les sujets que je vais traiter lors de cette série. Si vous avez des questions particulières n’hésitez pas à me laisser un commentaire.

Beaucoup de développeurs pensent que lorsqu’on créé et enregistre un IHttpModule, il n’en existe qu’une seule instance et la méthode Init() sera appelée qu’une fois.

C’est faux

Cela peut vraiment créer des bugs subtils dont en ne se rend pas compte tout de suite. J’ai eu dernièrement le cas sur un projet ASP.NET MVC sur lequel je travaille mais c’est valable également pour ASP.NET web forms classiques. C’est normal, c’est la même plateforme et les IHttpModules marchent de la même manière.

Mon module IHttpModule

HttpModules sont très utiles lorsque vous voulez vous brancher sur différents évènement émis par ASP.NET comme par exemple Begin_Request ou End_Request. Cela permet d’injecter dynamiquement de la fonctionnalité dans l’application ASP.NET. Un des modules les plus connus dans le monde MVC est par exemple UrlRouting.

Pour ma part j’ai crée un HttpModule pour pouvoir enregistrer et configurer mon containeur IoC StructureMap. Mon HttpModule est en quelque sort le CompositionRoot de mon application. Il se présente comme ceci :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class StructureMapDependencyRegistrarModule : IHttpModule 
{

protected static bool DependenciesRegistered;


public void Init(HttpApplication context)
{
context.BeginRequest += ContextBeginRequest;
}


private void ContextBeginRequest(object sender, EventArgs e)
{
EnsureDependenciesRegistered();
}


protected internal override void EnsureDependenciesRegistered()
{
// Simply clears default services injected into the container after initialization.
// This is mostly used to clear out any mock or stub objects introduced in a unit test after the unit test is over.
ObjectFactory.ResetDefaults();
DependencyRegistrar = new StructureMapDependencyRegistrar();
DependencyRegistrar.ConfigureOnStartup();
DependenciesRegistered = true;
}
}

Le module est enregistré de cette manière dans Web.config :

1
2
3
<httpModules> 
<add name="DependencyRegistrar" type="Jaskula.DependencyResolution.StructureMap.StructureMapDependencyRegistrarModule, Jaskula.DependencyResolution"/>
</httpModules>

Cela peut faire penser qu’une seule instance de IHttpModule sera créée et que la méthode Init() sera appelée qu’une fois et ainsi notre code d’initialisation sera exécuté qu’une seule fois lorsque la requête Http arrive. Cependant dans la vraie vie cela ne se passe pas comme ça. La méthode Init()sera exécutée plusieurs fois. Pour expliquer ce phénomène il faut comprendre le fonctionnement de HttpApplication contexte.

HttpApplication

HttpApplication contexte c’est l’objet qui est passé dans votre méthode Init() et qui est utilisé pour traiter une seule requête Http. Quand votre application démarre, le ASP.NET worker process instanciera autant d’objets HttpApplication qu’il estime avoir besoin et les insère dans son pool d’applications. Cela est fait pour optimiser les performances en réutilisant les instances déjà créées et le renvoyer au pool une fois utilisées. La création de ses objet est généralement très chère d’où l’idée de pooling comme dans le cas des connections à une base de données par exemple.

Lorsqu’une requête Http arrive, l’objet HttpApplication instanciera une copie de chaque IHttpModule enregistré et appellera sa méthode Init(). Donc si dans le pool il y a 10 objets HttpApplication vous allez avoir 10 copies de votre IHttpModule et sa méthode Init() sera appelée 10 fois ! Le nombre d’objets HttpApplication créés dans le pool dépendra du processus ASP.NET et son estimation de l’optimisation nécessaire. Donc si par exemple votre module utilise beaucoup de ressources externes il est possible qu’il soit créé plus de copies dans le pool.

Qu’il s’exécute qu’une seule fois !

Pour qu’il s’exécute qu’une seule fois nous allons recourir au vieux pattern double check/lock strategy ce qui nous assurera que la méthode Init() soit appelée qu’une seule fois. Notre module ressemble maintenant à ça :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class StructureMapDependencyRegistrarModule : IHttpModule
{

protected static bool DependenciesRegistered;
protected static readonly object Lock = new object();
 

public void Init(HttpApplication context)
{
context.BeginRequest += ContextBeginRequest;
}


private void ContextBeginRequest(object sender, EventArgs e)
{
EnsureDependenciesRegistered();
}


protected internal override void EnsureDependenciesRegistered()
{
if (!DependenciesRegistered)
{
lock (Lock)
{
if (!DependenciesRegistered)
{
// Simply clears default services injected into the container after initialization.
// This is mostly used to clear out any mock or stub objects introduced in a unit test after the unit test is over.
ObjectFactory.ResetDefaults();
DependencyRegistrar = new StructureMapDependencyRegistrar();
DependencyRegistrar.ConfigureOnStartup();
DependenciesRegistered = true;
}
}
}
}
}

Comme vous le voyez, il faut être très vigilent car ceci peut produire des bugs très subtils. Dans mon cas le code aurait enregistré plusieurs fois les mêmes types dans le containeur IoC ce qui finirai par jeter une exception. Ceci est un hint que je vous donne lorsque vous serez amené à jouer avec les modules http.

Bon codage :)

Après une petite introduction à son utilisation que vous pouvez lire ici j’ai également eu envie d’installer ma copie et de jouer un peu avec. Après les premières minutes de son utilisation. J’ai tout de suite ADORE. L’utilisation intuitive sans besoin d’aller ouvrir 50 fenêtres. Tout y est, dans Visual Studio.

Alors comment ça se passe ? J’ouvre mon projet ASP.NET MVC 2 sur lequel je suis en train de travailler. Tout de suite en bas à droite de mon IDE je vois une icône à la ReSharper

image

L’icône grise est celle de NDepend v3 et la vert de ReSharper.

Un petit click sur l’icône grise et un menu s’ouvre :

image

Ca me plaît :) Je peux directement attacher ma solution à NDepend à partir de Visual Studio. Je clique donc sur “Attach new NDepend Project to current VS Solution”. Une nouvelle fenêtre s’ouvre qui me propose de faire de l’analyse sur tous mes projets de la solution:

image

Je clique sur le bouton “OK” pour lancer l’analyse. Après quelque seconds un rapport est généré (je vais en parler dans un autre post) et la liste des erreurs affichée. D’ailleurs je constate que la bibliothèque System.Web.Mvc ne peut pas être chargée car ne se trouve pas dans le répertoire spécifié :

image

Je vais voir ce qui se passe dans les propriétés du projet (NDepend –> NDepend Project –> Edit Project {} Properties). Une fenêtre s’ouvre ou dans une colonne nous avons les assemblies du projet et dans l’autres les assemblies tiers. Vous pouvez constater que System.Web.Mvc n’est pas effectivement chargé:

image

J’apprécie l’intégration de la fenêtre dans l’environnement Visual Studio :). Techniquement parlant lors de l’installation de ASP.NET MVC 2, les assemblies ont été placés dans le répertoire C:\Program Files\Microsoft ASP.NET\ASP.NET MVC 2\Assemblies alors que si vous cliquez sur le bouton “View Folders” vous constaterez que ce chemin ne se trouve pas dans la liste de répertoires :

image

Rien de plus facile. Il suffit de rajouter le répertoire à la liste et le tour est joué. Je relance l’analyse est tout marche comme attendu.

image

Le but de ce post était juste de vous donner un aperçu de l’intégration de NDepend dans l’environnement de Visual Studio 2010. Dans les prochains posts je vous démontrerai plutôt les fonctionnalités offertes pour vous accompagner dans l’analyse et dans le refactoring de votre code.

En attendant pour en savoir plus je vous invite de vous rendre sur le site de NDepend : http://www.ndepend.com/.

A bientôt :)

Pour la persistance de données dans mes projets j’ai pratiquement toujours utilisé NHibernate qui pour moi est une référence en matière de ORM. La possibilité de pouvoir utiliser des POCO (Plain Old CLR Object) était un pré-requis pour avoir une couche supplémentaire d’abstraction par rapport à la couche de données. Je n’aime pas avoir mes entités polluées par le code de la persistance de données. Etant donnée que les versions précédentes d’Entity Framework ne supportaient pas cette fonctionnalité je ne me suis pas intéressé à son utilisation plus que ça (Il y avait également d’autre fonctionnalités qui ne ne me convenait pas mais je n’ai pas envie de rentrer dans le débat pour ou contre un tel ou tel ORM).

Ceci étant dit, la version 4 de EF semble vraiment plus riche en terme de fonctionnalités et donc je commence à m’y intéresser de nouveau. Lorsque vous savez comment l’implémentation des POCO est simple dans NHibernate je me suis posé la question comment cela a été fait dans Entity Framework. Ceci n’est pas un comparatif d’une telle ou telle implémentation, c’est juste ma découverte de Entity Framework que je voudrai partager avec vous.

Pourquoi utiliser les POCO ?

POCO (Plain Old CLR Object) est un objet standard que vous avez l’habitude d’utiliser lorsque vous développez sur la plateforme .NET (et oui, vous faites aussi du POCO avec VB, ce n’est pas plain old C# object comme beaucoup le pensent). Les POCOs sont donc utilisés pour définir le modèle de domaine. D’après l’approche DDD (Domain-Driven-Design) votre modèle métier est là pour conceptualiser la logique business que vous souhaitez traiter avec votre application. Donc pour parler simplement nous y trouvons des entités qui véhiculent l’état du système ou parfois qui possède un comportement, des relations, des services qui encapsule un comportement en mettant en interaction des entités, etc. Techniquement cela est représenté par des classes/objets toutes simples (POCO) dont la seule responsabilité et de traiter la problématique métier.

L’intérêt d’utiliser les POCO est de ne pas avoir à gérer la persistance au sein de vos entités. Donc les responsabilité de la logique métier et de la persistance sont bien séparées. C’est toujours dans le souci de respecter le principe SRP. Le fait que dans EF 4 nous ne sont pas obligé d’hériter de l’objet EntityObject nous permet de respecter le SRP.

POCO et EF

Dans les versions précédentes d’EF nous étions obligé que nos classes hérite de l’objet EntityObject qui fourni un service comme par exemple la notification de changement d’état, identité, la gestion des relations, etc. Vous utilisiez également les attributs spéciaux pour décorer vos classes et vos propriétés afin qu’EF puisse faire la correspondance entre chaque classe et propriété avec une propriété correspondante dans le modèle.

Dans EF vous avez deux manières d’implémenter les POCOs :

  • Le contexte est responsable de récupérer l’état à l’instantané d’une entité en faisant appel à la méthode ObjectContext.DetectChanges(). Cette méthode peut également être appelée en automatique par l’appel de la méthode SaveChanges().
  • Vous définissez toutes les propriétés d’un POCO en virtual (comme dans NHibernate) ce qui permets à EF d’attacher un objet proxy EntityObject à la classe POCO et qui fera ce que EntityObject sait faire déjà donc à savoir notifier le contexte des changements sur les propriétés et les relations et permettre le lazy loading.

Pour implémenter les POCOs vous pouvez importer votre modèle à partir de la base de données existante ou créer votre base de données à partir d’un model existant. Cette fonctionnalité n’est disponible qu’en version 4.0 de EF. Vous pouvez en savoir plus en lisant ce post (EN).

Dans notre exemple nous allons nous servir de la base de données Northwind alors nous allons créer notre modèle à partir de la base de données.

Lorsque j’utilise NHibernate, même si la base de données existe j’ai défini d’abord mon modèle métier à l’aide des POCOs et ensuite je défini mon mapping. Il est possible de suivre la même démarche avec EF et vous pouvez en savoir plus en lisant ce poste là (EN). Dans notre exemple nous allons utiliser les templates Visual Studio pour EF et les POCO et voir si notre productivité augmente, ce que logiquement devrait être le cas car les entités POCOs sont générées par le template.

Prérequis

Afin de poursuivre nos aurons besoin de:

  • Visual Studio 2010 RC
  • Entity Framework POCO Templates for C# que vous pourrez télécharger ici.
  • Une base SQL Server Northwind (et le serveur où la base est hébergée).
  • Rejeter tous les aprioris envers EF lorsqu’on est fan de NHibernate :)

Maintenant nous pouvons passer à notre modèle.

Modèle

Avant de générer nos classes POCO nous devons tout d’abord générer notre modèle des entités avec le template “ADO.NET Entity Data Model”. Notre modèle basé sur la base de données Northwind est le suivant :

NorthwindModel

Je n’ai pas utilisé l’intégralité de la base Northwind, le modèle ci-dessus en est juste un extrait que nous allons utiliser dans notre exemple. Ce que nous avons ici est très simple à comprendre. Une commande (Order) est passée par un client (Customer) est ensuite prise en charge par un livreur (Shipper). Cette même commande est également constituée de plusieurs lignes (Order_Detail) et chaque ligne concerne un produit (Product) obtenu auprès d’un fournisseur (Supplier). Je ne vais pas traverser les associations dans l’autre sens pour expliquer le modèle ;)

Vous constaterez que le modèle est simple, il n’y a que des associations et entités (cas d’école :)). Il serait beaucoup plus intéressant d’y introduire des scénarios plus complexes comme par exemple l’héritage. On aurait pu imaginer d’avoir des sous classes Toy, Instrument qui hériterai de la classe Product. On peut le faire avec le Designer dans VS mais c’est un sujet pour un autre post.

Générer les POCO

Maintenant que nous avons notre modèle il est temps de générer nos classes POCO. Pour cela il vous suffit de faire un click-droit sur la surface blanche de votre designer sous Visual Studio et de choisir le menu “Add Code Generation Item…

EFCodeGenerationItemMenu

Cela ouvrira une boîte de dialogue où vous verrez 3 templates :

  • ADO.NET EntityObject Generator
  • ADO.NET POCO Entity Generator
  • ADO.NET Self-Tracking Entity Generator

Pour notre exemple nous allons bien entendu choisi le template POCO. Je nomme donc mon élément “Northwind.tt” et je clique sur le bouton “Add”. Ensuite une boîte de dialogue s’affiche pour vous informer d’une éventuel problème de sécurité mais vous la validez en cliquant sur “OK”.

Après toute ces opérations voici ce qui a été généré; Une classe “Northwind.Context.cs” pour le contexte d’EF pour interagir avec la base de données et une classe POCO pour chaque Entité. Notre projet doit ressembler à ceci :

Solution

Notez tout de même que le T4 vous a également généré une classe Northwind.cs qui ne contient qu’une seule méthode FixupCollection<T>. Cette méthode permet de synchroniser les associations entre les objets.

Je ne vous conseille pas de modifier ces classes car de toute façon si vous modifier le modèle le T4 va régénérer et ré écraser ces classes.

Remarques

Il y a des points que je dois éclaircir et qui ne me plaisent pas pour le moment dans l’implémentation actuelle :

  • Entités POCOs gèrent ses relations avec les autres objets ce qui est normal. Cependant le code technique pour le faire est omniprésent et n’a rien à voir avec les préoccupations d’une entité POCO. Ce que je veux dire qu’à priori les entités savent (indirectement) comment elle seront persistées en BD car le code technique vérifie un tas de cas pour que justement le modèle puisse persister correctement. Ceci encore je peux comprendre pour les cas précis et bien déterminées mais dans ce cas il y en a partout. Il faut que je vérifie comment je peux le traiter autrement.
  • Relations bidirectionnelles partout. Si on suit la logique DDD les relations bidirectionnelles doivent être évitées si possible. Cela rajoute de la complexité au modèle et il faut gérer les cas de synchronisation des collection que j’ai évoqué dans le premier point. Mais c’est le business qui détermine si on en a besoin ou non des relations bidirectionnelles. Dans ce cas on en a mis partout au cas où on s’en servirai, mais pour moi par exemple, avoir une collection de produit dans l’Entité Category c’est absurde. Je dois donc modifier le modèle et régénérer mes classes. C’est un sujet pour le prochain post.
  • Value Objects au sens DDD, je ne vois pas bien encore comment je peux le gérer

Posts à venir

J’en ai pas terminé avec EF. En fait EF rentre dans une série des posts que je prépare sur l’architecture, DDD et ASP.NET MVC 2. J’aurais du commencer par définir le modèle du domaine tout d’abord mais j’ai d’abord voulu jouer avec EF4 comme un sale gosse. Mais pour le moment le jouet est cassé (peut-être que je ne sais pas jouer avec :)) et tant que je n’ai pas résolu certains problèmes que j’ai signalé dans les remarques je n’irai pas plus loin avec EF. Donc s’il y a des pros de EF et qui puisse me conseiller comment respecter les principes de DDD avec EF je serai très heureux. En attendant je vais modifier l’implémentation pour gérer mes remarques. Le prochain post concernera le DDD et la définition du modèle pour notre base Northwind. S’en suivront les sujets comme EF et DDD (Repositories, Agrégats, etc.)

A bientôt

C’est le dernier principe des principes du Design Orienté Objet (The Principles of Object Oriented Design) fondés par Robert C. Martin plus connu sous le pseudonyme d’Uncle Bob.

DIp_SOLID

l’image empruntée de LosTechies.

Je ne traite pas les principes dans l’ordre mais peu importe, cela n’a pas une grande importance. Pour rappel, voici les sujets précédents:

  1. [Design Patterns] Partie 1: SRP: Single Responsibility Principle
  2. [Design Patterns] SRP encore une fois

Dependency Inversion Principle (DIP): Principe d’Inversion de Dépendance.

“A: Les modules de plus haut niveau ne doivent pas dépendre des modules de plus bas niveau. Les deux doivent dépendre des abstractions”

“B: Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre des abstractions”

Le terme d’inversion de dépendance (DIP) est souvent assimilé aux termes d’Injection de dépendance et Inversion of Control (IoC). Pour ma part j’estime que l’injection de dépendance est un sujet très important de connaître et franchement, pour construire des applications robustes et évolutives il est très difficile de passer à côté. Je vais vous épargner le grand débat théorique dont j’ai l’habitude :) et cette fois ci passons directement à l’exemple. Le but de DI (et autre principes S.O.L.I.D) est de palier les problèmes liés au mauvais design des applications. Bien que vous utilisez l’approche OOP, votre code est structuré à l’intérieur des classes mais il est toujours très difficile à le modifier et à le faire évoluer. C’est à cause de ces dépendances que les classes ont entre elles et si vous modifiez une classe il y a un risque qu’une autre “pète” à l’autre bout de la planète :)

Dans l’exemple suivant nous allons construire un pseudo loggueur. Pour une question de simplicité excusez-moi pour certains raccourcis (comme l’instanciation un peu hasardeuse ;))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Program 
{
static void Main(string[] args)
{
var logger = new Logger();

// mon programme démarre et il
// faut le loguer
logger.Log("Début du démarrage du programme");

Console.ReadKey();
}
}


public class Logger
{
public void Log(string message)
{
var txtLog = new TxtLogWriter();
txtLog.LogToFile(message);
}
}


public class TxtLogWriter
{
public void LogToFile(string message)
{
Console.WriteLine(message);
Console.WriteLine("--- Ecrit dans un fichier ---");
}
}

Comme vous pouvez le constater le programme est simplifié au maximum. L’idée est que la classe Logger est utilisée dans le programme principal pour écrire certains événements qui se passent lors de l’exécution de ce programme. Un message de type chaîne de caractères est passé à la classe Logger qui fait appel à son tour à la classe TxtLogWriter qui l’écrit dans un fichier de texte (dans l’exemple nous nous contentons de l’afficher dans la console).

Evolution en vue (comme d’hab) !

Mais, (il y a toujours un mais) on nous a demandé d’avoir la possibilité d’écrire les messages dans le journal d’évènements de Windows. Ah, c’est facile, est bien souvent on se lance dans la programmation pour créer une deuxième classe comme WinLogWriter :

1
2
3
4
5
6
7
8
public class WinLogWriter 
{
public void LogToEventLog(string message)
{
Console.WriteLine(message);
Console.WriteLine("--- Ecrit dans un journal Windows ---");
}
}

Le problème est que maintenant pour que cela fonctionne nous devons implémenter au sein de la classe Logger une condition qui nous permettra suivant un paramètre d'utiliser soit la loggueur txt soit le loggueur windows. L’ensemble du programme se présente maintenant comme ceci :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Program 

{
static void Main(string[] args)
{
var logger = new Logger("win");

// mon programme démarre et il
// faut le loguer
logger.Log("Début du démarrage du programme");

Console.ReadKey();
}
}


public class Logger
{
private string _typeWriter;

public Logger(string typeWriter)
{
_typeWriter = typeWriter;
}


public void Log(string message)
{
switch (_typeWriter)
{
case "txt":
var txtLog = new TxtLogWriter();
txtLog.LogToFile(message);
break;
case "win":
var winLog = new WinLogWriter();
winLog.LogToEventLog(message);
break;
}
}
}


public class TxtLogWriter
{
public void LogToFile(string message)
{
Console.WriteLine(message);
Console.WriteLine("--- Ecrit dans un fichier ---");
}
}


public class WinLogWriter
{
public void LogToEventLog(string message)
{
Console.WriteLine(message);
Console.WriteLine("--- Ecrit dans un journal Windows ---");
}
}

Constat, la partie switch est super MOCHE ! Pire encore, notre classe Logger est maintenant entièrement dépendante de 2 classes TxtLogWriter et WinLogWriter alors qu’avant elle ne dépanadait que d’une seule classe.  Qu’est que cela implique ? Que potentiellement une modification aura l’impacte sur plus de code, la maintenance sera plus difficile et les tests seront également plus difficile à réaliser.

Introduire une abstraction

En fait quand on y regarde de plus prêt, la classe Logger ne doit pas savoir qu’elle classe concrète logue le message. Tout ce qu’elle doit savoir c’est qu’UNE classe logue le message avec la méthode “WriteLog”. Nous pouvons accomplir cela avec une interface.

1
2
3
4
public interface ILogWriter 
{
void WriteLog(string message);
}

Ensuite nos deux classes TxtLogWriter et WinLogWriter doivent implémenter cette interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TxtLogWriter : ILogWriter 
{
public void WriteLog(string message)
{
Console.WriteLine(message);
Console.WriteLine("--- Ecrit dans un fichier ---");
}
}


public class WinLogWriter : ILogWriter
{
public void WriteLog(string message)
{
Console.WriteLine(message);
Console.WriteLine("--- Ecrit dans un journal Windows ---");
}
}

Maintenant dans la classe Logger nous instancions la classe que nous voulons en tant que ILogWirter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void Log(string message) 
{
ILogWriter logWriter;
switch (_typeWriter)
{
case "txt":
logWriter = new TxtLogWriter();
break;
case "win":
logWriter = new WinLogWriter();
break;
}

logWriter.WriteLog(message);
}

Cela cependant n’a pas amélioré notre design. Notre classe Logger est toujours dépendante des implémentations concrètes de 2 classes. Heureusement nous avons une autre arme à notre disposition pour palier à ça.

Injection de dépendance (Dependency Injection)

Ce que nous ne voulons pas c’est la dépendance en dure dans la classe Logger et les classes TxtLogWriter et WinLogWriter. (Le fait d’utiliser le mot clé new crée une dépendance entre les classes). Nous pouvons dire que la classe Logger contrôle la vie des autres classes. Ce que nous voulons c’est d’injecter la dépendance à l’exécution. Il y a plusieurs manière d’injecter les dépendances mais les plus connues sont celles-ci:

  • injection par constructeur
  • injection par la méthode
  • injection par la propriété
  • Factory pattern
  • à l’aide d’un outil IoC (Inversion of Control) comme Unity, StructureMap, Ninject, Autofact, Windsor….

Nous réaliserons ici l’injection par le constructeur. La classe Logger ne doit pas savoir qu’elle LogWriter elle va utiliser quand la classe sera utilisée.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Logger 
{
private ILogWriter _logWriter;


public Logger(ILogWriter logWriter)
{
_logWriter = logWriter;
}


public void Log(string message)
{
_logWriter.WriteLog(message);
}
}

Vous remarquerez que nous avons “viré” tout le bloc switch très moche. De plus la classe Logger ne sait pas qu’elle instance concrète elle utiliser pour logguer les messages. L’instance actuelle n’est plus créée dans Logger mais est injectée par le constructeur.

Nous pouvons également utiliser l’injection par la méthode ou la propriété mais là dessus je ferai un autre post pour expliquer les différences. Notre appel dans la méthode Main est maintenant comme ceci:

1
var logger = new Logger(new WinLogWriter());

Ici nous créons l’instance concrète mais bien souvent cette instance est passé par un containeur IoC ou une Factory abstraite.

Conslusion

DIP permet de créer les designs qui sont “propres” car les dépendances sont injectées, donc cette décision ne repose plus sur le client. Pour ma part j’utilise un containeur IoC mais vous pouvez aussi bien d’utiliser une factory abstraite pour les fournir. Le fait que notre desing soit plus propre implique que notre code est plus facile à modifier et à faire évoluer.

J’ai encore plein d’idées sur les sujets similaires mais je manque un peu de temps. Cependant si cela vous intéresse je peux bien me motiver ;)

A bientôt.

Je continue cette série de posts concernant les frameworks des test unitaires dans le développement BDD. Cette fois ci j’ai essayé MSpec que je ne connaissais pas très bien. Vous pouvez lire les postes précédents qui traitement des tests unitaires façon BDD:

  • [Tests] ASP.NET MVC 2 et les Tests Unitaires avec NBehave
  • [Tests] ASP.NET MVC 2 et les Tests Unitaires avec NBehave – Suite
  • [Tests] Ma quête BDD continue. Tests Unitaires avec SpecFlow

    Dans ce poste je vais également partir du même exemple (template par défaut de ASP.NET MVC 2) afin que vous puissiez comparer les différents frameworks des test unitaires BDD. Cette fois-ci je vais utiliser MSpec (machine.Specifications) qui est un framework de tests dont le but est de purifier et simplifier le code des tests. En fait plutôt que d’utiliser les méthodes et les attributs, MSpec utilise les délégués et les méthodes anonymes, cela bien évidement doit rendre votre code plus lisible. De plus, chose importante, une pleine intégration avec ReSharper 4.5 et 5.0.

    Pour commencer vous pouvez télécharger Mspec :

    Dans la version binaire vous avez l’installateur pour ReSharper. La syntaxe de MSpec est très simple. Tout ce que vous devez savoir sont les trois mots clés:

    • Establish
    • Because
    • It

    Le code de test de notre controlleur ASP.NET MVC est très simple donc je pense qu’une description détaillée n’est pas nécessaire (cependant laissez-moi un commentaire si vous voulez connaître les détails):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    public class AccountControllerTests3 
    {
    protected static AccountController controller;
    static IFormsAuthenticationService formsService;
    static IMembershipService membershipService;
    protected static ActionResult result;
    protected static LogOnModel model;


    Establish context = () =>
    {
    var controllerBuilder = new TestControllerBuilder();

    formsService = MockRepository.GenerateStub<IFormsAuthenticationService>();
    membershipService = MockRepository.GenerateStub<IMembershipService>();
    model = MockRepository.GenerateStub<LogOnModel>();

    controller = new AccountController(formsService, membershipService);
    controllerBuilder.InitializeController(controller);

    };


    Because user_logs = () =>
    {
    bool rememberMe = false;

    membershipService.Stub(
    x => x.ValidateUser("bdd", "mspec")).Return(true);
    formsService.Stub(x => x.SignIn("bdd", rememberMe));

    controller.ModelState.IsValid.ShouldBeTrue();
    };
    }

     



    [Subject(typeof(AccountController), "LogInTests")]
    public class When_logging_into_application_with_good_login_and_password : AccountControllerTests3
    {
    private It user_should_be_redirected_to_the_home_page = () =>
    {
    model.UserName = "bdd";
    model.Password = "mspec";

    result = controller.LogOn(model, string.Empty);

    result.AssertActionRedirect().ToAction<HomeController>(
    x => x.Index());
    };
    }
     
     


    [Subject(typeof(AccountController), "LogInTests")]
    public class When_logging_into_application_with_bad_login_and_password : AccountControllerTests3
    {
    It The_error_message_should_be_shown = () =>
    {
    model.UserName = "BAD";
    model.Password = "BAD";

    result = controller.LogOn(model, string.Empty);

    controller.ModelState[""].Errors[0].ErrorMessage.ShouldEqual(
    "The user name or password provided is incorrect.");
    };
    }

    Si on exécute nos tests dans ReSharper test runner nous obtenons le résultat suivant :

    MSpec

    Personnellement je ne sais pas si le code écrit est plus clean. Cela peut dépendre des cas. MSpec est une alternative intéressante mais pour le moment mon outils préféré reste quand même SpecFlow. Si parmi vous il y a des pros de MSpec, n’hésitez pas à me faire un retour pour améliorer mon test ou le réécrire. En tout cas c’est un outils intéressant dont je vais suivre l’évolution.

    A bientôt,

  • Depuis la toute première apparition de MEF sur codeplex les ambigüités fusent de tous les côtés. Les questions les plus souvent posées sont :

    • Quelle est la différence entre un IoC/MEF?
    • Dois-je utiliser MEF comme un IoC?
    • Peut-on utiliser MEF avec un autre IoC déjà en place?
    • MEF est il un IoC?

    Bien que je n’ai pas la prétention d’être un expert de MEF, voici comment je le comprends à travers son utilisation et les projets que j’ai fait (suis en train de faire), les discussions sur twitter et mes lectures :). Ce post ne rentre pas dans les détails techniques, c’est juste un point de départ pour une nouvelle discussion.

    1. Trois mots clés “Extensibility”, “Discovery”, “Composition”.

    MEF se concentre plus particulièrement sur l’extensibilité et la composition. Son but premier est d’étendre les applications en par exemple ajoutant tout simplement un binaire dans un répertoire prédéfini. MEF propose un mécanisme unifié pour atteindre ce but. Le développeurs n’ont plus besoin de réinventer la roue à chaque fois quand un mécanisme similaire est nécessaire.

    Donc, MEF en quelque sort peut-être considéré comme un Framework des “plug-ins” quand l’auteur de l’application n’est pas le même (dans la pratique l’auteur de l’application peut coder le deux) que l’auteur du plug-in et que ni l’application ni le plug-in n’ont aucune autre connaissance à part l’interface publiée (contract).

    2. Comment tout ceci se rapporte à IoC ?

    Pour moi la principale différence (non technique mais fonctionnelle) est que MEF est généralement utilisé pour gérer les “instances” que vous ne connaissez pas et dont vous n’avez rien à faire quel type concret sera derrière l’interface, alors qu’un containeur IoC sert à gérer les “instances” que vous connaissez.

    Vous pouvez par exemple avoir enregistré dans votre containeur un Logger pour l’utilisation dans votre application et un autre Logger pour le besoin des tests unitaires. Potentiellement avec MEF vous pourrez avoir de 1 à N Loggers dont vous ne connaissez pas le type et l’implémentation. Encore une fois, l’intention de MEF est de gérer l’extensibilité par plug-in anonymes ce que le différencie d’un conteneur IoC standard.

    La où le conteneur IoC est très bon c’est le contrôle de votre code au bas niveau, contrôle de la dépendance des grappes d’objets, gestion de la vie des instances, gestion de l’AOP et des décorateurs.

    3. L’un sait faire ce que l’autre ne sait pas faire (et vice versa :))

    Effectivement il y a des fonctionnalités qui peuvent être implémentées avec MEF ou un containeur IoC avec plus ou moins d’effort mais comme je l’ai souligné au début de l’article le but de MEF n’est pas le même.

    Même dans mes petits projets je favorise l’utilisation d’un containeur IoC. Une de bonnes pratiques d’utilisation d’un containeur IoC et que vous le référencez uniquement dans un endroit appelé le CompositionRoot. Dans la plupart des applications cela est un code lancé au démarrage d’une application comme par exemple Global.asax ou un HttpModule. Ce la veut dire que votre code ne dois plus jamais référencer le conteneur dans un autre endroit. Tout la magique d’un conteneur IoC est qu’il enregistre les objets et leur dépendances et permet de les construire et nous les fournir dès qu’on en a besoin.

    Un petit problème

    Cependant cela peut poser un petit problème. Dans les applications très larges la configuration centrale peut donner mal à la tête car elle peut vite devenir très difficile à lire et à comprendre. De plus, la configuration centrale peut-être assimilé à des variables globales qui sont accessibles à tout le monde et vous ne savez plus qui les utilise et quand. Bien que la plupart des containeurs IoC donne la possibilité de gérer les sous containeurs dans la pratique il est très difficile de les gérer indépendamment.

    Enfin, dans une large application nous risquons de nous retrouver avec des containeurs IoC qui gèrent des centaines des composants enregistrés alors que la plupart des opérations dans l’application n’en utilise que quelque uns. C’est là où je vois la limitation d’un containeur IoC.

    MEF again

    Ce que permet MEF c’est de diviser votre système en morceaux plus petits. MEF permet de coder les applications comme une série des applications plus petites et quand on relie toutes les applications nous obtenons l’ensemble des fonctionnalités. Comme on a dit, MEF permet la découverte des plug-ins (extensions). Il dispose d’un mécanisme qui en se servant de la metadata peut accéder aux extensions. MEF permet de décorer les extensions avec une metadata additionnelle qui permet les requêtage et le filtres puissants. Combiné avec une capacité de faire du lazy load sur les extensions, d’interroger la metadata avant de charger l’extension permet de scénarios intéressants comme par exemple le versionning des plug-ins.

    Conclusion

    Je pense que l’utilisation de l’un n’exclu pas l’utilisation de l’autre. Le containeur IoC et MEF ne sont pas destinés pour faire la même chose. Faire cohabiter les deux est une chose un peu plus difficile car il faut bien réfléchir à des aspects qui ne se posent pas lorsque vous développez avec l’un ou l’autre. Quel containeur prend en charge la vie des instances ? Est-ce que MEF peut accéder aux service IoC et comment ? Est-ce que le containeur IoC peut accéder aux exports MEF ? Cela rajoute de la complexité donc réfléchissez bien avant de vous lancer. Il y a des efforts qui sont fait dans ce sens dans MEFcontrib où par exemple une intégration MEF/Unity est possible. De même Castle Windsor est en train de travailler sur le même genre d’intégration.

    A bientôt

    Dans mes posts précédents j’ai commencé à parler des tests unitaires avec NBehave. Si vous êtes curieux vous pouvez y jeter un œil mais cela n’est pas nécessaire pour comprendre cet article. Quoique pour les exemples ça peut servir:

    Cette fois ci, je vais reprendre l’exemple précédent et l’utiliser avec SpecFlow. SpecFlow permet d’écrire des scénarios dans les exigences BDD. Le langage utilisé est compatible avec Gherkin qui est un Business Redeable Domain Specific Language. Gherkin est compris par Cucumber qui est un autre outils pour le développement BDD. Ce qui est important ce que nous n’avons pas besoin d’avoir notre propre environnement d’exécution (comme la console NBehave dans l’exemple précédent) mais NUint s’en chargera pour nous.

    Note: Avec SpecFlow on ne parle plus de Story mais de Feature (exigence Gherkin).

    Passons donc à la pratique.

    1. Téléchargez SpecFlow ici et installez-le.

    2. Ajoutez une référence vers le TechTalk.SpecFlow.dll dans le projet qui contiendra vos Features (dans le projet de test en l’occurrence).

    3. Ajoutez le fichier de feature écrite au format Gherkin. Ce qui est super ce qu’en installant SpecFlow vous disposer maintenant des templates dans Visual Studio pour créer votre feature.

    SpecFlow_visualStudio_integration

    Une feature avec des scénario par défaut est proposé. Nous allons le changer par le notre. Celui que nous avons utilisé avec NBehave (en renomme juste Story en Feature)

    Feature: Handle user log in
        In order to check authentication
        As a user
        I want the system to check if user can log in

    Scenario: User logs in

        Given I would like to log in
        When I log in as bdd with a password nbehave without remembering me
        Then I should be redirected to the home page


    Scenario: User doesn't log in
        Given I would like to log in
        When I log in as BAD with a password BAD without remembering me
        Then The error message should be shown

     

    Comme vous pouvez le constater SpecFlow génère lui même le code qui fait l’intégration de nos tests dans NUnit !

    SpecFlow_VisualStudio_Integration2

    4. Maintenant nous implémentons notre classe de test. Je l’ai appelé AccountControllerTests2 et par rapport à la classe que nous avons utilisé avec NBehave seulement les choses suivantes changent :

    • L’attribut ActionSteps a été remplacé par l’attribut Binding.
    • Les paramètres sont passés à l’aide de la chaîne "(.*)” et non le caractère $ comme dans l’exemple précédent.

    Et (puis) c’est TOUT :)

    Voici la classe complète :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    [Binding] 
    public class AccountControllerTests2
    {
    private AccountController controller;
    private IFormsAuthenticationService formsService;
    private IMembershipService membershipService;
    private ActionResult result;
    private LogOnModel model;

    private void SetUpController()
    {
    var controllerBuilder = new TestControllerBuilder();

    formsService = MockRepository.GenerateStub<IFormsAuthenticationService>();
    membershipService = MockRepository.GenerateStub<IMembershipService>();
    model = MockRepository.GenerateStub<LogOnModel>();

    controller = controllerBuilder.CreateController<AccountController>(new object[] {formsService, membershipService});
    }

    [Given(@"I would like to log in")]
    public void AccesAccountController()
    {
    SetUpController();
    controller.ShouldNotBeNull();
    }

    [When(@"I log in as (.*) with a password (.*) without remembering me")]
    public void LogIn(string username, string password)
    {
    bool rememberMe = false;

    membershipService.Stub(x => x.ValidateUser(username, password)).Return(true);
    formsService.Stub(x => x.SignIn(username, rememberMe));

    model.UserName = "bdd";
    model.Password = "nbehave";

    controller.ModelState.IsValid.ShouldBeTrue();

    result = controller.LogOn(model, string.Empty);
    }

    [Then(@"I should be redirected to the home page")]
    public void RedirectToHomePage()
    {
    result.AssertActionRedirect().ToAction<HomeController>(x => x.Index());
    }

    [Then(@"The error message should be shown")]
    public void ShowErrorMessage()
    {
    controller.ModelState[""].Errors[0].ErrorMessage.ShouldEqual("The user name or password provided is incorrect.");
    }
    }

     

    5. Exécutez vos tests ! C’est super, l’intégration est complète avec NUnit et par conséquent avec ReSahrper.

    SpecFlow_ReSharper_OK

    Juste pour vous montrer que le test échoue bien lorsque le mot de passe fourni n’est pas bon :

    SpecFlow_ReSharper_KO

    Conclusion

    Avec l’outils comme SpecFlow le développement BDD devient encore plus facile. L’intégration complète avec Visual Studio NUnit et donc ReSharper est importante car on n’a pas besoin d’exécuter des outils externes comme dans le cas de NBehave.

    J’espère que SpecFlow va évoluer. Il est prévu entre autres l’intégration avec MSTests.

    Sur le site de SpecFlow vous trouverez également une version pour Visual Studio 2010.

    UPDATE: On vient de me tweeté que SpecFlow supporte les scénarios écrits en Anglais, Français, Allemand, Hongrois, Hollondais et Suédois. Donc vous n'êtes pas obligé d'utiliser l'anglais

    Bons tests et à bientôt :)

    Après une petite introduction dans mon post [Tests] ASP.NET MVC 2 et les Tests Unitaires avec NBehave aux tests unitaires avec NBehave. J’ai décidé de terminer mon exemple en employant la nouvelle syntaxe de NBehave. Cette fois ci je teste la classe AccountController du template de création de l’application web ASP.NET MVC 2.

    De quelle nouvelle syntaxe on parle ?

    Si vous vous rappelez l’exemple précédent nous avons utilisé la syntaxe fluent pour définir nos scenarios. En fait, cette syntaxe n’est plus conseillée ce que personnellement je trouve un peu dommage. Il est dorénavant préconisé d’utiliser les attributs Given/When/Then directement pour décorer les méthodes.

    De plus les Stories et les Scenarios doivent être décrits dans un fichier texte externe ce qui permet de modifier les scenarios de test sans recompiler le code.

    La partie pratique

    Tout d’abord définissons nos scenarios de tests dans un fichier texte externe. Pour le besoin de notre exemple je crée un fichier AccoutController.scenario que j’inclus dans mon projet de tests que se présente comme ceci:

    NbehaveSolution

    Le contenu de ce fichier contient une Story et deux Scenarios pour tester que l’utilisateur peut se logeur avec un login et un mot de passe valide et qu’un message d’erreur sera affiché dans le cas où l’utilisateur ne possède pas de login et/ou de mot de passe valide:

    Story: Handle user log in

    Scenario: User logs in

        Given I would like to log in
        When I log in as bdd with a password nbehave without remembering me
        Then I should be redirected to the home page


    Scenario: User doesn't log in
        Given I would like to log in
        When I log in as BAD with a password BAD without remembering me
        Then The error message should be shown

    Comme vous pouvez le constater les scenarios ne contiennent aucune ligne de code et sont compréhensibles par des utilisateurs non techniques.

    Maintenant définissons notre classe de test pour les scenarios ci-dessus. La classe doit être décoré avec l’attribut ActionsSteps afin d’interpréter ce fichier de texte. Une correspondance est fait entre chaque ligne de texte et les méthodes décorées avec les attributs correspondants. Donc par exemple la phrase :

    Given I would like to log in

    sera mappée soit à la méthode décorée comme ceci :

    1
    2
    [Given("I would like to log in")] 
    public void AccesAccountController()

    soit à la méthode qui sera appelé de cette manière :

    1
    2
    [Given] 
    public void I_would_like_to_log_in()

    Je crois également que le CamelCase marche au lieu des “_”.

    Le but de ce post n’est pas l’explication de la manière dont les tests ont été écrits mais si vous avez des questions n’hésitez pas à me faire un commentaire. En tout cas avec NBehave nos tests peuvent ressembler à ceci:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    [ActionSteps] 
    public class AccountControllerTests
    {
    private AccountController controller;
    private IFormsAuthenticationService formsService;
    private IMembershipService membershipService;
    private ActionResult result;
    private LogOnModel model;

    private void SetUpController()
    {
    var controllerBuilder = new TestControllerBuilder();

    formsService = MockRepository.GenerateStub<IFormsAuthenticationService>();
    membershipService = MockRepository.GenerateStub<IMembershipService>();
    model = MockRepository.GenerateStub<LogOnModel>();

    controller = controllerBuilder.CreateController<AccountController>(new object[] {formsService, membershipService});
    }

    [Given("I would like to log in")]
    public void AccesAccountController()
    {
    SetUpController();
    controller.ShouldNotBeNull();
    }

    [When("I log in as $username with a password $password without remembering me")]
    public void LogIn(string username, string password)
    {
    bool rememberMe = false;

    membershipService.Stub(x => x.ValidateUser(username, password)).Return(true);
    formsService.Stub(x => x.SignIn(username, rememberMe));

    model.UserName = "bdd";
    model.Password = "nbehave";

    controller.ModelState.IsValid.ShouldBeTrue();

    result = controller.LogOn(model, string.Empty);
    }

    [Then("I should be redirected to the home page")]
    public void RedirectToHomePage()
    {
    result.AssertActionRedirect().ToAction<HomeController>(x => x.Index());
    }

    [Then("The error message should be shown")]
    public void ShowErrorMessage()
    {
    controller.ModelState[""].Errors[0].ErrorMessage.ShouldEqual("The user name or password provided is incorrect.");
    }
    }

    A noter que notre méthode SetUpController() prépare notre contrôleur de test en lui passant tous les objets fake nécessaires.

    Vous pouvez également passer les paramètres à vos méthodes à partir du fichier de texte en les marquant avec “$” comme dans la phrase ci dessus: I log in as $username with a password $password without remembering me. Le mot trouvé dans le fichier texte sera passé en paramètre à la méthode qui d’ailleurs doit accepter ces paramètres dans sa signature.

    Exécution des tests

    Maintenant il faut qu’en exécute nos tests pour s’assurer que tout fonctionne correctement. Pour cela nous allons utiliser la console fournie avec NBehave. La ligne de commande suivante permet d’exécuter nos tests:

    NBehave-Console.exe MvcApplication.Tests.dll /sf=Specs\AccountController.scenario

    Remarque: En paramètre sf nous passons notre fichier texte contenant des scénarios.

    Si tout se passe bien vous devriez avoir le résultats suivant:

    NBehave-Console_Results

    Conclusion

    Personnellement j’aime bien l’approche BDD. Je trouve que le fait d’écrire des scénarios et des stories permet de mieux se concentrer sur la fonctionnalité métier que nous voulons tester. Concernant la nouvelle syntaxe de NBehave il y a des avantages et des inconvénients.

    Avantages

    • les tests sont plus facile à lire car sont écrit dans un langage naturel.
    • le code de tests est plus facile à lire également.
    • les stories et les scenarios peuvent être écrits par des personnes non techniques.
    • possibilité de modifier les valeurs des tests sans recompiler le code.

    Inconvénients

    • pour le moment on est obligé d’utiliser la console de NBehave pour exécuter les tests. Je n’ai pas trouvé comment les lancer avec ReSharper ou NUnit. Si vous avez des infos là dessus je suis preneur.
    • il faut tout de même modifier et recompiler le code si vous ajoutez une phrase Given/Then/When qui n’est pas mappé dans le code. Je veux dire par là que la l’utilisation du fichier texte est quand même limité.

    Pour ma part, l’utilisation de NBehave est très satisfaisante. N’hésitez pas à y jeter un œil car ça vaut vraiment le coup.

    A bientôt :)

    Juste un simple poste pour vous dire que...

    HighOnCoding vous propose de gagner un livre Pro ASP.NET MVC par Steven Sanderson tout simplement en tweetant :

    HighOnCoding giving away Pro ASP.NET MVC by Steven Sanderson. Winner announced Jan 31st. RT to WIN your copy #highoncoding

    Les resultats seront annoncés le 31/01/2010

    A+

    Dans mon poste précédent [Design Patterns] Partie 1: SRP: Single Responsibility Principle j’ai vous ai montré comment appliquer le principe SRP au niveau de l’application. Cependant comme je vous ai expliqué le principe SRP s’applique également au niveau d’une seule classe. Prenons un exemple suivant avec notre classe Product:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class Product 
    {
    public string Name { get; set; }
    public decimal? Price { get; set; }
    public int? UnitsInStock { get; set; }

    public Product(string name, decimal? price, int? unitsInStock)
    {
    Name = name;
    Price = price;
    UnitsInStock = unitsInStock;
    }

    public Product ApplyDiscount(bool applyDiscount)
    {
    // 2: calcul de la remise
    var discount = applyDiscount ? .90m : 1;

    return new Product(this.Name, this.Price * discount, this.UnitsInStock);
    }
    }

    J’ai dans cette classe une méthode ApplyDiscount(bool applyDiscount) qui permet de calculer une ristourne pour le produit. Tout va bien. Maintenant si j’ajoute une nouvelle méthode pour calculer le taux de la TVA par exemple :

    1
    2
    3
    4
    public Product ApplyTax() 
    {
    return new Product(this.Name, this.Price * 1.196m, this.UnitsInStock);
    }

    Logiquement notre classe a 2 responsabilités maintenant. Elle calcule la ristourne et la taxe. Si jamais nous devons changer la manière dont est calculé la taxe ou la ristourne nous devons obligatoirement modifier cette classe. Bien que les méthodes de calcul semblent être bien situées dans la classe Product, il est cependant préférable d’externaliser le calcul dans des classes externes. Il est très probable que la manière dont est calculé la taxe et la ristourne change. De plus nous pourrions plus facilement d’étendre notre modèle si nous devons définir plusieurs algorithmes de calcul et d’implémenter le pattern Stratégie. Pour cela par exemple pour calculer la ristourne nous pouvons créer la classe DiscountProductCalculator :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class DiscountProductCalculator 
    {
    public static Product GetDiscountedProduct(Product initialProduct, bool applyDiscount)
    {
    // 2: calcul de la remise
    var discount = applyDiscount ? .90m : 1;

    initialProduct.Price = initialProduct.Price * discount;

    return initialProduct;
    }
    }

    Nous modifions ensuite la méthode ApplyDiscount(bool applyDiscount)dans la classe Product afin d’utiliser notre nouvelle classe destinée à calculer la ristourne:

    1
    2
    3
    4
    public Product ApplyDiscount(bool applyDiscount) 
    {
    return DiscountProductCalculator.GetDiscountedProduct(new Product(this.Name, this.Price, this.UnitsInStock), applyDiscount);
    }

    Analogiquement nous pouvons créer une classe pour calculer la taxe (TaxProductCalculator) et modifier la méthode ApplyTax() afin d’appeler notre nouvelle classe pour calculer la taxe. Je ne mets pas le code ici car la démarche est exactement la même que dans le cas de la ristourne.

    Conclusion

    Dorénavant si nous devons changer le mode de calcul pour la ristourne ou la taxe nous ne modifions que les classes correspondantes (en admettant que les appels des méthodes des ces classes de changent pas, sinon il faudrait aussi changer l’appelant donc la classe Product).

    Comme vous pouvez le voir le SRP s’applique également au niveau d’une classe simple. Bien que c’est une bonne pratique d’attribuer une responsabilité à une classe, il n’est parfois pas possible de le faire. Pour cela il ne faut pas tomber dans la paranoïa et essayer à tout prix de respecter ce principe. Il est important de juger les parties qui changent souvent et les encapsuler dans des classes externes.

    A bientôt



    Les 10 derniers blogs postés

    - Merci par Blog de Jérémy Jeanson le 10-01-2019, 20:47

    - Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

    - Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

    - Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

    - Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

    - Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

    - Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

    - Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

    - Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

    - SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01