Aiguilles, bottes de foin et living documentation d’un système microservices

Nicholas Suter
Mar 21 · 10 min read

Laissez moi vous narrer une histoire palpitante, captivante et tout un tas d’autres adjectifs en -ante. C’est l’histoire du développement de Youniversalis, notre outil d’auto-documentation de notre système d’information composé de microservices.

Younited Credit a beau avoir commencé son activité à peine en 2011, on a tout de même réussi à créer très très rapidement un monolithe. Celui-ci, dans un contexte d’hypercroissance, a rapidement donné des signes de fatigue; puis a commencé à s’écrouler sous son propre poids. Pour faire simple, nous avions un gros front office, un gros back office et une multitude de batchs. Et tout ce petit monde communiquait au travers d’une grosse base de données.

C’est pourquoi, en 2016, nous nous sommes posé quelques questions cruciales :

  • Comment intégrer de nouveaux besoins dans les prochaines années sans retomber dans les mêmes travers ?
  • Comment découper notre produit en briques déployables indépendamment ?
  • Comment tester de nouvelles choses ? Et si ça marchait, comment les mettre à l’échelle ?

On a naturellement levé la tête et le cool kid du moment s’appelait Microservices. Au-delà de l’effet de mode, ce type d’architecture répondait sur le papier à nos problèmes. Aurions-nous pu nous y prendre autrement ? Très certainement. Regrettons-nous ce choix ? Sûrement pas.

Tout n’a pas été simple, loin de là. La recette toute faite n’existe pas. Et ce n’est jamais simple d’aborder une page blanche. Notre cahier des charges de l’époque était mince : on savait en partie les problèmes qu’on ne voulait plus, et on ne savait pas encore quels problèmes on aurait à résoudre dans le futur. Nous avons donc émis quelques hypothèses et nous sommes partis. Parmi les choses que nous avons appris à :

  • Déployer souvent. Très souvent. Nous avons dû faire d’énormes progrès en termes de DevOps : nous déployons aujourd’hui entre 8 et 12 microservices par jour en production. Forcément, on a dû tout automatiser. Pour ça, on use et abuse d’Azure DevOps et d’Octopus.
  • Monitorer. On apprend encore, mais après une période ELK, aujourd’hui, on aime bien Application Insights.
  • Valoriser la simplicité. Même si c’est parfois (souvent) complexe d’arriver à une solution simple. KISS et YAGNI, les amis.
  • Réfléchir MVP. On commence petit, on teste, on apprend et on étend ou on jette.

Bientôt 3 ans plus tard, on a appris un milliard de choses. Certaines hypothèses ont été confirmées, d’autres invalidées. Corollaire : on a fait certains bons choix, d’autres mauvais. Mais on s’est améliorés tout au long de cette période, et on continuera de s’améliorer dans le futur.

Aiguilles et bottes de foin

Notre patrimoine est aujourd’hui composé d’environ 350 applications réparties dans 120 microservices. Pour nous, un microservice est un ensemble d’applications (SPA, API, Azure Function, application console, etc.) traitant une problématique métier précise. Ces microservices appartiennent à des domaines. Et ces domaines sont collés au métier.

Donc est apparu dans la dernière année un problème nouveau pour nous : celui de la gouvernance. Tant qu’un système est composé d’un ensemble de composants qu’un humain peut retenir, pas de soucis. Mais passé cette barrière, les choses se compliquent. Et on se retrouve à chercher une aiguille dans une botte de foin.

Notre premier réflexe a été cartographier. A la main, évidemment. Donc on a sorti nos plus beaux outils de dessin (Powerpoint, Visio, draw.io, peu importe) et on a dessiné de jolis schémas.

On a d’abord tenté de tout faire rentrer sur un seul schéma :

Cette approche avait deux inconvénients majeurs, en plus d’être fastidieuse et ingrate : le schéma est illisible et mécaniquement faux. Illisible parce qu’on tente d’afficher trop d’informations. Et faux, parce que notre système évolue trop et trop vite pour maintenir la cartographie à jour.

On a aussi rapidement tenté de maintenir des schémas par domaine, mais sans résoudre réellement les deux problèmes ci-dessus, cela en ajoutait un troisième : pour avoir la vision globale, il fallait coordonner et synchroniser les visions locales.

En bons ingénieurs, nous sommes fainéants. Idéalement, nous aimerions une documentation qui se génère toute seule. Et on a eu beau chercher, on a rien trouvé sur le marché qui réponde à notre besoin. Donc on l’a fait nous-mêmes, comme des grands.

Youniversalis

