Les nouvelles technologies au service de l’architecture?

L’objectif du stage est très exactement le suivant : Il s’agit de l’identification de microservices à partir des applications monolithiques. Ce sujet de recherche consiste en l’utilisation de plusieurs outils et algorithmes ciblés qui permettront, à partir d’une architecture dite monolithique, d’identifier une possible décomposition de cette architecture en ensembles de microservices qui permettront alors de faciliter la transition d’une architecture vers l’autre.

Les concepts de monolithes et microservices sont explicités dans la description du travail proposé, qui décrit les enjeux de chacun et les raisons pour lesquelles une telle transition d’architecture est souhaitée pour une entreprise ou autre infrastructure.

J’ai également pu appréhender le fonctionnement d’une structure de recherche d’une université durant ce stage, qui comporte certains avantages et inconvénients qui seront détaillés tout au long du rapport.

Structure d’accueil Le stage s’est déroulé dans le bâtiment du département informatique de l’Université du Québec à Montréal, lieu similaire à Polytech Nice Sophia, où l’on y retrouve des salles de cours, ainsi que des bureaux pour les enseignants/chercheurs. Nous avions à disposition un bureau réservé aux membres de l’équipe du maître de stage (généralement les personnes qui sont sous la responsabilité de Mme. Moha pour ma part). Mais par manque de confort et de luminosité dans le bureau, nous avons préféré nous installer dans un espace de l’université qui ouvert à tous, lieu équivalent au Learning Centre de Polytech.

Lorsqu’elles étaient nécessaires, des réunions étaient organisées de façon ponctuelle dans des salles où nous pouvions détailler nos avancées, débattre sur certains choix de conception/développement et s’organiser pour la suite du stage. Les membres de la réunion étaient principalement la maître de stage (Mme. Moha), et les autres étudiants concernés par mon sujet de stage : Rafik (master) et Manel (doctorante).

Concernant la structure du service, je me retrouvais donc dans la faculté des sciences de l’UQAM, qui appartient à la structure générale de l’université. L’organigramme complet du fonctionnement de l’UQAM est fourni en annexe. (voir : Organigramme de la direction et des services de l’UQAM ) 3) Plan du rapport Il sera décrit dans les chapitres suivants les différentes problématiques qui ont donné naissance au sujet de stage qui m’a été proposé, qui tente de répondre à ces problématiques-là.

Je détaillerai ensuite les étapes qui m’ont permis de mettre en place cet outil d’identification de microservices, en explicitant les enjeux principaux et les difficultés rencontrées.

Description du travail proposé

1) Diversité importante des architectures dans les projets informatiques

Les tout premiers projets informatiques se voulaient être petits, simples et ne comportaient que peu d’exigences. Au fil des années, ces exigences et besoins ont évolué, ce qui a pour résultat de faire grossir les projets informatiques, tout en les complexifiant. Ces conditions ont donné naissances à des applications dites « monolithiques ».

Une application monolithique est une application logicielle à un seul niveau dans laquelle l’interface utilisateur et le code d’accès aux données sont combinés en un seul programme à partir d’une seule plate-forme.

Une application réalisée de la sorte entraîne un ralentissement de son développement, dans la mesure où tous les éléments sont vraisemblablement liés et interdépendants, ce qui empêche d’ajouter de nouvelles fonctionnalités sans avoir à modifier des éléments déjà présents.

La vitesse à laquelle nous pouvons mesurer l’efficacité de l’application (tests et déploiements) est elle aussi impactée par ce type d’architecture, étant donné que l’architecture se veut lourde et difficilement portable.

Peu à peu, les architectures des applications ont été divisées en plusieurs couches différentes (couche de présentation, couche de logique métier, couche d’accès aux données) : Les architectures trois tiers. Cela a permis de grandement faciliter le développement d’une application monolithique mais conservait certains de ses désavantages, comme la portabilité, le déploiement et le temps de compilation.

La problématique principale réside dans la présence d’un monolithe, qu’il faudra découper afin de ne plus avoir les contraintes et désavantages qui lui sont associés. C’est alors qu’intervient l’introduction des architectures orientées services (SOA).

Il s’agit d’un ensemble de services (un service est une action exécutée par un « fournisseur » à l’intention d’un « client ») qui proposent chacun une fonctionnalité particulière et indépendante les unes des autres, découpant ainsi une application monolithique en une application composée de divers services.

Etant donné que les services sont peu couplés entre eux, il est donc assez simple de les déployer de façon indépendante et de les maintenir efficacement. La gestion de projet au sein d’une équipe de travail est également facilitée pour ces mêmes raisons. Plus récemment encore, un autre concept basé sur des patrons de conceptions et des pratiques plus adaptées aux contraintes du moment (déploiement quotidien, interconnexion entre les applications internes et externes, relation client multi-canal, démocratisation du DevOps) s’est peu à peu imposé dans l’écosystème des solutions informatiques : Il s’agit des microservices.

Il ne s’agit bien entendu pas du concept parfait et possède lui aussi ses qualités et défauts, mais de nombreuses infrastructures tendent à modifier leur architecture pour passer du monolithique vers les microservices.

2) Des changements d’architectures nécessaires pour concorder avec les nouvelles technologies La pratique technique qui tend à se populariser de plus en plus aujourd’hui dans le monde de l’informatique est le DevOps (unification du développement logiciel -dev et de l’administration des infrastructures informatiques -ops). Il s’agit d’une approche qui privilégie l’étroite collaboration entre les équipes de développement (dev) et d’exploitation (ops) pour toute solution informatique.

