Archive

Archive for mai 2013

Inversion de contrôle (suite)

mai 28, 2013 1 commentaire

Comme promis dans l’article d’introduction à l’IoC, nous allons illustrer par un exemple très simple ce qui se passe lorsqu’on effectue une inversion de contrôle (ou une injection de dépendance).

L’exemple consiste à afficher sur une page web (entre autres MVC, mais peu importe…) la liste de personnels pour un projet. Imaginons qu’on nous demande de développer une application de gestion de projets informatiques, et donc de pouvoir gérer les ressources pour les projets.

Premièrement, lorsqu’on veut architecturer une application en se basant sur le D-D-D (Domain-Driven-Design); il faut penser à se focaliser sur le code métier. Autrement dit, ce que l’on va écrire en premier lieu, c’est le code qui correspond au domaine métier. De tac au tac, pour afficher une liste de personnels, on a besoin bien évidement d’un objet « Personne »… cette classe sera à mettre bien évidement dans le projet « métier » (créer un objet de type « Class Library » et nommez-le comme vous le voulez, par exemple : Learning.Business.csproj

   public class Person

   {
        public string Name { get; set; }
        public string FirstName { get; set; }
   }

Pour illustration, je vais faire très simple, donc pas la peine de mettre des tonnes de propriétés pour notre objet « Person ».

Ensuite, dans le même projet Learning.Business.csproj, ajoutons une interface IPersonRepository. Cette interface servira de « contrat » pour le code utilisateur, afin de leur obliger de définir les méthodes nécessaires pour pouvoir « persiter » un objet « Person ». Ici, notons bien que : c’est le code métier qui exigera au code d’accès aux données, ce qu’il devra faire pour satisfaire la bonne exécution du métier, car n’oublions pas : on est en DDD, c’est le métier qui dirige tout.

public interface IPersonRepository 

{

    IEnumerable<Person> GetAllPersons() ;

}

Maintenant, passons au niveau du code d’accès aux données : créons un projet (Class Library) Learning.DataAccess.csproj, et ajoutons une référence de Learning.Business à ce projet. Remarquons que c’est le projet métier qui est ajouté comme référence (c’est l’inverse de ce qui se passe si on ne fait pas d’inversion de contrôle). En fait, le but c’est de pouvoir avoir une couche « métier » aussi indépendant que possible. Comme cela, si les autres couches évoluent, le métier ne sera pas impacté. A l’inverse, si le métier évolue – et comme c’est la couche qui dirige tout – les couches environnantes doivent changer. Dans ce projet, ajoutons une class PersonRepository qui implémente (vous l’avez deviné 😉 ) l’interface IPersonRepository.

public class PersonRepository : IPersonRepository

{

    IEnumerable<Person> GetAllPersons()

   {

        return (from p in DataContext.PersonsSet select p).ToList();

   }

}

Ne vous souciez pas du code « return … » c’est juste un exemple. Ici, on veut récupérer une liste de personnes à partir de la base de données, et on pourra passer par exemple par un DataContext (Entity Framework), mais on peut bien utiliser de l’ADO.Net pur, ou du NHibernate ou tout ce que vous voulez… Jusqu’ici, nous avons un code d’accès aux données qui respecte bien le contrat imposé par IPersonRepository, donc un code qui permet de récupérer une liste de personnes comme le métier l’a voulu.

Imaginons maintenant que la liste de personnes sera affichée dans une page Web. Comme je fais du MVC4 (ASP.net) actuellement, c’est ce qui me passe premièrement en tête. Mais l’IoC ne se limite pas à ce genre d’application, car le principe reste le  même. Enfin bref,… créons une application ASP.Net MVC4, et nommons ce projet Learning.Web.csproj. Ajoutons comme référence à ce projet le projet métier Learning.Business. Encore une fois, rappelons-le : on fait toujours référence au projet métier (partout ou presque partout). Créons ensuite un Controller MVC qui s’appelle PersonController.

public class PersonController : IController

{

     public ActionResult List()

     {

           // Ici on devra faire appel à un objet IPersonRepository pour ramener une liste de personnes

      }

}

 

J’ai mis exprès un commentaire dans la méthode List() : on a besoin d’une instance de IPersonRepository pour pouvoir lister les personnes. Mais comment pourrions-nous faire une instance de IPersonRepository ? Nous ne pouvons pas écrire IPersonRepository toto = new IPersonRepository() car IPersonRepository est une interface et non une classe. Nous ne pouvons non plus écrire IPersonRepository = new PersonRepository(), car on n’a pas de référence au projet Learning.DataAccess et une utilisation directe de Learning.DataAccess va court-circuiter la couche métier, et on perd toute notion d’architecture en couches, ainsi que la centralisation au niveau « métier ».

C’est là que viennent participer les moteurs d’injection de dépendance (Windsor Castle, Unity, NInject, etc.). Un moteur de DI (Dependency Injection) va faire en sorte de résoudre pour nous l’instance de IPersonRepository sans qu’on fasse appel à une instanciation bête et méchant de PersonRepository.

Supposons que nous utilisons Windsor Castle. Il existe plusieurs façons (d’autres moins élégantes mais plus faciles à comprendre, et d’autres plus élégantes mais une peu compliquées) de résoudre une instanciation.  Ce que je vous conseille, c’est de visiter le site de Castle Project, et de se conformer aux bonnes pratiques indiquées dans ce site. Ce qui va suivre est une illustration , qui peut ne pas s’adapter dans toutes les situations :

Ajoutons alors une référence au moteur Castle (ce sera des DLL à télécharger sur le site de Castle Project), dans notre projet Learning.Web.

Ensuite, ouvrons le fichier Global.asax.cs (pour un projet ASP.Net, Global.asax contient les codes d’initialisation et de suivi d’une demande Http), et mettons dans la méthode Application_Start() le code qui initialise notre « injection de dépendance »…

public void Application_Start()

{

     WindsorContainer Container = new WindsorContainer();

     Assembly assImpl = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + @ »bin\Learning.DataAccess.dll »);

     Assembly assContracts = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + @ »bin\Learning.Business.dll »);

     foreach (Type Timpl in assImpl.GetTypes())
    {
         foreach (Type Tcontr in assContracts.GetTypes())
        {
               if (Timpl.GetInterfaces().Contains(Tcontr))
              {
                     Container.Register(Component.For(Tcontr).ImplementedBy(Timpl).LifeStyle.PerThread);
              }
       }
   }

}