Nous ne cherchions pas à réinventer à roue. Ou à inventer l’eau chaude. Toutes les méthodes de formalisation ramènent à dessiner des boîtes et des flèches. Les boîtes sont les différentes entités à cartographier. Chez nous, ce sont :

  • des domaines
  • des microservices
  • des applications
  • des packages (Nuget, NPM, etc.)
  • des sources de données (bases SQL, bases NoSQL, fichiers, blobs, cache Redis, etc.)
  • des messages qui transitent sur un bus
  • des projets de déploiement
  • des containers où sont déployés les microservices
  • des repositories git ou d’autres contrôles de source
  • de la documentation (Markdown par exemple)

Quant aux flèches, ce sont les liens, ou dépendances. Une application appartient à un microservice (qui appartient à un domaine), se connecte à une base de données, envoie un message, est déployé dans un container par un projet de déploiement depuis son code situé dans un repo git. Chacun de ces verbes est une dépendance. Et on aime maîtriser nos dépendances.

Notre modèle ressemble à celui-ci :

Voici ce à quoi ressemble aujourd’hui la vue application dans Youniversalis :

On y retrouve les principales informations mentionnées ci-dessus. Une application a un nom, un type (ici, une WebApplication), un framework et un microservice d’appartenance. On peut aussi accéder directement à la documentation technique via les liens vers les fichiers Markdown. On a un accès direct au code source, au projet de déploiement et au container dans lequel il est déployé. Le bloc Properties permet de tagguer l’application pour la recherche. Le reste des informations concerne les dépendances. On les a sous forme de listes et aussi un schéma généré via d3.js.

A ce stade, nous avons une application qui permet de cartographier notre patrimoine applicatif. Mais si la saisie est manuelle, on n’aura pas fait mieux que les outils du marché que l’on a pourtant estimés insuffisants.

Générer les boîtes

Nous voulions pouvoir générer automatiquement nos entités (les boîtes !). Ces entités chez nous se situent à plusieurs endroits :

  • Azure DevOps Repositories pour les applications, le code source, la documentation
  • Azure DevOps Pipelines pour certains projets de déploiements
  • Octopus pour d’autres projets de déploiement

L’avantage d’utiliser des outils au goût du jour, c’est qu’ils exposent des API REST permettant de les interroger. Nous avons donc développé des synchroniseurs qui récupèrent la liste de nos entités architecturales. Pour le moment, nous procédons donc par polling, mais si nous trouvons un moyen de nous abonner aux créations, modifications et suppressions d’entités, on ne s’en privera pas. En crawlant Azure DevOps et Octopus, nous pouvons détecter les applications, repositories, projets de déploiement, containers, la documentation et les messages (les boîtes jaunes, bleues et grises du schéma plus haut). C’est déjà pas mal. Ça nous permet de détecter les ajouts et suppressions de ces entités.

Les entités restantes sont des regroupements logiques (microservices, domaines et unités organisationnelles), qui n’ont pas de matérialisation physique. Elles sont donc déclarées manuellement.

Et mes flèches ? Tu les aimes, mes flèches ?

Pour les liens application > microservice, nous nous sommes sérieusement posé la question de comment les détecter automatiquement. La plupart du temps, un microservice est représenté par une solution Visual Studio, contenant les différentes applications de ce microservice. Mais ce n’est pas systématique, et nous n’étions pas convaincus qu’imposer cette convention était nécessaire ou productive. Nous avons plutôt opté par une interface de saisie rapide pour créer les microservices et les lier aux applications.

Concernant l’organisation par domaine, nous avons hésité entre :

  • une approche déclarative, de type fichier manifeste dans le code
  • l’interface de saisie dans Youniversalis
  • ou encore une convention de nommage de type Younited.MonDomaine.MonMicroservice.MonApplication.

La dernière proposition a été écartée car nous voulons coupler faiblement notre organisation humaine et le code pour permettre de nous ré-organiser sans refactorer le code. Entre l’IHM et le manifeste, nous nous sommes dits qu’il n’était pas plus compliqué de faire 2 clics que d’écrire un fichier texte. Donc l’IHM sort vainqueur.

Reste la partie qui nous intéressait réellement. Était-il possible de générer automatiquement les dépendances directes (appels http), indirectes (messaging) entre applications, ainsi que les dépendances à une source de données. Une lueur d’espoir est née en regardant des schémas générés par Application Insights :

Nous nous reposons assez lourdement sur Application Insights pour notre télémetrie. C’est grâce à cet outil que nous générons nos dashboards et alertes sur la santé du système. Mais nous nous sommes dits qu’on pouvait peut-être en tirer des informations architecturales. En l’occurrence, une application instrumentée avec AppInsights cartographie nativement les dépendances http. Et comme toutes les données AppInsights sont interrogeables en KQL via une API REST, nous pouvons cartographier dans Youniversalis les dépendances http entre applications et les dépendances aux sources de données.

Le dernier point restant était ardu. Nous voulions aussi détecter automatiquement les dépendances de messages. Nous utilisons intensivement Azure Service Bus et le pattern Pub/Sub. Au niveau infrastructure, cela ressemble à ceci :