Par ce rapprochement, le but visé est d’améliorer la qualité du travail et la relation entre ces deux équipes qui ont chacun leur vision pour atteindre la satisfaction du client – livrer du nouveau code sur demande et maintenir la disponibilité des services.

L’approche DevOps cherche à améliorer l’efficacité et la vélocité des changements, en proposant des déploiements en continu tout en conservant la stabilité des environnements. Ceci s’obtient par l’automatisation dans tous les aspects, comme les tests et les déploiements des applications. Aussi, on privilégie le partage fluide des connaissances et des codes sources entre les deux équipes pour assurer le meilleur fonctionnement des environnements.

Comme expliqué précédemment, on constate que l’architecture la plus à même de permettre un processus de DevOps est l’architecture à base de microservices, étant donné que leurs avantages et objectifs sont similaires et étroitement liés. Un article paru dans The Wall Street Journal intitulé « Innovate or Die : The Rise of Microservices » (Innover ou mourir : l’avènement des microservices) explique que les logiciels sont devenus le principal élément différenciateur des entreprises dans tous les secteurs.

Les programmes monolithiques répandus dans de nombreuses entreprises ne peuvent pas être modifiés assez rapidement pour s’adapter aux nouvelles réalités et exigences en matière de concurrence, et c’est pourquoi une telle vague de transition vers les microservices existe actuellement.

Il suffit de regarder à l’heure actuelle le nombre de grands groupes qui ont opté pour ce type d’architecture. Des géants comme Amazon, Netflix, PayPal ou encore eBay utilisent des architectures à base de microservices, signe assez fort d’une conception qui se veut être à la mode.

Tous ces points-là font que l’on considère aujourd’hui les microservices comme étant une des architectures de références. 3) Une transition d’architecture ne peut être automatisée Malheureusement, il n’existe à l’heure actuelle aucun procédé automatique et efficace qui permette une transition instantanée d’une architecture monolithique vers un ensemble de microservices.

Il est nécessaire de fournir un travail important de réflexion et de conception pour parvenir à réaliser cette transition, étant donné qu’un projet informatique ne doive pas connaître de régressions en termes de fonctionnalités lorsque l’on restructure son architecture. De plus, le langage de programmation utilisé est susceptible de varier durant la transition, car chaque architecture possède ses normes et conventions.

Dans les cas des microservices par exemple, la communication entre différents microservices se réalise via des API REST ou SOAP (protocoles de communication) et contraignent donc à l’utilisation de certaines technologies. Pourtant, de nombreux projets rencontrent des phases de transition d’architecture. On peut constater que permettre de faciliter cette transition répondrait à une problématique présente actuellement qui se veut être récurrente.

L’identification de microservices à partir d’une application monolithique serait une solution permettant de faciliter ce processus et ainsi faire gagner un temps considérable. 4) Approche de la problématique dans le cadre d’une étude de recherche C’est pourquoi ce sujet est proposé dans le cadre d’une étude de recherche, où ce sont des heuristiques choisies et précises qui permettront de réaliser un outil capable d’approcher cette transition de structure dans un projet.

C’est donc ce projet en question qui m’a été proposé durant ce stage, où nous essayerons d’appliquer nos propres heuristiques afin de mettre cet outil en place. S’en suivra une étape de validation qui permettra de vérifier si l’approche choisie est efficace ou non pour réaliser l’identification de ces microservices. 5) Analyse des résultats obtenus et partage des connaissances acquises Dans le cas où le projet se voit être validé, il serait alors possible de proposer une publication d’article dans une des revues scientifiques basées sur le Génie Logiciel, domaine encadrant le sujet qui m’est proposé.

Si l’article est accepté, il sera alors possible de partager la « découverte » réalisée et permettre ainsi aux autres chercheurs de critiquer et analyser les résultats obtenus. De cette façon, le thème abordé pourra prendre de plus en plus d’envergure et obtenir plus de visibilité dans le domaine de la recherche, où d’autres personnes approfondiront ce même sujet et permettront ainsi de réaliser de nouvelles avancées.

Description du travail réalisé

1) Génération des données du « Knowledge Discovery Metamodel » (KDM) La première étape du sujet de recherche consistait à récolter le maximum d’informations sur un projet informatique donné (pour rappel, l’objectif est de permettre d’identifier des potentiels microservices à partir d’une application monolithique, le projet ici fera donc référence à un application monolithique quelle qu’elle soit) afin de pouvoir réaliser des traitements et des analyses sur ces informations (on peut parler ici de rétro-ingénierie).

Pour ce faire, il fallait être en mesure d’extraire un métamodèle du projet informatique. Un métamodèle est une description des différents modèles d’un projet : classes, objets, attributs, relations… et se décrit également lui-même. L’équipe de recherche avait déjà imposé son choix de représentation du métamodèle et je n’ai donc pas eu à réaliser d’état de l’art concernant cela, afin de sélectionner la meilleure représentation possible.

La représentation choisie est donc sous forme de « Knowledge Discovery Metamodel » (que l’on appellera KDM pour faciliter la lecture). Le KDM permet de représenter des systèmes de logiciels d’entreprise entiers, et pas uniquement le code source. Il s’agit d’une représentation à large spectre de la relation entité-entité pour décrire les logiciels existants.

