Activité : Inversion des dépendances, inversion de contrôle, injection de dépendances et framework (partie 1/2)

Consigne

Le but de cette activité est de vous familiariser avec les notions d’inversion des dépendances, d’inversion de contrôle et d’injection de dépendances et de framework d’application en réalisant une première application à l’aide du framework Javalin.

Pour ce travail on vous demande de prendre connaissance de la situation et de réaliser les tâches proposées. Si vous ne parvenez pas à terminer le travail en classe, il vous appartient de prendre sur votre temps pour l’achever.

Le code des tests unitaires doit être utilisé tel quel et ne doit pas être modifié. Le code des classes doit être conforme à la spécification fournie sous forme de tests unitaires et de diagramme UML.

Le travail est individuel. Vous pouvez communiquer, mais en respectant le code d’honneur.

Situation

Vous êtes développeur dans un projet de gestion de contacts. Vous recevez le mandat de réaliser une interface web. Pour cela, vous devez réaliser un serveur HTTP. Votre supérieur vous suggère d’utiliser le framework Javalin dont il a récemment entendu parler.

Objectifs

À la fin de ce travail, vous devez :

  1. Connaître le principe d’inversion des dépendances
  2. Connaître les notions d’inversion de contrôle (IoC) et d’injection de dépendances.
  3. Connaître la différence entre inversion de contrôle et injection de dépendances
  4. Connaître la notion de framework d’application
  5. Connaître la différence entre framework et bibliothèque
  6. Être capable d’expliquer ce qu’est un framework d’application.

Résultat attendu

  • Un projet Maven contenant les classes décrites
  • Un petit document où vous aurez décrit ce que vous avez fait et les difficultés que vous aurez rencontrées.

Ressources

Logiciel :

  • maven
  • Visual Studio Code

Documents :

Mise en route

Rendez-vous dans le répertoire de vos projets, lancez la commande ci-après et lorsque le système vous le demande, saisissez votre nom et votre mot de passe I-FR.

1
git clone https://gitlab.epai-ict.ch/m226/contact-webapp.git --single-branch

Déplacez-vous dans le répertoire contact-webapp et lancer la commande code . pour ouvrir le projet dans VSC (Visual Studio Code).

Lancez le projet en exécutant la tâche run (commande Tasks : Run Task ou Ctrl-B), ouvrez un navigateur à l’URL suivante : http://localhost:8080/ et que la page contient le texte “Not found”.

Principe d’inversion dépendance

Le principe d’inversion des dépendances (dependency inversion principle, le D des principes SOLID) est un principe de conception qui stipule que :

  1. Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d’abstractions.
  2. Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre des abstractions.

Ce principe devrait être respecté, quelles que soient les circonstances. Il ne s’applique pas à un design ou à un patron de conception en particulier, mais à la programmation orientée objet en général.

Bibliothèque, framework et inversion de contrôle

Dans le contexte de la programmation orienté objet, une bibliothèque (library, attention ce sont des faux amis) peut être définie comme étant simplement une collection ensemble de classes. Un langage de programmation de haut niveau s’accompagne toujours d’une bibliothèque standard qui fournit les services de base nécessaires à la réalisation d’un programme (entrée/sortie, système fichier, fonctions mathématiques, etc.). En Java, il s’agit de la bibliothèque de classes de Java (Java Class Library).

Selon cette définition, un framework est également une bibliothèque. Pour déterminer si une bibliothèque est un framework, ce qui importe c’est la manière dont communiquent le code du programme et le code de bibliothèque. Dans tous les cas, le code du programme appelle le code de la bibliothèque. Un framework est une bibliothèque qui appelle en retour le code l’application, il y a une inversion de contrôle.

Il n’y a pas de définition précise de l’inversion de contrôle et cela ne désigne pas une technique particulière. Il s’agit d’un principe commun à tous les frameworks, mais il existe plusieurs manières de le mettre en oeuvre. Les différentes formes d’injection de dépendances en sont des exemples.

Framework d’application

