Développer (… et maintenir) une application Web à moindre coût – 1/2

Introduction

Dans le développement d’applications, le premier réflexe est d’estimer le coût de réalisation. Ce coût est, dans l’immense majorité des cas, une contrainte déterminante dans le choix de la solution (architecture, choix de logiciel, affinage du périmètre, …).

L’analyse le cycle de vie d’une application montre que l’intégration et la maintenance sont les deux principaux postes de coût. Ils représentent à eux deux 75% du total.

Mon approche consiste donc à minimiser ces postes, sans pour autant impacter significativement le coût initial. En effet pour un projet au cout réel de 2000j, il y aura eu en moyenne 100j de développements et 1360j d’intégration et maintenance. Donc toute méthode permettant de diminuer les coûts ne serait-ce que de 10% (-136j), quitte à doubler les coûts de développement (100j) est économiquement rentable sur le long terme.

Une architecture modulaire (https://en.wikipedia.org/wiki/Component-based_software_engineering) consiste à définir de manière précise chaque composant d’une solution et leurs interactions.  Il en résulte un meilleur travail d’équipe (parallélisassions des taches), une évolutivité plus facile (nouvelle fonctionnalité = nouveau composant), une maintenance plus simple (tout changement d’implémentation d’un composant reste transparent pour les autres), …

Le couplage maîtrisé entre les modules dans ce type d’architecture est éligible à la diminution des coûts d’intégration et de maintenance d’un logiciel. L’objectif de cet article va donc être de présenter comment développer une application web modulaire.

 

Structure modulaire d’une application

La modularité s’applique à tous les niveaux, nous ne parlerons pas ici du niveau urbanisation et nous allons directement nous intéresser au niveau logiciel.

Une application est classiquement divisée en trois couches, un composant n’appartenant qu’à une seule couche :

  • la couche Provider: qui contient l’ensemble des composants fournissant des services (IHM, services web, abonnement un flux de données, …),
  • la couche Business: qui contient l’ensemble des composants implémentant la logique métier de l’application,
  • la couche Consumer: qui contient l’ensemble des composants consommant des services (systèmes de persistance, services web, …).

 

L’approche composant permet de facilement faire évoluer les fonctionnalités d’une application. L’adjonction de plugins permet d’ajouter un ensemble de composants définissant une ou plusieurs fonctionnalités et pouvant se baser sur les composants déjà en place.

Eclipse, Atlassian (Jira, Confluence), Nuxeo, Glassfish sont des exemples de logiciels implémentant ce patron d’architecture.

 

 

 

La gestion des IHM dans le cas d’application modulaire peut être vue de deux manières :

  • soit elles sont considérées comme faisant partie de la couche Provider,
  • soit elles font parties d’une application à part entière (approche front-end/back-end).

Si on intègre les notions de multi devices ou d’API, il semble plus cohérent de se tourner vers l’approche front-end/back-end et de voir l’application comme un fournisseur de services.

La partie front-end se base sur ces services pour la définition de l’IHM et la partie back-end se présente alors comme un composant du SI.

Choix des technologies

La définition d’une solution passe par trois questions : quoi ? comment ? avec quoi ? Si l’introduction a poser la première question et le paragraphe précédent a répondu à la seconde, il nous reste à définir les outils qui vont nous permettre de développer une application web modulaire.

L’architecture orientée composant doit faciliter les échanges entre composants par l’explicitation de leur contrat d’interface. Pour assurer une réutilisabilité et une maintenance plus facile, on se rapporte systématiquement à des spécifications, lorsque celle-ci existent.

Communication Front-end/Back-end

Dans l’approche front-end/back-end, ce n’est pas une mais deux applications que nous devons mettre en place. Ce sont, en définitive, deux composants de notre SI dont il convient de définir le protocole de communication.

En client léger, le front est hébergé par un navigateur, le langage est le JavaScript et les communications se font via l’API XMLHttpRequest spécifiée par le W3C. La mise en place d’une architecture REST semble s’imposer d’elle-même de par son utilisation du protocole HTTP et son identification des ressources par URI.

Technologie back-end

Toute technologie permettant de faire de la programmation orienté composant (POC) est éligible pour la réalisation de l’application back-end.

D’un point de vue théorique, tous les langages objet conviennent. Des frameworks existent, permettant de faciliter l’explicitation des contrats d’interfaces et la communication entre les composants comme par exemple Spring pour Java ou MEF et Unity pour .Net. Mais ils n’imposent pas le respect de l’approche composant. Dans la durée, à cause de contraintes projet ou simplement par inadvertance, des raccourcis peuvent être pris. Au final, la maintenance et l’évolutivité seront aussi couteuses que dans une architecture qui n’est pas à base de composants.

À ce jour seule la technologie OSGI impose une approche composant et en définit les spécifications assurant une interopérabilité entre les différentes implémentations. Cette technologie, si elle ne s’impose pas, est à privilégier.

 

Un principe de fonctionnement d’une application web modulaire, coté back-end, consiste à disposer d’un conteneur pour un contexte web particulier dans lequel les composants pourraient déposer leurs ressources : service REST, filtre, ressource statique, … Le conteneur fait alors l’union de l’ensemble de ces ressources et les présente de manière unifiée sous le même URL.

L’ajout d’un plugin permet d’ajouter de nouvelles pages et/ou de nouveaux services REST à l’application.

En plus des avantages déjà évoqués en termes de cout de maintenance, le déploiement s’en trouve simplifié puisqu’on ne livre plus l’application entière mais seulement les plugins.

Pour ce qui est de la continuité de service, pour des opérations de maintenance, on peut également assurer le fonctionnement du reste du site pendant le retrait ou la mise à jour d’un plugin.

 

En pratique avec la technologie OSGI, on définit simplement un service REST en utilisant les annotations JAX-RS.

Puis on les expose sous forme de services (OSGI) d’un bundle (composant OSGI), via blueprint (Specs. R6 Entreprise §121)

Technologie Front-end

Nous avons déjà défini que nous nous plaçons dans le cas de client léger dans un navigateur Web. Les langages utilisés sont donc : HTML, CSS et JavaScript. Mais il existe pléthore de frameworks. Il convient de faire une sélection. Pour cela utilisons l’approche composant. En effet l’application front-end est tout d’abord une application qui possède les couches Provider, Business et Consumer.

Comment définir les composants de la couche Provider

Pour la couche Provider, qui va gérer l’IHM proprement dite, la notion de composant est intrinsèque puisqu’une page peut être considérée comme un composant composite. C’est-à-dire elle-même composé de composants graphiques. Il nous faut donc définir des composants graphiques, en HTML cela revient à définir des balises spécifiques dont les attributs, les propriétés et les méthodes sont spécifiés et en constituent le contrat d’interface.

L’exemple ci-dessus montre la réutilisabilité et le caractère protecteur des composants. Il est possible de les composer pour définir de nouveaux composants et ils sont développés et testés de manière autonome facilitant les aspects de maintenance.

Mais nous n’avons toujours pas répondu à la question : quel framework utiliser pour développer ces balises spécifiques ? Là encore, beaucoup d’élus. J’ai testé les suivants : Angular, Knockout, Vue.js, React et tous fonctionnent parfaitement. Par contre, si on essaye de mélanger des balises développées avec un framework avec les balises d’un autre framework, dans ce cas, plus aucun de ces frameworks ne fonctionnent. Et c’est là que le bât blesse. Dans une architecture orientée composant, les communications se basent sur les contrats d’interface indépendamment des implémentations.

Cet aspect est crucial pour la maintenabilité de nos applications. Prenons le cas d’une application contenant un millier d’écrans et développée avec, par exemple, l’Angular de 2016 et projetons-nous dans cinq ans. L’expérience démontre que la version de 2022 sera probablement incompatible avec vos développements. Comment faire évoluer votre application ? Il ne reste que trois choix :

  • continuer à développer dans une version obsolète, cette solution alourdira votre dette technique
  • redévelopper avec un nouveau framework avec l’ensemble des coûts afférents (migration des pages à faire évoluer)
  • mettre à jour l’ensemble de vos pages avec la nouvelle version, ce qui même à concurrence de 2h par page nous amène à des coûts exorbitants si le nombre de pages est important

Aucune de ses solutions n’est satisfaisante, il nous faut donc éliminer l’approche par balises spécifiques gérés par un seul framework.

Heureusement, le W3C a défini une spécification pour les customs elements, et cette spécification est supportée par l’ensemble des navigateurs (via la mise en place de polyfills pour certains d’entre eux). Ce qui va dans le sens des règles énoncées en début de ce chapitre, à savoir, « on se rapporte systématiquement à des spécifications lorsque celle-ci existent ».

La définition de ces customs elements est extrêmement simple par le chainage du prototype HTMLElement.

C’est dans l’implémentation des fonctions connectedCallback, disconnectedCallback et attributeChangedCallback, de l’interface HTMLElement, que vont pouvoir intervenir les frameworks pour assurer le binding Javascript/HTML. On s’assure alors de l’interopérabilité de nos customs elements, qu’ils soient développés ou non en utilisant le même framework.

 

Voici un exemple d’implémentation (TypeScript) en utilisant Vue.js.

C’est dans l’implémentation de connectedCallback, appelée lorsque la balise est insérée dans le DOM, que l’on utilise Vue.js.

On notera l’utilisation d’un template externe pour respecter le patron de « separation of concerns » entre la vue et le vue-modèle. (Vue.js étant un framework MVVM).

Cette approche peut être utilisée avec tous les frameworks ayant une architecture modulaire, je l’ai testée également avec Knockout et DHTMLX.

Les recherches que j’ai pu faire me permettent d’affirmer que React est également éligible, et dans une moindre mesure AngularJS. Le paragraphe suivant nous en expliquera les raisons.

 

 

Auparavant, en conclusion intermédiaire, l’utilisation des customs elements (W3C), nous assure l’indépendance vis-à-vis des frameworks ce qui nous permet :

  • d’ajouter des fonctionnalités à nos applications en utilisant un nouveau framework,
  • de faire migrer progressivement l’existant (un custom element à la fois), évitant ainsi l’effet big-bang.

Éligibilité des frameworks JavaScript

Dans le paragraphe précédent, j’ai énoncé que tous les frameworks modulaires sont éligibles. Il convient de détailler ce critère.

La gestion d’un site en JavaScript nécessite la mise en place de plusieurs fonctionnalité, le binding HTML/JavaScript énoncé dans le paragraphe précédent n’en est qu’un parmi d’autre.  Il faut également gérer : le routage, les clients des services REST, un outil de publish/subscribe, …

Dans une architecture modulaire, un framework peut implémenter plusieurs fonctionnalités mais il ne doit pas dépendre de sa propre implémentation et pouvoir utiliser celle d’un autre composant. Prenons l’exemple du routage, cette fonctionnalité est utilisée pour la navigation dans les SPA (Single Page Application).

Avant toute chose, comme pour les customs elements, il convient de définir le contrat d’interface de cette fonctionnalité.

L’interface Router, en TypeScript, présentée ci-dessous en est un exemple:

 

À partir de là, tout framework voulant exposer la fonctionnalité de router doit respecter cette interface (un simple wrapper permet une adaptation facile) et tout framework voulant utiliser cette fonctionnalité doit utiliser cette interface.

Donc si une framework ne peut pas isoler ces fonctionnalités, il n’est donc pas modulaire, il ne peut donc pas être éligible. EmberJs en est un très bon exemple, ce framework se base sur son propre router et il gère également les fonctionnalités de binding. Les deux fonctionnalités sont indissociables, ce framework n’est donc pas modulaire, il n’est pas éligible selon nos critères sans grever fortement la maintenabilité de notre application.

Dans le paragraphe précédent, j’ai émis des réserves quant à l’éligibilité d’AngularJS. En pratique, il est possible d’isoler sa fonctionnalité de binding, mais l’effort est très important et dégrade fortement l’intérêt du framework.

Du JavaScript, oui mais quel JavaScript ?

Jusqu’à présent nous avons parlé de framework JavaScript, mais il existe plusieurs versions de JavaScript, laquelle choisir ?

L’ECMAScript 5 est en fin de vie mais c’est le seul langage entièrement supporté par tous les navigateurs. Pour les versions ECMA201x, on peut alors utiliser des outils de type Babel lors du build pour assurer le transpilage vers l’ECMAScript5. Cela nous permet de développer dans la dernière version (ECMA2017 à ce jour) et d’attendre que les navigateurs supportent cette version pour supprimer cette étape de build.

Une autre solution est d’utiliser le TypeScript.

Dans un premier temps, j’étais contre l’utilisation de ce langage. Développer en TypeScript pour générer du JavaScript me semble être une hérésie, pourquoi ne pas directement développer en JavaScript ? Le transpilage ne peut apporter que des problèmes pour arriver à générer ce que l’on veut vraiment obtenir. Sur ce point, la suite vous montrera que j’avais raison.

Mais si on prend un peu de recul, il se peut que dans 5 ans, on en soit à l’ECMA2022 et le code produit aujourd’hui deviendra obsolète. Il sera alors surement possible de transpiler notre TypeScript vers cette nouvelle mouture de JavaScript. Cet argument l’emportant sur ma première réticence et la découverte des decorators (utile pour la productivité) m’ont fait pencher pour l’adoption de TypeScript.

Dans cette partie, nous avons vu la théorie permettant de déterminer une architecture modulaire et les critères d’éligibilité des technologies. Dans le deuxième partie, nous nous intéresseront au packaging et au déploiement de la partie front-end, qui se révèlent des plus complexes.

Leave a Reply

Your email address will not be published. Required fields are marked *