Le concept clé de KDM est un conteneur : une entité qui possède d’autres entités. Cela permet au KDM de représenter les systèmes existants à différents degrés de granularité. Nous avons donc cherché un outil permettant, à partir d’un projet donné, de générer un KDM qui donnerait alors toutes les informations nécessaires à l’analyse de l’architecture monolithique.

MoDisco s’est présenté comme étant l’outil le plus adapté à la situation. Non seulement il permettait de générer un KDM à partir d’un projet, mais proposait également une API qui permettait par la suite de traiter le fichier KDM généré. Le point négatif quant à l’utilisation de cet outil résidait dans le fait qu’il n’est plus maintenu depuis Juin 2018, et un certain nombre de bugs ont été depuis découvert, ce qui fait qu’ils sont toujours présents dans la version actuelle. Malheureusement, un des bugs de l’outil MoDisco faisait que l’on ne pouvait pas générer le KDM de certains projets, à moins de repérer les fragments de code source problématiques, ce qui était particulièrement chronophage.

Une fois le KDM généré, nous étions alors capables d’appliquer une approche de rétro-ingénierie afin d’extraire les informations nécessaires à l’identification de potentiels microservices.

2) Heuristiques définies concernant l’identification de microservices Le découpage d’une architecture monolithique en microservices, résultant d’un processus de réflexion et de redécoupage du projet, ne peut être automatisé car il nécessite l’intervention du développeur qui doit faire des choix de conception et technologiques.

Il est en revanche possible d’aider le développeur concernant l’identification de microservices, en découvrant à partir d’un monolithe des potentiels microservices qui seraient cohérents d’un point de vue fonctionnel et structurel.

Pour ce faire, il était nécessaire de définir des heuristiques permettant un tel aboutissement. Par construction, un microservice se veut être fortement découplé d’un autre microservice (ou tout autre type d’architecture), c’est ce concept d’indépendance avec d’autres systèmes qui fait une des forces du microservice. Il fallait donc trouver un moyen de décomposer les classes d’un projet en microservices, en considérant que chaque microservice se doit d’être très peu couplé avec un autre.

Une représentation de cette problématique peut se faire sous forme d’un graphe, où chaque nœud représente une classe et chaque lien entre différents nœuds serait un lien entre deux classes. L’idée serait donc de trouver des ensembles de nœuds qui possèdent de nombreuses arêtes entre eux, mais très peu d’arêtes vers tous les autres ensembles de nœuds.

Voici une illustration permettant de mieux visualiser cette représentation : Figure 1 – Découpage d’un ensemble de nœuds en communautés Chaque couleur représente ici un ensemble de nœuds, soit un potentiel microservice. On peut observer que chaque microservice semble peu lié avec les autres (peu d’arêtes relient ces ensembles) tandis que la majorité des liens du graphe se retrouvent au sein même des microservices.

En plus de cette représentation, nous pouvons imaginer l’ajout d’un poids sur chacune des arêtes, ce qui influencera sur la formation des microservices. Pour notre cas, c’est cette idée de lien possédant un poids entre les nœuds que l’on va conserver, et qui nous permettra de réaliser cette identification de microservices. Nous avons donc défini deux types de liens : les liens logiques et les liens sémantiques. a. Liens logiques Nous appelons liens logiques les liens qui composent la structure d’une application au niveau du code source.

Par exemple, les appels d’une méthode entre deux classes est considéré comme étant un lien logique. De cette façon, nous avons distingué 5 types différents de liens logiques : les invocations de méthodes, les invocations de constructeurs, la contenance entre deux classes et enfin les notions d’héritage et d’implémentation entre deux classes. Nous avons également considéré un poids différent pour chacun des liens.

En effet, nous considérons que chaque type de lien possède une importance plus ou moins grande dans la formation d’un microservice, où un appel d’une méthode dans une classe est un type de lien bien moins important que la contenance d’une classe dans une autre par exemple.

Chaque classe possède donc un ensemble de liens logiques avec une autre classe, où chaque type de lien possède un poids différent. Lorsque l’on souhaite récupérer le lien logique entre deux classes, on réalise donc la somme de tous les poids des différents liens qui la composent.

Voici un exemple permettant de résumer le fonctionnement : Ici, les classes « OptionPanel » et « EmbeddedPanel » sont reliées par 3 types de liens logiques : ONEWAY (une classe est contenue dans l’autre) qui est présent une seule fois entre les deux classes, METHOD_INVOKE où l’on retrouve 11 appels de méthodes entre les deux classes et enfin CONSTRUCTOR_INVOKE où l’on a 5 appels de constructeur entre les deux classes.

Voici les poids associés à chacun des types de liens : OneWay: 10 Method Invoke: 1 Constructor Invoke: 2 Si nous souhaitons calculer le poids du lien logique entre les deux classes ci-dessus, il suffit alors d’additionner le tout en prenant en compte les poids de chaque type : POIDS LOGIQUE = 1 * 10 + 11 * 1 + 5 * 2 = 31 Le poids logique entre les classes OptionPanel et EmbeddedPanel est de 31. b. Liens sémantiques Nous avons d’un autre côté le lien sémantique, qui est un lien plus abstrait que le lien logique.

