Accueil > .Net, C#, Développement, Design Pattern > [C# Design Pattern] Injection de dépendances et Unity

[C# Design Pattern] Injection de dépendances et Unity

Aujourd’hui, on va parler un peu de Unity et d’injection de dépendances.

C’est un projet issue du projet open source Enterprise Library (EntLib de son petit nom).
Mais pour faciliter la gestion de l’open source, le projet a été refactoré en 10 projets différents, correspondant à 10 blocks logiques. Plus de détail sont disponibles ici : Microsoft Enterprise Library – Open Development Model.

Ainsi, Unity possède son propre repository sous Codeplex : patterns & practices – Unity.
Unity possède également sa propre documentation sur la MSDN : Unity Container.
Enfin, Unity est disponible via NuGet : Unity 3.0.1304.1 (il y a d’autres versions dispos en bas de page).

Dans le cas de ce billet, la version utilisée est la version 2.1.505.2.

 

Injection de dépendances

 

L’injection de dépendances est assez simple à comprendre.

Prenons un exemple simple :

public class UserService
{
    public User GetUserById(int id)
    {
        UserRepository repo = new UserRepository();
        return repo.GetUserById(id);
    }

    public List<User> GetAllUsers()
    {
        UserRepository repo = new UserRepository();
        return repo.GetAllUsers();
    }
}

On peut donc voir que UserService dépend fortement de la classe UserRepository.
Autrement dit, la classe UserService dépend fortement de l’implémentation de UserRepository : nous avons donc un couplage fort entre ces deux classes.
Selon les principes SOLID, on contrevient ici au dernier : Dependency Inversion Principle, à savoir qu’un module ne doit pas dépendre d’un module sous-jacent.

Comment faire pour régler ce problème ?
L’injection de dépendances est là pour nous.
Concrètement, on va passer par une interface qui va servir de contrat pour tous les repositories concernés.

public interface IUserRepository
{
    User GetUserById(int id);

    List<User> GetAllUsers();
}

Concernant notre service, on va le modifier ainsi :

public class UserService
{
    private IUserRepository _repository;

    public UserService(IUserRepository repository)
    {
        _repository = repository;
    }

    public User GetUserById(int id)
    {
        return _repository.GetUserById(id);
    }

    public List<User> GetAllUsers()
    {
        return _repository.GetAllUsers();
    }
}

Ce n’est pas bien compliqué, non ?

Du coup, en utilisation, on pourra avoir ceci :

UserService service = new UserService(new UserRepository());
service.GetAllUsers().ForEach(Console.WriteLine);

UserService serviceBDD = new UserService(new UserRepositoryBdd());
serviceBDD.GetAllUsers().ForEach(Console.WriteLine);

UserService serviceTests = new UserService(new UserRepositoryTests());
serviceTests.GetAllUsers().ForEach(Console.WriteLine);

J’utilise donc trois repositories différents, allant chercher des données dans trois sources différentes (ou pas).
Je pourrais donc avoir un résultat comme ceci :

Id: 1, Login: Login #1
Id: 2, Login: Login #2
Id: 3, Login: Login #3
Id: 4, Login: Login #4
Id: 5, Login: Login #5
Id: 1, Login: Login BDD #1
Id: 2, Login: Login BDD #2
Id: 1, Login: Login Tests #1
Id: 2, Login: Login Tests #2
Id: 3, Login: Login Tests #3
Appuyez sur une touche pour continuer…

Comme on peut le voir, Dependency Inversion Principle et injection de dépendances permettent très facilement de changer l’implémentation des classes sans pour autant devoir changer celles qui les consomment.

 

Unity

 

Nous en arrivons maintenant à Unity.

Unity réalise plusieurs choses, mais dans le cadre de ce billet, nous n’allons voir que l’injection de dépendances.
Pour plus d’information, je conseille d’aller voir les liens en première partie.

Pour débuter, il faut déjà référencer les différentes interfaces et classes concrètes.
Le principe est assez simple : on ajoute tout notre mapping dans un conteneur pour ensuite l’utiliser.
Le conteneur en question est le Microsoft.Practices.Unity.UnityContainer.
On fait appel, principalement, à deux méthodes : RegisterType pour associer une classe à une interface et Resolve pour récupérer une classe déjà instanciée.

Ce qui nous permet d’écrire ceci (il y a d’autres signatures avec prises en compte des génériques) :

using(UnityContainer container = new UnityContainer())
{
    container.RegisterType(typeof (IUserRepository), typeof (UserRepository));

    IUserRepository repostory = container.Resolve<IUserRepository>();
    repostory.GetAllUsers().ForEach(Console.WriteLine);
}

Le problème va donc être de différencier nos classes.
Unity prévoit le cas assez simplement en associant des noms :

using(UnityContainer container = new UnityContainer())
{
    container.RegisterType(typeof(IUserRepository),
        typeof(UserRepository), "RepoBase");
    container.RegisterType(typeof(IUserRepository),
        typeof(UserRepositoryBdd), "Bdd");
    container.RegisterType(typeof(IUserRepository),
        typeof(UserRepositoryTests), "Tests");

    IUserRepository repoBase = container.Resolve<IUserRepository>("RepoBase");
    repoBase.GetAllUsers().ForEach(Console.WriteLine);

    IUserRepository bdd = container.Resolve<IUserRepository>("Bdd");
    bdd.GetAllUsers().ForEach(Console.WriteLine);

    IUserRepository tests = container.Resolve<IUserRepository>("Tests");
    tests.GetAllUsers().ForEach(Console.WriteLine);
}

Ça reste quand même très simple.
Mais…cette forme implique que le mapping se fait "en dur".
Donc, si on l’écrit dans une couche métier (ou assimilable), ce sera quand même peu aisé de changer les repositories pour les tests.

C’est là qu’arrive la configuration via fichier de configuration (App.Config ou Web.Config) !
Il faut donc référencer Microsoft.Practices.Unity.Configuration et System.Configuration.

Pour obtenir la même chose :

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
  </configSections>

  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <container>
      <register
        type="UnitySample.IUserRepository, UnitySample"
        mapTo="UnitySample.UserRepository, UnitySample"
        name="RepoBase"/>
      <register
        type="UnitySample.IUserRepository, UnitySample"
        mapTo="UnitySample.UserRepositoryBdd, UnitySample"
        name="Bdd"/>
      <register
        type="UnitySample.IUserRepository, UnitySample"
        mapTo="UnitySample.UserRepositoryTests, UnitySample"
        name="Tests"/>
    </container>
  </unity>
</configuration>

Pour l’utilisation, il y a une petite variante :

using(UnityContainer container = new UnityContainer())
{
    var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
    section.Configure(container);

    IUserRepository repoBase = container.Resolve<IUserRepository>("RepoBase");
    repoBase.GetAllUsers().ForEach(Console.WriteLine);

    IUserRepository bdd = container.Resolve<IUserRepository>("Bdd");
    bdd.GetAllUsers().ForEach(Console.WriteLine);

    IUserRepository tests = container.Resolve<IUserRepository>("Tests");
    tests.GetAllUsers().ForEach(Console.WriteLine);
}

Là, on commence à avoir un truc sympa qui permet de changer les repositories au besoin.

 

Pour aller plus loin…

 

Dans les exemples de ce billet, les repositories sont toujours appelés dans un bloc using pour avoir le conteneur (UnityContainer) sous la main.
Mais ce n’est pas utile.
Ainsi, on peut très bien utiliser une classe qui charge les repositories une seule fois et les exposent sous forme de propriétés.

Par exemple :

    public class RepoList
    {
        public IUserRepository UserRepository { get; private set; }

        public RepoList()
        {
            using (UnityContainer container = new UnityContainer())
            {
                var section = (UnityConfigurationSection) ConfigurationManager.GetSection("unity");
                section.Configure(container);

                UserRepository = container.Resolve<IUserRepository>("RepoBase");
            }
        }
    }

Ce qui nous donnerait, à l’utilisation :

RepoList myRepositories = new RepoList();
myRepositories.UserRepository.GetAllUsers().ForEach(Console.WriteLine);

Et en poussant encore le vice…

    public class RepoList
    {
        public IUserRepository UserRepository { get; private set; }

        public RepoList(UnityContainer container)
        {
            using (container)
            {
                UserRepository = container.Resolve<IUserRepository>();
            }
        }

        public RepoList()
            : this(GetDefaultContainer())
        {
            
        }

        private static UnityContainer GetDefaultContainer()
        {
            var container = new UnityContainer();
            container.RegisterType(typeof(IUserRepository), typeof(UserRepository));
            return container;
        }
    }

Avec, dans le projet de tests :

public static UnityContainer GetTestContainer()
{
    var container = new UnityContainer();
    container.RegisterType(typeof (IUserRepository), typeof (UserRepositoryTests));
    return container;
}
RepoList prodRepository = new RepoList();
prodRepository.UserRepository.GetAllUsers().ForEach(Console.WriteLine);

RepoList testsRepository = new RepoList(GetTestContainer());
testsRepository.UserRepository.GetAllUsers().ForEach(Console.WriteLine);

 

Conclusion

 

Comme nous avons pu le voir ici, il est possible de faire plein de choses sympas avec l’injection de dépendances.
D’une part, cela facilite grandement la maintenance et l’évolution du code, mais aussi les tests unitaires.
C’est donc aussi un bon moyen d’avoir du code plus testable et donc plus fiable.
Trois raisons qui font que l’injection de dépendances, c’est bien, c’est beau. Il faut donc pratiquer !

Après, Unity est très bon dans le domaine, assez flexible pour pouvoir couvrir les besoins tout en restant assez aisé à manipuler et prendre en main.
Bien sûr, il fait d’autres choses, il est possible de le complexifier à loisir, mais…pourquoi faire compliquer quand on peut faire simple ???

Pour le moment, j’utilise Unity sur un seul projet.
J’ai un objet qui expose les différents repositories (une variante des derniers morceaux de code).
Honnêtement, il est très efficace et c’est un vrai plaisir de l’utiliser.
Par contre, il faut bien se poser AVANT de développer pour prévoir l’injection de dépendances.
En effet, si le code n’est pas prévu dans cette optique, ça devient vite un calvaire. Mais si c’est moche pour l’injection de dépendances, il y a de très grosses chances que ça devienne aussi un calvaire pour la maintenance corrective ou évolutive. Ce qui implique généralement des coûts qui ne cessent d’augmenter au fil de la vie de l’application (verrues sur verrues sur rustines…).

  1. Pas encore de commentaire.
  1. 29/12/2013 à 12:31

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

Suivre

Recevez les nouvelles publications par mail.

Rejoignez 55 autres abonnés

%d bloggers like this: