Page Controller (Contrôleur de pages)

Changer de vue:
ScriptFree
Page Controller (Contrôleur de pages)

Contexte

Vous avez décidé d'utiliser le modèle Model-View-Controller (MVC) pour séparer les composants de l'interface utilisateur de la logique métier dans votre application Web dynamique. L'application que vous développez construit des pages Web de manière dynamique mais la navigation entre les pages est principalement statique.

Probleme

Comment structurer au mieux le contrôleur pour des applications Web moyennement complexes de manière à pouvoir réutiliser des éléments et à conserver une certaine souplesse tout en évitant de dupliquer le code ?

Facteurs à prendre en compte

Les facteurs suivants agissent sur le système dans le contexte présent et doivent être pris en compte dans la recherche de la solution au problème :

  • Le modèle MVC se concentre essentiellement sur la séparation du modèle et de la vue et délaisse quelque peu le contrôleur. Dans les scénarios d'applications clientes riches, la séparation du contrôleur et de la vue est moins critique et souvent mise de côté [Fowler03]. En revanche, dans une application cliente légère, cette séparation est inhérente à l'application car la présentation s'effectue dans un navigateur client alors que le contrôleur fait partie de l'application côté serveur. Le contrôleur nécessite donc un examen plus approfondi.
  • Dans les applications Web dynamiques, plusieurs actions utilisateur peuvent conduire à différentes logiques contrôleur, suivies par la même présentation de page. Ainsi, dans une application de messagerie Web simple, l'envoi tout comme la suppression d'un message de la boîte de réception renvoient l'utilisateur à la page de la boîte de réception (actualisée). Bien que la même page soit retournée après l'une des deux activités, l'application doit exécuter une action différente, suivant la page qui précédait et le bouton sur lequel l'utilisateur a cliqué.
  • Le code qui génère la plupart des pages Web dynamiques implique des étapes similaires : vérification de l'authentification de l'utilisateur, extraction des paramètres de la page de la chaîne de requête ou des champs de formulaire, collecte des informations de session, extraction des données d'une source de données, rendu des parties dynamiques de la page et ajout des entêtes et pieds de pages appropriés. Ceci peut donc entraîner la duplication d'une grande quantité de code.
  • Si la création de pages de serveurs rédigées en script (telles que ASP.NET) peut être aisée, elle peut également introduire un certain nombre d'inconvénients à mesure que l'application prend de l'ampleur. Avec ce type de pages, la séparation entre le contrôleur et la vue est très faible ce qui réduit les opportunités de réutilisation du code. Si plusieurs actions conduisent à la même page, par exemple, il sera difficile de réutiliser le code d'affichage entre plusieurs contrôleurs car il est étroitement lié au code du contrôleur. Les pages de serveur rédigées en script qui intercalent logique métier et logique de présentation sont également plus difficiles à tester et à déboguer. Le développement de pages serveur rédigées en script nécessite donc à la fois des compétences de développement de logique métier et des compétences de graphisme pour rendre les pages HTML visuellement efficaces et attractives. Et il est bien rare qu'une même personne possède cette double compétence. Si l'on tient compte de ces considérations, on comprendra qu'il convient de réduire le code des pages de serveur rédigées en script pour mieux développer une logique métier dans des classes bien réelles.
  • Comme nous l'avons évoqué avec le modèle MVC, l'évaluation du code de l'interface utilisateur est généralement chronophage et fastidieux. En séparant le code spécifique de l'interface de la logique métier réelle, vous facilitez le test de la logique métier et sa répétabilité. Et ceci s'applique non seulement à la partie présentation d'une application mais aussi à sa partie contrôleur.
  • L'apparence et la navigation communes tendent à améliorer la convivialité et la reconnaissance de la marque d'une application Web. Cette apparence commune peut cependant conduire à une répétition du code de présentation, particulièrement si le code est incorporé dans des pages de serveur rédigées en script. Vous avez donc besoin d'un mécanisme vous permettant d'améliorer la réutilisation de la logique de présentation d'une page à l'autre.

Solution