Nous avons choisi de nommer lien sémantique entre 2 classes la « cohérence » que toutes deux possèdent d’un point de vue du sens lexical. Le but est de vérifier si les différents mots que l’on extraits d’une classe (en décomposant le nom de la classe et le nom de toutes les méthodes de cette classe en différent mots) sont sémantiquement proches des mots extraits d’une deuxième classe.

Nous estimons que, étant donné qu’un développeur donne la plupart du temps des noms concrets et explicites aux classes/méthodes, il est possible de juger du fait de leur « sens » sémantique si deux classes sont potentiellement regroupable au sein d’un même microservice.

Pour les liens sémantiques, contrairement aux liens logiques, il n’est pas possible de déterminer un poids entre deux classes sans l’aide d’un outil. Nous avons donc réalisé un état de l’art concernant les divers outils d’analyse sémantique afin de sélectionner le plus adapté à notre situation.

Il existe des outils connus comme WS4J qui proposent des fonctionnalités permettant de donner une valeur pour le lien sémantique entre 2 mots seulement, mais le processus se complique lorsqu’il s’agit d’un ensemble de mots à comparer. L’outil nous fournit en résultat une matrice de valeurs qui est difficilement exploitable. Nous avons alors trouvé un outil qui se nomme « Disco », qui se trouve être complet, dans la mesure où le résultat sous forme de matrice est ici traité et donne donc des résultats exploitables. Il est de plus possible de traiter de différentes façons la matrice afin d’obtenir des résultats plus adaptés à la situation. Disco permet également de personnaliser le « dictionnaire » de mots sur lequel se base l’outil pour la reconnaissance sémantique, donnant donc la possibilité à l’utilisateur de fournir son propre dictionnaire, qui serait adapté à son contexte d’usage.

Les détails quant aux choix des traitements de matrice et les explications pour créer son propre dictionnaire sont fournis en annexe (voir : Détails sur l’utilisation de l’outil Disco). Les résultats donnés par l’outil varient entre 0 et 1 : 0 correspondant à une similarité sémantique quasi nulle et 1 pour les ensembles de mots qui sont identiques.

Voici un exemple d’utilisation de cet outil : Admettons que nous avons extrait de 3 classes différentes les mots suivants : Classe 1 (Book): “Book get page show author” Classe 2 (Redaction): “Redaction get publisher page show library” Classe 3 (Animal): “Animal get race pedigree owner” L’analyse sémantique entre les classes 1 et 2 donnent une similarité de 0.895 tandis que l’analyse entre les classes 2 et 3 donnent une similarité de 0.456 Ici encore, j’ai rencontré certains problèmes en utilisant l’outil Disco qui ne permettait pas un usage complet de ses fonctionnalités du fait de la présence d’erreurs inattendues.

Tout d’abord, la fonctionnalité permettant de réaliser l’analyse sémantique de 2 ensembles de mots comportait des erreurs sur les entrées d’utilisateur, où j’ai alors dû ajouter des vérifications sur la validité des paramètres donnés.

Une fois cela réglé, je me suis ensuite rendu compte que le programme sortait en erreur lorsqu’il rencontrait un mot « inconnu » par le dictionnaire lors de l’analyse sémantique d’un groupe de mots. Or un développeur est vraisemblablement parfois amené à utiliser des abréviations ou autres mots non répertoriés dans un dictionnaire de mots lorsqu’il développe, et je me suis vite rendu compte lors d’essais que de nombreux liens sémantiques n’ont pu être générés à cause de cela.

Le problème a donc été résolu en retirant d’un ensemble de mots les mots non reconnus, permettant ainsi une analyse sémantique des mots « compréhensibles » par le programme. Je me suis par la suite confronté à de nouvelles problématiques pour permettre à mon outil d’analyse de microservices d’utiliser la version que j’ai corrigée de Disco, où certaines contraintes de dépendances du code source de Disco vers mon projet se posaient. c. Choix des heuristiques Pour l’identification des microservices, nous essayons au travers des heuristiques choisies de conserver la logique structurelle du code source (avec les liens logiques) en regroupant les classes qui sont le plus reliées d’un point de vue implémentation, comme expliqué dans la partie a., où l’on privilégiera des liens forts (contenance de classe) plutôt que des liens plus faibles (appel de méthode).

Nous essayons également de conserver l’aspect logique métier définie par le développeur au travers des liens sémantiques, dans la mesure où chaque groupe de mots extrait d’une classe définit son fonctionnement d’un point de vue sémantique et donne alors des indications sur sa cohérence avec l’utilisation d’une autre classe.

3) Traitement des données du KDM a. Utilisation d’un parseur La meilleure façon de récolter les diverses informations générées dans un KDM était de créer un parseur adapté aux fichiers du KDM. Dans notre cas, l’extension de ces fichiers était en XMI, un dérivé du XML permettant de condenser les informations grâce à des métadonnées (description spécifique d’une donnée).

Comme expliqué précédemment, l’outil MoDisco propose une API permettant de récupérer les informations nécessaires du KDM. Une structure du parseur avec des petites fonctionnalités m’a été fourni, où l’on pouvait récupérer toutes les classes d’un projet, et les différents types de liens entre les classes : appels de méthode, appel du constructeur d’une autre classe, contenance d’une classe dans une autre, etc. Je me suis donc servi de cette base pour le compléter et récupérer via l’API de MoDisco tous les éléments nécessaires à la création de notre propre structure. b. Génération d’un arbre de structure Pour structurer les données récoltées par le KDM, nous nous sommes inspirés de l’exemple présenté dans la partie 2). Nous avons donc une structure de « graphe », qui contient un ensemble de classes (les nœuds du graphe) et chaque classe contient ses informations (nom de classe, nom des méthodes etc.) ainsi que tous les liens que le nœud en question possède avec un autre nœud.