😀 Je vous l’accorde, le code ci-dessus est un peu tordu (a priori). Mais au final, ce qui y est écrit veut dire : pour chaque « interface » défini dans l’assembly Learning.Business.dll, nous demandons au moteur Windsor (l’objet Container) de trouver son implémentation dans l’assembly Learning.DataAccess.dll. Si plus tard, notre couche d’accès aux données change : il suffit de modifier le nom de cet assembly puis de recompiler. Ou mieux, on peut le mettre dans le fichier de configuration (web.config ou app.config)… Remarquez ensuite le code Container.Register(…) : car c’est ceci qui permet à Windsor de mémoriser quelque part l’implémentation pour une interface donnée.

Enfin, revenons à notre PersonController. Comme dit plus haut; ce qui nous manque, c’est un code qui instancie un IPersonRepository et qui fait appel ensuite à sa méthode GetAllPersons(). Mais comme nous avons une référence vers Windsor Castle, nous allons juste utiliser l’objet WindsorContainer pour réaliser notre instanciation. Autrement dit, à la place de la ligne de commentaire (voir plus haut), nous aurons le code suivant :

var container = new WindsorContainer();
IPersonRepository personRep = container.Resolve<IPersonRepository>();

return View(« PersonsList », personRep.GetAllPersons());

Ce qui est important dans ce code : c’est l’appel à la méthode Resolve(…) : nous disons au moteur d’IoC de « résoudre » à notre place l’instanciation de  l’interface IPersonRepository. Et comme en amont, nous lui avons déjà dit de chercher l’implémentation là où il devra le faire : notre objet personRep sera bel et bien une instance de PersonRepository (défini dans Learning.DataAccess); alors que nous ne faisons même pas référence à cette couche.

===

Pour terminer, je tiens juste à noter que les moteurs de DI, entre autres Windsor Castle, peuvent effectuer des manipulations beaucoup plus « magiques » et « impressionantes » en dehors des notions classiques de Register – Resolve… Si vous aimez aller plus loin, vous vous aventurerez bien sûr dans les notions de Component, Factory, FactoryFactory, etc. 🙂

Catégories :Microsoft .Net, Pattern