Utilisez le modèle Page Controller (Contrôleur de page) pour accepter l'entrée de requête de page, appeler les actions demandées sur le modèle, puis déterminer l'affichage approprié à utiliser pour la page résultante. Séparez la logique de distribution de tout code associé à la vue. Si possible, créez une classe de base commune pour l'ensemble des contrôleurs de page afin d'éviter la duplication du code et pour augmenter la cohérence et la testabilité. La figure 1 illustre la relation du contrôleur de page avec le modèle et la vue.

20031127-Des-Page-Controller-1.gif

Figure 1 : Structure de Page Controller

Le contrôleur de page reçoit une demande de page, extrait toutes données pertinentes, appelle les mises à jour du modèle et transfère la requête à la vue. La vue dépend donc du modèle pour l'extraction des données à afficher. Le fait de définir un contrôleur de page distinct isole le modèle des spécificités de la requête Web, comme la gestion de la session, ou l'utilisation de chaînes de requête ou de champs masqués de formulaire pour transmettre les paramètres à la page. En simplifiant, on pourrait dire que vous créez un contrôleur pour chaque lien de l'application Web. Les contrôleurs restent ainsi simples car vous n'avez qu'à vous occuper d'une action à la fois.

La création d'un contrôleur distinct pour chaque page Web (ou action) peut conduire à une forte duplication du code. C'est pour cette raison que vous devez créer une classe BaseController qui intègre les fonctions communes, telles que la validation des paramètres (voir figure 2). Chaque contrôleur individuel de page peut hériter de cette fonctionnalité courante issue de BaseController. Outre l'héritage de cette classe commune de base, vous pouvez également définir un ensemble de classes d'assistants que les contrôleurs peuvent appeler pour effectuer des fonctions courantes.

20031127-Des-Page-Controller-2.gif

Figure 2 : Utilisation de BaseController pour éliminer le code en double

Cette approche fonctionne bien si toutes les pages sont similaires et si vous pouvez placer les fonctions communes dans une seule classe de base. Plus il y a de variations d'une page à l'autre, plus vous devrez utiliser de niveaux dans l'arborescence d'héritage. Disons que toutes les pages analysent les paramètres mais que seules les pages qui affichent des listes extraient les données de la base de données alors que les pages qui nécessitent une entrée de données mettent à jour le modèle au lieu d'extraire les données. Vous pouvez maintenant introduire deux nouvelles classes de base, ListController et DataEntryController qui héritent toutes deux de BaseController. Les pages de liste peuvent ensuite hériter de ListController et les pages d'entrée de données de DataEntryController. Bien que cette approche fonctionne correctement dans cet exemple relativement simple, l'arborescence des héritages peut prendre de l'ampleur et devenir complexe avec des applications métiers bien réelles. Vous pouvez être tenté d'ajouter une logique conditionnelle aux classes de base pour pouvoir prendre en compte certaines variantes, mais un tel ajout violerait le principe d'encapsulation et les classes de base constitueraient un goulet d'étranglement à chaque modification du système. Vous devez donc envisager d'utiliser des classes d'assistants ou le modèle Front Controller dès lors que votre application devient plus complexe.

L'utilisation d'un contrôleur de page pour une application Web est un besoin tellement courant que toutes les infrastructures d'application Web en fournissent une implémentation par défaut. La plupart des infrastructures intègrent un contrôleur de page sous la forme d'une page de serveur (comme ASP, JSP et PHP). Ces pages de serveur associent en fait les fonctions de vue et de contrôleur et ne fournissent pas la séparation souhaitée entre le code de présentation et le code du contrôleur. Malheureusement, certaines infrastructures facilitent l'association entre le code relatif à la vue et celui lié au contrôleur et rendent en revanche plus difficile la séparation de la logique de contrôleur. C'est pour cette raison que l'approche Page Controller a si mauvaise réputation auprès de nombreux développeurs. Cependant, nombreux sont les développeurs qui associent Page Controller à une mauvaise conception et Front Controller à une bonne conception. Cette perception résulte en fait d'un (mauvais) choix d'implémentation spécifique et Page Controller tout comme Front Controller sont des choix architecturaux parfaitement viables.

Il est donc préférable de séparer la logique du contrôleur en classes distinctes, pouvant être appelées depuis la page de serveur. L'infrastructure de page ASP.NET fournit un excellent mécanisme permettant d'obtenir cette séparation : il s'agit des classes code-behind.

Variantes

Dans la plupart des cas, le contrôleur de page dépend des spécificités d'une demande Web en HTTP. Par conséquent, le code du contrôleur de page contient généralement des références aux entêtes HTTP, des chaînes de requête, des champs de formulaire, des requêtes de formulaire de fractionnement, etc. On comprend donc que le test de ce code hors de l'infrastructure de l'application Web est particulièrement difficile. La seule option qui existe est de tester le contrôleur en simulant des requêtes HTTP et en analysant les résultats. Ce type de test est à la fois chronophage et sujet à erreurs. Pour améliorer la testabilité, vous pouvez par conséquent séparer le code dépendant du Web du code indépendant du Web en deux classes distinctes (voir figure 3).

20031127-Des-Page-Controller-3.gif

Figure 3 : Séparation du code dépendant du Web du code indépendant du Web

Dans cet exemple, AspNetController encapsule toutes les dépendances sur l'infrastructure de l'application (ASP.NET). Cette classe peut, par exemple, extraire tous les paramètres entrant issus d'une requête Web et les transmettre à la classe BaseController de manière indépendante par rapport à l'interface Web (dans une collection par exemple). Cette approche ne se contente pas d'améliorer la testabilité, elle vous permet de réutiliser le code du contrôleur avec d'autres interfaces utilisateur, dans une interface client riche par exemple ou avec un langage script personnalisé.

La contre-partie de cette approche est la surcharge qu'elle induit. Vous avez maintenant une classe supplémentaire et chaque requête doit être traduite avant d'être satisfaite. Vous devez par conséquent conserver la partie spécifique de l'environnement du contrôleur aussi petite que possible et étudier les compromis entre dépendances réduites et développement et exécution plus efficaces.

Contexte final

L'utilisation du modèle Page Controller présente un certain nombre d'avantages et d'inconvénients.

Avantages
  • Simplicité. Comme chaque page Web dynamique est traitée par un contrôleur spécifique, la portée des contrôleurs est donc limitée et ils peuvent rester simples. Comme chaque contrôleur de page ne traite qu'une seule page, le modèle Page Controller est particulièrement bien adapté aux applications dont la navigation est simple.
  • Fonctions d'infrastructure intégrées. Le contrôleur est déjà intégré sous sa forme la plus basique dans la plupart des plates-formes d'application Web. Par exemple, si un utilisateur clique sur une page Web qui conduit à une page dynamique générée par un script ASP.NET, le serveur Web analyse l'URL associée à ce lien et exécute la page ASP.NET associée. En fait, la page ASP.NET est le contrôleur de l'action entreprise par l'utilisateur. L'infrastructure de page ASP.NET fournit en outre des classes code-behind pour exécuter le code du contrôleur. Ces classes assurent une meilleure séparation entre le contrôleur et la vue et vous permettent de créer une classe de base de contrôleur qui inclut une fonctionnalité commune à tous les contrôleurs Pour obtenir un exemple, reportez-vous à l'article Implémentation de Page Controller dans ASP.NET.
  • Meilleure réutilisation. Le fait de créer une classe de base de contrôleur réduit la duplication du code et vous permet de réutiliser du code commun d'un contrôleur de page à l'autre. Vous pouvez réutiliser ce code en implémentant une logique récurrente dans la classe de base. Tous les objets Page Controller concrets héritent alors automatiquement de cette logique. Si l'implémentation de la logique varie d'une page à l'autre, vous pouvez toujours utiliser une méthode Template et implémenter la structure d'exécution de base dans la classe de base. L'implémentation de sous-étapes spécifiques peut varier d'une page à l'autre.
  • Extensibilité. Vous pouvez étendre un contrôleur de page relativement aisément à l'aide des classes d'assistants. Si la logique au sein du contrôleur devient trop complexe, vous pouvez déléguer une partie de la logique aux classes d'assistants. Ces classes d'assistants fournissent par ailleurs un autre mécanisme de réutilisation, outre l'héritage.
  • Séparation des responsabilités du développeur. Avec une classe Page Controller, les responsabilités entre les membres de l'équipe de développement peuvent être aisément réparties. Le développeur du contrôleur doit être familiarisé avec le modèle du domaine et avec la logique métier implémentée. De son côté, le concepteur de la vue peut se concentrer sur le style de présentation des résultats.