On peut donc avoir pour un nœud donné toutes ses informations ainsi que ses liens (logiques et sémantiques) avec les autres nœuds du graphe. C’est à partir de cette structure que nous allons pouvoir commencer le processus de « clustering » (regroupement des classes), où nous pourrons alors identifier de potentiels microservices.

4) Clustering des groupes de classes Le point critique de ce sujet réside dans le choix de l’algorithme qui permettra de donner une identification de ces potentiels microservices.

Le choix était déjà spécifié dès mon arrivée sur le sujet de stage : Il s’agit de l’algorithme de Louvain.

C’est un procédé d’extraction de communautés (dans notre cas ce sont les microservices) applicable à de grands réseaux. Il s’agit d’un algorithme glouton avec une complexité temporelle en O(n / log n). L’algorithme de Louvain permet de s’assurer que le nombre et le poids des liens est plus important à l’intérieur des partitions (ensembles de noeufs) qu’entre les partitions.

Pour l’exprimer autrement, il faut que la densité intra-communautaire dépasse la densité inter-communautaire. Cet algorithme correspond parfaitement à nos besoins dans la mesure où c’est ce que l’on recherche dans la formation des microservices (cf. exemple en début de partie 2 : Heuristiques définies concernant l’identification de microservices). Il faut retenir que l’algorithme est applicable sur de grands réseaux, on pourrait donc imaginer que le résultat sera plutôt biaisé sur de petits réseaux.

L’architecture précédemment réalisée permettra donc de réunir les informations nécessaires à l’utilisation de cet algorithme de Louvain. L’objectif était donc de trouver une implémentation de cet algorithme afin de l’intégrer dans le travail actuel. Etant donné que cet algorithme s’utilise principalement dans le cadre d’études de recherches, je me suis confronté à certains problèmes. En effet, il n’existe que des projets indépendants proposés par des particuliers (étudiants, chercheurs) et j’ai alors dû m’assurer que l’utilisation de l’algorithme était viable, ou du moins utilisable.

Ce nouveau problème, qui s’ajoute à la liste des problèmes rencontrés durant mon stage, m’a permis de réaliser une différence notable entre le domaine de l’informatique en entreprise et en recherche. De nombreux outils utilisés dans le cadre de la recherche sont peu documentés ou comportent des défauts d’utilisation assez flagrants, et nous devons alors comprendre en profondeur l’outil et le modifier pour parvenir au résultat attendu initialement.

Cela diffère grandement des enseignements que l’on reçoit pour préparer une insertion dans le domaine de l’entreprise, où l’on doit s’assurer que les programmes fournis soient fonctionnels, testés, portables et déployables à grande échelle. Dans le domaine de la recherche, l’accent est plutôt mis sur la qualité de l’algorithme proposé sans s’assurer complètement de son utilisabilité, du moins par un autre utilisateur que le développeur de l’algorithme lui-même.

Après quelques recherches je suis alors tombé sur une implémentation de l’algorithme qui semblait assez populaire (le nombre de téléchargements était élevé) et qui avait à disposition une documentation assez explicite. Encore une fois, l’outil n’était en revanche plus maintenu depuis Juillet 2015, et donc toute potentielle erreur dans le projet devrait alors être corrigée manuellement. L’utilisation de ce projet m’a posé une nouvelle contrainte dans la mesure où il ne pouvait fonctionner correctement que sous un environnement Linux/Mac OS et non pas sur Windows.

J’ai alors dû changer d’environnement de travail pour pouvoir utiliser cet outil-là. Une nouvelle contrainte s’est posée quant à l’utilisation de l’algorithme de Louvain. Pour rappel, il permet à partir d’un ensemble de liens entre nœuds donnés, d’extraire des communautés qui sont le plus interdépendantes possible. L’entrée de l’algorithme prend donc en compte le lien entre les classes (l’identifiant des classes qui composent le lien), ainsi que le poids relatif au lien donné.

Ceci prend donc la forme suivante : « Identifiant de la classe 1 » « Identifiant de la classe 2 » « Poids du lien entre les 2 classes » Or, comme je l’ai précédemment décrit, nous avons défini 2 types de liens différents pour chaque lien entre classes avec chacun un poids différent. Il fallait donc être en mesure de réunir le poids des liens logiques et sémantiques en un seul et unique poids.

Il fallait être capable de pondérer ces deux poids, sachant que le poids des liens logiques peut varier de 0 à l’infini (si une classe possède une infinité de lien avec une autre classe, le poids du lien logique n’étant pas borné) tandis que le poids des liens sémantiques est borné entre 0 et 1. De nombreuses tentatives ont été réalisés afin de permettre une pondération des deux poids, mais aucune ne semblait satisfaisante : Soit les calculs ne permettaient pas une pondération réellement équilibrée, soit il n’y avait pas de cohérence dans les manières de pondérer les deux types de poids.