L’application PublisherApp publie un message sur un topic. 3 souscriptions sont abonnées à ce topic, permettant de router le message vers 3 files d’attentes (“queues”) et donc 3 applications abonnées, qui sont chez nous des WebJobs (des applications console s’exécutant dans le contexte d’une WebApp Azure) ou des Azure Functions (le serverless vu par Microsoft).

En tant qu’architectes, ce qui nous intéresse ici sont le publisher, le message et les subscriber. L’épineuse question que l’on se pose est : peut-on détecter automatiquement ces informations, de la même façon qu’on a réussi à détecter automatiquement les dépendances http grâce à AppInsights ? La réponse est oui, mais ça nous a demandé pas mal de travail et un poil de chance. La chance, tout d’abord, parce qu’on utilise MassTransit pour faciliter notre utilisation du Service Bus. Ça nous permet de créer automatiquement les topics, souscriptions et files d’attente à partir de conventions en plus de faciliter les opérations récurrentes de messagerie. Et MassTransit fournit un package MassTransit.ApplicationInsights permettant de remonter des données de télémétrie sur envoi et réception de message. On commence à s’approcher du but. Nous avons poussé quelques Pull Requests sur le projet Github pour nous permettre de pousser les informations supplémentaires dont nous avions besoin dans AppInsights.

Une fois ces fonctionnalités disponibles sur MassTransit, nous pouvions enfin instrumenter correctement nos (nombreuses) applications et avec quelques lignes de configuration dans chacune, nous pouvons voir remonter les dépendances dans AppInsights. Ne restait plus qu’à mettre à jour notre synchroniseur AppInsights > Youniversalis.

Nous sommes maintenant doté d’un outil de gouvernance automatisé remontant une quantité importante d’informations.

C’est utile :

  • pour les architectes : on contrôle visuellement le couplage faible (messages) et fort (appels http) entre différents domaines. On peut aussi contrôler la répartition plus ou moins homogène des microservices entre différents domaines et donc équipes.
  • pour les nouveaux arrivants : on a une vue d’ensemble du système et on peut descendre au niveau du domaine pour prendre connaissance du patrimoine applicatif de sa nouvelle équipe
  • pour les développeurs : on peut comprendre les interactions entre applications sans avoir à descendre dans le code.

Nous avons pour ambition d’amener aussi les utilisateurs finaux de nos outils (ou leurs représentants) à utiliser Youniversalis. Cela nous permettra de valider que nous parlons bien le même langage (well hello ubiquitous language). De plus, si l’architecture que nous produisons leur parait compréhensible, c’est sans doute que nous avons bien fait notre travail.

Et ensuite ?

Nous fourmillons d’idées pour enrichir Youniversalis. Tout d’abord, nous allons ajouter un nouveau niveau de granularité dans l’outil : les packages. Nos applications consomment toutes des packages internes ou externes : des packages Nuget pour les applications .NET, des packages NPM pour les applications Javascript. La prochaine fonctionnalité à sortir consistera à lister l’ensemble des packages référencés dans le système, et dans quelle version. Cela nous permettra de nous assurer que nous n’utilisons pas trop de versions différentes d’un même package, mais aussi de piloter nos campagnes d’upgrade pour des besoins applicatifs ou de sécurité.

Aujourd’hui, la base de données de Youniversalis est une base relationnelle Azure SQL. Nous nous posons sérieusement la question de passer sur une base graph de type Cosmos Graph ou Neo4j. Dans Youniversalis, les liens sont des éléments de première classe, peut-être même plus que les entités. Donc une base graph parait une solution intelligente.

Enfin, Youniversalis n’a pas grand chose à avoir avec le métier de Younited Credit. Et nous pensons que nos travaux sur le sujet de la gouvernance automatisée d’un système complexe pourrait sûrement intéresser d’autres sociétés ou organisations. C’est pourquoi nous souhaitons open sourcer Youniversalis. Il nous faut encore travailler le packaging et aussi réfléchir à toute la terminologie utilisée dans l’outil. Il est aujourd’hui très opiniated. Peut-être trop. Cela mérite réflexion. On y parle domaines, microservices, http. Mais la structure pourrait parfaitement permettre de cartographier un système traditionnel on prem qui communique au travers d’une base de données ou d’échange de fichiers. Ce n’est pas notre vision chez Younited, mais rien ne l’empêche, à part les termes utilisés.

YounitedTech

Le blog Tech de Younited, où l’on parle de développement, d’architecture, de microservices, de cloud, de data… Et de comment on s’organise pour faire tout ça. Ah, et on recrute aussi, on vous a dit ?

Thanks to Slim Ayache.

Nicholas Suter

Written by

Chief architect @younited

YounitedTech

Le blog Tech de Younited, où l’on parle de développement, d’architecture, de microservices, de cloud, de data… Et de comment on s’organise pour faire tout ça. Ah, et on recrute aussi, on vous a dit ?