Il existe framework de toutes sortes. On en trouve notamment pour la réalisation de tests unitaires (JUnit, Jasmin, etc.), pour la persistance des données (JPI, Hibernate, Entity Framework, etc.) et, bien sûr, pour la réalisation d’application. Dans tous les cas, le framework prend en charge l’essentiel des aspects techniques, ce qui permet aux programmeurs de se concentrer sur les aspects métier.

Pour faire court, un framework d’application est un framework qui dispose d’une classe qui représente une application et qui en fournit une implémentation de base. La réalisation d’une application consiste alors, pour l’essentielle, à étendre d’une manière ou d’une autre l’application de base fournie par le framework. Cela peut se faire à l’aide de l’héritage ou par tout autre moyen.

Un framework d’application est généralement spécialisé, il ne permet pas de réaliser n’importe quelle application. Celui que nous allons utiliser, Javalin, permet de réaliser des serveurs HTTP.

Injection de dépendances

L’injection de dépendances désigne l’un des moyens de réaliser l’inversion de contrôle. Même si l’injection de dépendance peut prendre des formes très élaborées (annotations, containers IoC, fichiers de configurations, etc.), elle peut être également très simple. Par exemple, lorsque l’on passe le concrete builder au director dans patron de conception «builder», on injecte dans le director un objet dont il dépend (le concrete builder). Il s’agit d’une injection de dépendance par paramètre. Une autre manière aurait été d’injecter le builder à l’aide du constructeur.

Lorsqu’on réalise une injection de dépendance, il est important de respecter le principe d’inversion des dépendances, mais cela ne va pas de soi. Il est tout à fait possible de réaliser une injection de dépendances qui fonctionne et qui ne respecte pas le principe.

Tâches

Tester le fonctionnement de l’application de base

Lancez le projet et assurez-vous qu’il démarre sans erreur. Si c’est le cas, vous devriez voir une bannière similaire à celle qui se trouve ci-après. Si vous ouvrez l’URL http://localhost:8080/ avec votre navigateur vous devriez vous apparaître immédiatement une page blanche avec le texte “Not found”. Si c’est le cas, le serveur HTTP remplit bien sa fonction : il répond aux requêtes du navigateur (même si en l’état la seule réponse qu’il est en mesure de fournir est “404 Not Found”).

1
2
3
4
5
6
7
8
9
10
11
12
13
[main] INFO org.eclipse.jetty.util.log - Logging initialized @242ms to org.eclipse.jetty.util.log.Slf4jLog
[main] INFO io.javalin.Javalin - 
        __                      __ _
        / /____ _ _   __ ____ _ / /(_)____
    __  / // __ `/| | / // __ `// // // __ \
    / /_/ // /_/ / | |/ // /_/ // // // / / /
    \____/ \__,_/  |___/ \__,_//_//_//_/ /_/

        https://javalin.io/documentation

[main] INFO io.javalin.Javalin - Starting Javalin ...
[main] INFO io.javalin.Javalin - Listening on http://localhost:8080/
[main] INFO io.javalin.Javalin - Javalin started in 218ms \o/

Observez le code de votre projet et répondez aux questions suivantes :

  • Peut-on créer directement une instance de la classe Javalin ?
  • Si non pourquoi (que dit le compilateur) ?
  • Quel est le nom du patron de conception mis en oeuvre pour permettre la création d’une instance ?

Modifier le comportement de l’application

La classe Javalin définit un certain nombre de méthodes qui permettent de modifier le comportement du serveur HTTP. Ajoutez les deux ligne suivantes avant l’appel de la méthode start.

1
2
    app.enableDebugLogging();
    app.enableStaticFiles("./public", Location.EXTERNAL);

La première indique au serveur de loguer le traitement des requêtes pour faciliter le débogage et la seconde lui indique qu’il doit servir les fichiers qui se trouve dans le sous-répertoire public du répertoire de travail (le répertoire depuis lequel a été lancé le programme). Dans notre cas, il s’agit du répertoire du projet.