La solution finale a été de laisser à l’utilisateur de l’outil d’identification de microservices le choix de sa propre pondération, en spécifiant le coefficient pour le poids logique et de même pour le poids sémantique. Le poids résultant était donc le suivant : Poids final = (Coeff. logique * poids logique ) + (Coeff. sémantique * poids sémantique ) Une fois cette problématique résolue, nous étions alors en mesure depuis l’arbre de structure réalisé (décrit en partie 3) section b.) de fournir les entrées nécessaires au programme utilisant l’algorithme de Louvain.

L’implémentation de cet algorithme proposait divers résultats en sortie. En effet, le programme utilisait 10 différents types de « qualités » pour générer les clusters (un cluster est un ensemble regroupant les différentes communautés), chaque qualité utilisant une méthode particulière (je n’ai malheureusement pas réussi à saisir les différences fondamentales entre chacune des qualités).

En plus de cela, chaque qualité avait ses propres « niveaux » de clustering. Un niveau de clustering correspond en fait à la granularité de formation des communautés, c’est-à-dire que le niveau 0 va créer un cluster composé du nombre maximal de communautés (1 communauté par classe/node) et lorsque l’on augmente le niveau, la granularité s’affine et forme des communautés de plus en plus grande, composé de plusieurs nœuds et réduisant donc le nombre total de communautés dans le cluster.

Ainsi, si l’on souhaite obtenir le nombre le plus réduit possible de microservices lors d’un processus de clustering, il faut s’orienter sur le niveau le plus élevé d’une qualité donnée. Le programme implémentant l’algorithme de Louvain génère en sortie un fichier qui indique, pour chaque classe donnée, la communauté à laquelle elle appartient.

Il est très compliqué de s’imaginer une telle représentation en visualisant le fichier, c’est pourquoi l’usage d’une interface graphique était nécessaire à la bonne compréhension de la formation des différents clusters. 5) Visualisation des clusters obtenus L’idée initiale pour permettre une représentation visuelle des microservices identifiés était l’implémentation d’une interface graphique assez simple, basée sur JavaFX par exemple.

Après réflexion et discussion avec les membres de l’équipe de recherche, j’ai réalisé que poursuivre cette idée pouvait être une certaine perte de temps. En effet, me concentrer sur la réalisation d’une interface graphique en partant de zéro ne m’aurait pas apporté une valeur ajoutée importante sur mon sujet, cela sortant du cadre d’une étude de recherche.

Nous avons alors étudié certains outils ou librairies permettant de réaliser une représentation visuelle de façon simple et rapide, et il se trouve que ce sont les interfaces web qui proposent le plus d’outils de visualisation. Je me suis tout d’abord penché sur Neo4j, un système de gestion de base de données basée sur les graphes, car je pensais initialement qu’une visualisation sous forme de graphe pouvait être adaptée pour la représentation d’un cluster, à l’image de la figure proposée en exemple dans la partie 2) (voir : Figure 1 – Découpage d’un ensemble de nœuds en communautés). Je me suis assez rapidement ravisé car la sortie de l’algorithme de Louvain ne fournit que les communautés dans lesquelles se retrouvent les classes, sans qu’il n’y ait de lien : Une représentation sous forme de graphe n’est donc pas appropriée. Je me suis tout de même renseigné sur les librairies utilisées par Neo4j permettant de créer les graphes, car leur représentation visuelle me semblait à la fois simple et explicitait de façon efficace toutes les informations nécessaires.

Une de leur librairies, D3.js, permet de manipuler des données importantes et de les représenter de diverses manières. Je me suis donc penché sur les fonctionnalités proposées et je me suis effectivement aperçu qu’ils proposaient une pléthore de représentations différentes.

J’ai finalement trouvé une représentation nommée « Zoomable Circle Packing », qui semblait parfaitement appropriée à mes besoins : représenter de façon visuelle les différents microservices créés tout en ayant la possibilité d’accéder à plus de détails, comme le nom des classes composant un microservice par exemple. J’ai donc généré, à partir des fichiers de sortie de l’algorithme de Louvain, les nouveaux fichiers d’entrée nécessaires à l’utilisation de la librairie D3.js, afin de visualiser les différentes communautés qui ont pu être identifiées. Voici un exemple de communauté générée avec les détails sur les classes composant une communauté : Figure 2 – Visualisation d’un cluster généré Figure 3 – Visualisation d’une communauté avec détail de la classe 6) Automatisation de l’utilisation de l’outil d’identification Pour réaliser un scénario d’identification de microservices de bout en bout, il me fallait alors passer par 4 différentes étapes : La première qui était l’utilisation de mon parseur qui, à partir d’un fichier KDM, me donnait les fichiers d’entrée pour l’algorithme de Louvain. La seconde étape était donc l’utilisation de l’implémentation de cet algorithme, qui me générait à son tour le résultat de l’algorithme sous forme d’un fichier texte assez peu explicite en termes de visualisation.

Il me fallait donc passer par la troisième étape qui consistait à traiter ce fichier de sortie pour pouvoir créer un nouveau fichier d’entrée qui sera utilisable par la quatrième et dernière étape, l’implémentation de la librairie D3.js qui affichait enfin le résultat final.

La réalisation de ce scénario se voulait alors longue, dans la mesure où il fallait pour chaque programme le lancer avec les fichiers générés aux précédentes étapes, en plus d’ajouter plusieurs commandes afin de compiler les différents projets. Tout cela faisait perdre un temps considérable.

