Parce que la vie est trop courte pour redessiner des schémas J'ai récemment rejoint une nouvelle entreprise en tant . Comme cela arrive toujours, j’ai dû repartir de zéro. Des choses comme : où se trouve le code d’une application ? Comment se déploie-t-il ? D'où viennent les configurations ? Heureusement, mes collègues ont fait un travail fantastique en créant une « infrastructure en tant que code ». Alors je me suis surpris à penser : qu'ingénieur logiciel si tout est dans le code, pourquoi n’y a-t-il pas un outil pour relier tous les points ? Cet outil examinerait la base de code et créerait un diagramme d'architecture d'application, mettant en évidence les aspects clés. Un nouvel ingénieur pourrait regarder le schéma et dire : « Ah, d'accord, c'est comme ça que ça marche. » Tout d'abord J'ai beau chercher, je n'ai rien trouvé de pareil. Les correspondances les plus proches que j'ai trouvées étaient les services qui dessinent un diagramme d'infrastructure. J'en ai mis quelques-uns dans afin que vous puissiez y regarder de plus près. Finalement, j’ai abandonné la recherche sur Google et j’ai décidé de m’essayer au développement de nouveaux trucs sympas. cette revue Tout d'abord, j'ai créé un exemple d'application avec Gradle, et Terraform. Le pipeline d'actions GitHub déploie l'application sur Amazon Elastic Container Service. Ce dépôt sera une source pour l'outil que je vais construire (le code est ). Java Docker ici Deuxièmement, j'ai dessiné un diagramme de très haut niveau de ce que je voulais voir comme résultat : J'ai décidé qu'il y aurait deux types de ressources : Relique J'ai trouvé le terme trop surchargé, j'ai donc choisi . Alors, qu’est-ce qu’une relique ? C'est 90 % de tout ce que vous voulez voir. Y compris, mais sans s'y limiter: artefact Relic Artefacts (cases bleues sur le schéma, c'est-à-dire Jars, images Docker), Configs les ressources Terraform (cases roses sur le schéma, c'est-à-dire les instances EC2, ECS, files d'attente SQS), Ressources Kubernetes, et bien d'autres encore Chaque relique a un nom (par exemple, my-shiny-app), un type facultatif (par exemple, Jar) et un ensemble de paires clé → valeur (par exemple, chemin → /build/libs/my-shiny-app.jar) qui décrit entièrement Relic. Elles sont appelées . Plus Relic a de définitions, mieux c'est. définitions Source Le deuxième type est un . Les sources définissent, construisent ou fournissent des reliques (par exemple, les cases jaunes ci-dessus). Une source décrit une relique à un endroit donné et donne une idée de son origine. Bien que les sources soient les composants à partir desquels nous obtenons le plus d’informations, elles ont généralement des significations secondaires sur le diagramme. Vous n'avez probablement pas besoin de beaucoup de flèches pour passer de Terraform ou Gradle à toutes les autres reliques. Source Relic et Source ont une relation plusieurs-à-plusieurs. Diviser et conquérir Couvrir chaque morceau de code est impossible. Les applications modernes peuvent comporter de nombreux frameworks, outils ou composants cloud. AWS dispose à lui seul d'environ 950 ressources et sources de données pour Terraform ! L’outil doit être facilement extensible et découplé dès sa conception afin que d’autres personnes ou entreprises puissent y contribuer. Bien que je sois un grand fan de l'architecture incroyablement connectable des fournisseurs Terraform, j'ai décidé de construire la même chose, bien que simplifiée : Le a une responsabilité claire : créer des reliques basées sur les fichiers sources demandés. Par exemple, lit les fichiers *.gradle et renvoie , ou Relics. Chaque fournisseur crée des reliques des types dont il a connaissance. Les fournisseurs ne se soucient pas des interactions entre les reliques. Ils construisent des reliques de manière déclarative, totalement isolées les unes des autres. fournisseur GradleProvider Jar War Gz Avec cette approche, il est facile d’aller aussi loin que vous le souhaitez. Un bon exemple est GitHub Actions. Un fichier YAML de flux de travail typique se compose de dizaines d’étapes utilisant des composants et des services faiblement couplés. Un workflow peut créer un JAR, puis une image Docker, et le déployer dans l'environnement. Chaque étape du flux de travail pourrait être couverte par son fournisseur. Ainsi, les développeurs, disons, créent un fournisseur lié uniquement aux étapes qui les intéressent. de Docker Actions Cette approche permet à un nombre illimité de personnes de travailler en parallèle, ajoutant ainsi plus de logique à l'outil. Les utilisateurs finaux peuvent également mettre en œuvre rapidement leurs fournisseurs (dans le cas de certaines technologies propriétaires). Voir plus sous Personnalisation ci-dessous. Fusionner ou ne pas fusionner Examinons le prochain piège avant d'entrer dans la partie la plus juteuse. Deux fournisseurs, chacun créant une relique. C'est très bien. Mais et si deux de ces Reliques n’étaient que des représentations du même composant défini à deux endroits ? Voici un exemple. analyse la définition de tâche JSON et produit une relique de type . Le workflow d'action GitHub comporte également une étape liée à ECS, donc un autre fournisseur crée une relique . Maintenant, nous avons des doublons car les deux fournisseurs ne se connaissent pas. De plus, il est faux pour l’un d’entre eux de supposer qu’un autre a déjà créé une relique. Et alors ? AmazonECSProvider AmazonECSTask AmazonECSTaskDeployment Nous ne pouvons supprimer aucun des doublons en raison des définitions (attributs) de chacun d'eux. Le seul moyen est de les fusionner. Par défaut, la logique suivante définit la décision de fusion : relic1.name() == relic2.name() && relic1.source() != relic2.source() Nous fusionnons deux reliques si leurs noms sont égaux, mais qu'elles sont définies dans des sources différentes (comme dans notre exemple, JSON dans le référentiel et la référence de définition de tâche se trouve dans les actions GithHub). Lorsque nous fusionnons, nous : Choisissez un seul nom Fusionner toutes les définitions (paires clé → valeur) Créer une source composite faisant référence aux deux sources originales Tracer une ligne J'ai intentionnellement omis un aspect crucial d'une relique. Il y a peut-être un — et c'est mieux de l'avoir ! Le Matcher est une fonction booléenne qui prend un argument et le teste. Les matchers sont des éléments cruciaux d’un processus de liaison. Si une relique correspond à une définition de la relique d'une autre, elles seront liées entre elles. Matcher Vous vous souvenez quand j'ai dit que les fournisseurs n'avaient aucune idée des reliques créées par d'autres fournisseurs ? C'est toujours vrai. Cependant, un fournisseur définit un Matcher pour une relique. En d’autres termes, il représente un côté d’une flèche entre deux cases sur le diagramme résultant. Exemple. Dockerfile a une instruction ENTRYPOINT. ENTRYPOINT java -jar /app/arch-diagram-sample.jar Avec une certaine certitude, nous pouvons dire que Docker tout ce qui est spécifié sous . Ainsi, Relic a une fonction Matcher simple : . Très probablement, certaines reliques avec dans les définitions y correspondront. Si oui, une flèche entre et Relics apparaît. conteneurise ENTRYPOINT Dockerfile entrypointInstruction.contains(anotherRelicsDefinition) Jar arch-diagram-sample.jar Dockerfile Jar Avec Matcher défini, le processus de liaison semble assez simple. Le service de liaison parcourt toutes les reliques et appelle les fonctions de leur Matcher. La relique A correspond-elle à l'une des définitions de la relique B ? Oui? Ajoutez un bord entre ces reliques dans le graphique résultant. Le bord pourrait également être nommé. Visualisation La dernière étape consiste à visualiser notre graphique final de l'étape précédente. En plus du PNG évident, l'outil prend en charge des formats supplémentaires, tels que , et . Ces formats de texte peuvent sembler moins attrayants, mais l'énorme avantage est que vous pouvez intégrer ces textes dans presque n'importe quelle page wiki ( , et beaucoup plus). Mermaid Plant UML DOT GitHub , Confluence Voici à quoi ressemble le diagramme final de l’exemple de dépôt : Personnalisation La possibilité de brancher des composants personnalisés ou de modifier la logique existante est essentielle, en particulier lorsqu'un outil en est à sa phase initiale. Les reliques et les sources sont suffisamment flexibles par défaut ; vous pouvez y mettre ce que vous voulez. Tous les autres composants sont personnalisables. Les fournisseurs existants ne couvrent pas les ressources dont vous avez besoin ? Implémentez le vôtre en toute simplicité. Vous n'êtes pas satisfait de la logique de fusion ou de liaison décrite ci-dessus ? Aucun problème; ajoutez votre propre ou . Emballez le tout dans un fichier JAR et ajoutez-le au démarrage. En savoir plus . LinkStrategy MergeStrategy ici Sortie La génération d'un diagramme basé sur le code source gagnera probablement du terrain. Et l’outil en particulier (oui, c’est le nom de l’outil dont je parlais). ! NoReDraw Les contributeurs sont les bienvenus L'avantage le plus remarquable (qui vient du nom) est qu'il n'est pas nécessaire de redessiner un diagramme lorsque les composants changent. Le manque d’attention des ingénieurs est la raison pour laquelle la documentation en général (et les diagrammes en particulier) devient obsolète. Avec des outils comme , cela ne devrait plus poser de problème car il est facilement connectable à n'importe quel pipeline PR/CI. N'oubliez pas que 😉 NoReDraw la vie est trop courte pour redessiner des schémas