Inconvénients

Du fait de sa simplicité, Page Controller est l'implémentation par défaut de la majorité des applications dynamiques Web. Vous devez cependant être conscient des limitations suivantes :

  • Un contrôleur par page. La principale contrainte du modèle Page Controller réside dans le fait que vous créez un contrôleur pour chaque page Web. Ceci fonctionne correctement pour les applications disposant d'un jeu statique de pages et d'un chemin de navigation simple. Pour les applications plus complexes, une configuration dyanmique des pages et des mappages de navigation entre les pages s'avèrent nécessaires. Le fait d'étendre cette logique à plusieurs contrôleurs de page pourrait rendre la maintenance de l'application plus difficile, même si une partie de la logique peut être transférée dans un contrôleur de base. En outre, les fonctionnalités intégrées de l'infrastructure Web peuvent limiter la souplesse dont vous disposez pour nommer les URL et les chemins de ressources (même si vous pouvez compenser une partie de cette perte de souplesse par des mécanismes de bas niveau tels que les filtres ISAPI). Dans ces scénarios, pensez à utiliser Front Controller, qui intercepte toutes les requêtes Web et transfère la requête au gestionnaire approprié, sur la base de règles configurables.
  • Arborescences d'héritage complexes. Il semble que l'héritage soit l'une des fonctionnalités les plus aimées et les plus haïes de la programmation orientée objet. L'utilisation de l'héritage seul pour réutiliser des fonctionnalités communes peut conduire à une hiérarchie d'héritage rigide. Pour plus de détails, reportez-vous à l'article Implémentation de Page Controller dans ASP.NET.
  • Dépendance de l'infrastructure Web. Dans sa forme basique, le contrôleur de page dépend de l'environnement de l'application Web et ne peut pas être testé indépendamment. Vous pouvez utiliser un mécanisme de wrapper pour dissocier la partie dépendante du Web, mais cette méthode nécessite un niveau supplémentaire d'indirection.

Considérations relatives au test

Comme Front Controller dépend des spécificités de l'infrastructure d'application Web (telles que les chaînes de requêtes et les entêtes HTTP), vous ne pouvez pas instancier et tester les classes du contrôleur en dehors de l'infrastructure Web. Si vous souhaitez exécuter un ensemble de tests d'unité automatisés sur la classe du contrôleur, vous devez démarrer le serveur d'application Web à chaque test. Vous devrez ensuite soumettre des requêtes HTTP dans un format qui exécute la fonction souhaitée. Une telle configuration introduit bon nombre de dépendances et d'effets secondaires dans le test. Pour améliorer la testabilité, envisagez de séparer la logique métier (y compris la logique du contrôleur à mesure qu'elle devient plus complexe) du code dépendant du Web.

Modèles connexes

Pour obtenir davantage d'informations, veuillez vous reporter aux modèles connexes suivants :

  • Intercepting Filter . Ce modèle constitue une alternative pour implémenter une fonctionnalité récurrente dans une application Web. L'infrastructure du serveur Web peut faire passer chaque requête par une chaîne configurable de filtre avant de les transférer au contrôleur. Les filtres traitent généralement les fonctions bas niveau telles que le décodage, l'authentification et la gestion des sessions alors que le modèle Page Controller gère les fonctionnalités de l'application. En outre, les filtres ne sont généralement pas spécifiques d'une page.
  • Front Controller . Ce modèle, est une alternative plus complexe mais également plus puissante au modèle Page Controller. Le Front Controller définit un seul contrôleur pour l'ensemble des requêtes de page, ce qui lui permet de prendre les décisions de navigation portant sur plusieurs pages.
  • Model-View-Controller. Page Controller est une variante d'implémentation de la partie contrôleur du modèle MVC.


Dernière mise à jour le jeudi 27 novembre 2003