En tant qu’utilisateur, j’ai rapidement constaté qu’une telle utilisation se voulait assez lente et forcément peu agréable. Je me suis alors penché sur la possibilité d’automatiser au maximum ces différentes étapes. Après quelques recherches, j’ai constaté que l’automatisation d’un programme se réalisait principalement avec un langage de script.

Il fallait alors réaliser un choix quant au langage de script utilisé, dans la mesure ou les principaux langages de script sont « Cmd » pour Windows et « Shell » pour Linux/Mac OS. Il est d’usage dans le domaine de la recherche de réaliser des programmes sur des environnements Linux, principalement par souci de portabilité. J’ai alors commencé par réaliser des scripts Shell sur chacun des projets, où il me suffisait donc pour chacune des étapes de lancer le script afin de réduire le nombre de commandes à utiliser pour chacune des étapes.

Lorsque je mettais en place un nouveau script, je mettais à jour le ReadME GitHub de mon projet afin de guider les utilisateurs sur l’utilisation des scripts et des différents arguments à ajouter. Pour les utilisateurs n’étant pas sur un environnement Linux, les différentes commandes manuelles ont également été explicitées et détaillées afin de permettre une utilisabilité de l’outil sur plusieurs environnements différents.

Pour aller plus loin dans l’automatisation du fonctionnement de l’outil, j’ai réalisé un script qui permettait d’utiliser les autres scripts précédemment développés, et qui allait également chercher de façon automatique les nouveaux fichiers générés à chaque étape.

Ainsi, il suffit pour l’utilisateur de donner en entrée toutes les informations nécessaires au fonctionnement de l’outil de façon intégrale (chemins vers le fichier KDM, le fichier du dictionnaire pour l’analyse sémantique, coefficients pour les liens logiques et sémantiques etc.).

Cette automatisation permet également pour l’algorithme de Louvain de générer tous les résultats possibles, selon les qualités et différents niveaux générés. Enfin, un lien est proposé en fin d’exécution qui redirige vers l’interface graphique (implémentation de D3.js), où il est alors possible de sélectionner les différentes identifications des microservices, selon une qualité ou un niveau donné.

7) Projets de référence Afin de permettre la validité de l’outil réalisé, il faut être capable d’identifier de façon « manuelle » une décomposition d’un monolithe en microservices, à l’image de la refonte de l’architecture d’un projet en microservices que pourrait réaliser un groupe de projet ou n’importe quelle entreprise.

On obtiendrait après réflexion la découpe « idéale » d’un monolithe en microservices. On pourrait alors être en mesure de comparer la construction manuelle de l’architecture de microservices avec celle proposée par l’outil d’identification.

Pour être en mesure de réaliser une telle comparaison, il faut disposer d’une architecture monolithique volumineuse que l’on connaît de façon précise, afin de pouvoir réaliser une décomposition cohérente sur un grand projet, étant donné que l’outil fonctionne principalement pour les architectures de taille assez importante. Le choix a donc été réalisé par des membres de l’équipe, qui porte sur un projet développé durant plusieurs années qui correspond à un outil d’identification de patrons et d’antipatrons de conception.

Les chercheurs m’encadrant sur mon sujet ont pour certains une connaissance profonde du monolithe ainsi sélectionné, dans la mesure où ils ont participé à sa réalisation. En revanche un problème assez important s’est posé lorsque ce choix fut validé. Le projet en question est en réalité une composition de nombreux différents projets, ce qui posait un problème pour l’outil permettant de générer le fichier de KDM, qui ne peut traiter qu’un seul projet.

Le seul moyen de permettre l’analyse de l’ensemble des projets pour en générer le KDM était donc de déplacer tous les différents projets dans un seul et même projet (on appelle cela du refactoring). Il s’agissait d’un travail de longue haleine dans la mesure ou le projet (qui se nomme Ptidej) était composé de pas moins de 70 différents projets 2). Le processus de refactoring étant relativement long, il m’a alors été proposé de me baser sur un autre projet monolithique afin de mettre en place le même système de vérification des microservices identifiés.

Il s’agit d’un logiciel de gestion bibliographique libre dont le code source est disponible sur GitHub. Bien que nous n’ayons pas une connaissance précise du code proposé par cette application, il se trouve qu’il est très bien conçu et permet d’identifier de potentiels microservices assez facilement.

8) Comparaison des résultats Par manque de temps, le processus de comparaison n’a pu être réalisé pour les deux projets sélectionnées (JabRef et Ptidej). La réalisation des architectures à base de microservice pour ces deux projets monolithiques de référence nécessitait un nombre assez important de réunions, afin de mettre en commun les idées et avis de chacun pour réaliser la meilleure architecture possible.

Malheureusement des contraintes d’indisponibilités sur la fin du stage n’ont pas permis de réaliser assez de réunions pour permettre la mise au point de ces architectures. Celles-ci seront réalisées dans les semaines à venir et je serai informé de la cohérence ou non des résultats obtenus.

L’obtention d’un résultat satisfaisant aurait par ailleurs été assez long dans la mesure où l’outil d’identification que nous proposons comporte de nombreuses variations de paramètres, qui donneraient alors des résultats différents. En effet, comme il est possible de donner différents coefficients pour le calcul des poids logiques et sémantiques, il faudrait réaliser différents essais afin de déterminer quels coefficients donneraient satisfaction.