Relancez le projet puis suivez une nouvelle fois l’URL http://localhost:8080/ et observez ce qui apparait dans la fenêtre du terminal. Allez maintenant à l’URL http://localhost:8080/poetry.html.

  • D’où vient le document HTML qui s’affiche ?
  • Combien de requêtes ont été envoyées au serveur HTTP pour afficher cette page ?

Les méthode enableDebugLogging et enableStaticFiles modifient bien le comportement de l’application, mais il s’agit de fonctionnalités prédéfinies. Ces méthodes n’apportent pas plus de souplesse qu’un fichier de configuration. Un framework doit permettre d’étendre les fonctionnalités de l’application de base.

Gestionnaires de ressources HelloWorld

Le rôle d’un serveur HTTP est de répondre aux requêtes HTTP qui lui sont envoyées sur le port 80. Une requête HTTP se compose essentiellement d’une méthode (GET, POST, PUT, DELETE, etc.) et d’un identifiant de ressource (URL ou Uniform Ressource Locator) sur laquelle la méthode doit être appliquée.

Une URL se compose d’un protocole (“http://”), du nom d’un hôte et d’un port (“localhost:8080”) et d’un chemin (“/poetry.html”). Le chemin n’est pas un chemin dans le système de fichier, mais le nom la ressource. Le chemin peut correspondre au chemin d’un fichier, mais c’est un cas particulier.

Lorsque le serveur HTTP reçoit une requête, il doit extraire le nom de la ressource et exécuter une procédure correspondant à cette ressource dont le rôle est de construire une réponse qui contient une représentation de la ressource. Cette représentation peut être, le cas échéant, le contenu d’un fichier (.html, .css, etc.), mais elle pourrait également être un document (html, xml, json, etc.) produit à partir d’une base de données ou du résultat d’un calcul. La fonction enableStaticFiles active la création d’une réponse à partir du contenu d’un fichier.

Pour traiter une requête pour une ressource dynamique, on doit :

  • réaliser une procédure traitement de la ressource (ressource handler)
  • associer la procédure à un chemin (le nom de la ressource) et à une méthode (p. ex. GET),
  • passer la procédure au framework pour qu’il puisse l’appeler.

Pour cela, Javalin définit une interface Handler qui définit une unique méthode handle. Pour réaliser une procédure de traitement, on commence par créer une classe qui implémente cette interface. On crée ensuite une instance de cette classe (un handler) que l’on passe à l’objet app à l’aide de la méthode addHandler qui permet d’associer le handler à une méthode et un chemin.

Pour illustrer cela, commencez par créer une nouvelle classe HelloWorldHandler avec le code ci-après.

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
package ch.epai.ict.m226.webapp;

import io.javalin.Context;
import io.javalin.Handler;

public class HelloWorldHandler implements Handler {

    @Override
    public void handle(Context ctx) throws Exception {
        StringBuilder sb  = new  StringBuilder();
        sb.append("<html>");
        sb.append("<head>");
        sb.append("<title>contact</title>");
        sb.append("</head>");
        sb.append("<body>");
        sb.append("<h1>Hello World !</h1>");
        for (int i = 0; i < 10; i += 1) {
            sb.append(String.format("<p>%d. Hello World !</p>", i));
        }
        sb.append("</body>");
        sb.append("</html>");

        ctx.html(sb.toString());
    }
}

Ajoutez ensuite la ligne suivante avant l’appel de la méthode start dans la procédure main.

1
    app.addHandler(HandlerType.GET, "/hello-world", new HelloWorldHandler());

Compilez l’application et naviguez vers l’URL suivant : http://localhost:8080/hello-world.

Gestionnaires de ressources Contact

Si vous n’avez pas terminé le projet contact-manager, terminez-le puis ajoutez un package ch.epai.ict.m226.contact_manager dans le projet contact-webapp et copiez toutes les classes du projet contact-manager.

Ajoutez ensuite un gestionnaire de ressource (handler) pour afficher la liste de personnes à l’URL http://localhost:8080/contacts en utilisant le package ch.epai.ict.m226.contact_manager.