En plus de cela s’ajoute l’algorithme de Louvain qui propose différentes « qualités » de clustering, qui donne pour chaque qualité sélectionnée des résultats différents. Il aurait encore une fois fallu déterminer quel niveau de qualité donne des résultats probants. En plus de la réalisation de l’outil d’identification de microservices, j’ai contribué au fonctionnement d’un autre projet.

Celui-ci était déjà implémenté mais comportait certaines erreurs que j’ai dû corriger, afin de le rendre fonctionnel. Il s’agit de l’implémentation d’une métrique nommée TurboMQ (Turbo Modularization Quality) qui analyse un cluster donné et lui donne un score. Ce score est calculé en fonction des liens entre les nœuds : plus les communautés sont indépendantes entre elles, plus le score est élevé. Si c’est le cas, cela signifie que les microservices semblent être correctement formés et nous obtenons ainsi une indication de plus sur l’étape de validation de l’outil que nous réalisons.

Ce projet ne s’intègre donc pas au fonctionnement de l’outil d’identification des microservices, mais est extrêmement utile pour permettre de valider les résultats obtenus avec notre projet. 9) Conduite de projet Tout au long du projet, je me suis servi d’un journal de bord pour prendre note de chacune des informations importantes qui ont été proposées durant les réunions d’équipe.

Cela m’a permis de garder une trace des idées principales et de pouvoir les développer sur le journal de bord. Une fois que les objectifs étaient clairs et définis, j’ai alors pu tenir à jour un Kanban (système de tickets permettant d’y mettre les tâches à réaliser pour le projet) qui m’a alors donné plus de visibilité sur chacun des objectifs définis tout en permettant de laisser une trace du travail réalisé pour les personnes qui reprendront le projet.

La méthode de travail adoptée était similaire à une méthode Agile, où je définissais chaque semaine les objectifs à atteindre tout en m’adaptant au travail réalisé les semaines précédentes (en cas de retard ou d’avance sur les objectifs précédemment réalisés). Le code source réalisé sur l’outil d’identification est versionné sur GitHub, où j’ai pu associer les fonctionnalités réalisées sur l’outil avec les tâches créées sur le Kanban.

J’ai mis en place un flux de travail nommé Git Flow, ou je créais une nouvelle « branche » du projet à chaque ajout de fonctionnalité. J’ai également tenu à jour un ReadME qui correspond au manuel d’utilisation du projet, spécifiant quelles commandes permettent de faire fonctionner chaque partie du projet (rappelons que l’outil est subdivisé en 4 parties différentes).

J’ai également tenu une page Wiki du projet sur GitHub, où j’ajoutais des informations complémentaires comme des détails quant à l’utilisation de l’outil d’analyse sémantique (voir : Détails sur l’utilisation de l’outil Disco) ou encore d’autres détails sur l’utilisation de l’implémentation de l’algorithme de Louvain.

Dans le cas où le processus de validation de l’outil réalisé s’avère concluant, on pourrait affirmer que la méthode de Louvain est un algorithme qui permettrait d’identifier de façon efficace des microservices.

Cela démontrerait également que les heuristiques sélectionnées étaient les plus adaptées aux problématiques données. Le concept associé à cet outil possède selon moi un fort potentiel dans le domaine de l’entreprise, car il permettrait de guider les développeurs dans leur processus de restructuration d’une architecture, qui est comme nous l’avons constaté une problématique plus actuelle que jamais.

Durant ce stage, j’ai grandement gagné en autonomie dans la mesure où de nombreuses décisions assez critiques me revenaient. J’ai ainsi pu développer mon esprit critique afin de pouvoir peser le pour et le contre lors d’une prise de décision, tout en apprenant à prendre un certain recul dans les avancées réalisées.

Pour un tel projet de recherche, 10 semaines de travail ont finalement paru très courtes et il m’a fallu mettre en place une bonne organisation afin de pouvoir mener à bien le projet. Avec une conduite de projet assez rigoureuse et un investissement constant, j’estime que le projet réalisé durant mon stage sera assez facilement réutilisable par les membres de l’équipe de recherche, qui pourront alors avancer dans leurs travaux et permettre de contribuer à l’avancée de la recherche en informatique, dans le secteur du génie logiciel.

Dans de nombreux cas nous faisons une distinction assez forte entre le domaine de la recherche et le domaine de l’entreprise en informatique. Pourtant, nous pouvons réaliser avec ce projet comme exemple que la recherche et le monde de l’entreprise peuvent être bénéfique si une étroite collaboration est réalisée.

En effet, dans le cas où une entreprise pourrait investir dans la recherche, cela permettrait à ce secteur de développer des outils qui pourraient ainsi augmenter la productivité dans une entreprise.

Nous pouvons éventuellement entrevoir en tant que perspective d’avenir une étroite collaboration entre les laboratoires de recherche et les entreprises, qui pourrait réellement être bénéfique aux deux partis. Annexe Organigramme de la direction et des services de l’UQAM Détails sur l’utilisation de l’outil.

In a context where technologies are evolving rapidly, it is necessary for companies or other structures in the field of information technology to appropriate and integrate these innovations into their applications, in order to remain efficient and up-to-date.

However, some technological developments require a complete overhaul of the architecture of an application, which can then prove to be problematic and tedious. In collaboration with a research team from the Université du Québec à Montréal, I developed a tool that would accelerate this process, thus giving developers of an application the opportunity to facilitate this architectural redesign, which is complicated and yet necessary.