Les graphes comptent parmi les structures de données les plus utilisées en informatique, et pourtant elles restent étonnamment méconnues en pratique. La plupart des développeurs les manipulent quotidiennement sans s’en rendre compte : dès qu’un outil doit ordonner des tâches, déterminer ce qui dépend de quoi ou paralléliser intelligemment du travail, il y a fort à parier qu’un graphe de dépendances tourne sous le capot.
Avant de plonger dans les exemples concrets, posons les fondations. Un graphe trouve ses racines en mathématiques : c’est une représentation abstraite d’objets ainsi que des relations qu’ils entretiennent entre eux. Les objets sont communément appelés noeuds (ou nodes / vertices en anglais) et les relations qui les lient sont des arêtes (links / edges).
Aussi simples puissent-ils paraître, les graphes sont d’une immense utilité, et ce notamment grâce à l’orientation de leurs arêtes. Une arête orientée n’exprime pas une simple proximité entre deux noeuds : elle matérialise une relation directionnelle précise : « A dépend de B », « A doit s’exécuter avant B ». De fil en aiguille, c’est exactement ce qui permet de construire un graphe de dépendances. Et une fois ce graphe en main, on peut en déduire un ordre d’exécution, repérer les cycles, ou isoler les portions réellement impactées par un changement.
Voici trois familles d’outils que vous utilisez probablement déjà, et qui reposent entièrement sur cette idée.
Nx, Rush, Bazel & co : l’orchestration en monorepo
Les outils de monorepo tirent l’un de leurs principaux atouts de l’orchestration généralisée de tâches, et celle-ci repose essentiellement sur un graphe de dépendances. Le modèle est direct : chaque projet du monorepo représente un noeud, et les dépendances entre projets sont matérialisées par des arêtes orientées entre ces noeuds.
Une fois ce graphe construit, deux capacités majeures deviennent possibles.
Déterminer l’ordre d’exécution et paralléliser
Connaître les dépendances entre projets permet de calculer un ordre d’exécution valide pour les tâches (build, test, lint…) : un projet n’est traité qu’une fois ses dépendances traitées. Mais surtout, le graphe révèle ce qui est indépendant. Deux projets sans lien de dépendance peuvent être construits en parallèle sans risque, ce qui ouvre la voie à des optimisations d’exécution significatives sur de gros dépôts.
Le pattern « Affected » : ne refaire que le nécessaire
C’est sans doute le bénéfice le plus spectaculaire. À partir du graphe, on peut déterminer précisément quels projets du monorepo sont affectés par un changement dans un projet donné, et n’exécuter les tâches (test, build, lint…) que pour ces projets-là. Inutile de reconstruire tout le dépôt lorsqu’une seule librairie de feuille a changé : on remonte les arêtes pour identifier l’ensemble des projets impactés en aval, et on ignore le reste.
C’est l’un des deux fondements du pattern Incremental (ou Affected). Le second est la gestion d’un cache, local ou distribué, qui évite de recalculer une tâche dont le résultat n’a pas changé. Les deux mécanismes se combinent pour faire passer le temps de CI d’un monorepo de plusieurs dizaines de minutes à quelques secondes sur les changements ciblés.
docker-compose : un ordre de démarrage explicite
Le même principe se retrouve dans un outil que beaucoup utilisent au quotidien sans y penser : docker-compose.
Dans un fichier de configuration, l’option depends_on attachée à un service exprime explicitement qu’un service A dépend d’un service B. Cette relation est, ni plus ni moins, une arête orientée dans un graphe de dépendances.
services:
web:
build: .
depends_on:
- db
db:
image: postgres:16
Concrètement, cette déclaration permet à docker-compose de respecter un ordre de démarrage séquentiel plutôt que le mode parallèle qui s’applique par défaut. Le service web ne sera initialisé qu’une fois sa dépendance, db, déjà démarrée.
Le point essentiel est là : sans ce lien explicite, docker-compose ne peut pas connaître les relations entre les services et ne peut donc en déduire aucun ordre d’exécution. Le graphe de dépendances n’est pas un détail de confort, c’est un pré-requis à toute orchestration. Tant que les arêtes ne sont pas déclarées, l’outil n’a d’autre choix que de tout lancer de front.
Pipelines CI/CD : un graphe exprimé en DSL
En parlant d’orchestration de tâches, quoi de plus représentatif qu’un pipeline CI/CD ? Des stages et des jobs qui, selon les cas, doivent s’exécuter en parallèle ou en cascade : c’est un graphe de dépendances dans sa forme la plus pure.
Le DSL propre à chaque plateforme (GitHub Actions, GitLab CI, etc.) sert justement à exprimer ce graphe. On y déclare qu’un job en attend un autre, qu’un stage suit un précédent, ou qu’un ensemble de tâches peut s’exécuter simultanément.
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: npm run build
test:
needs: build
runs-on: ubuntu-latest
steps:
- run: npm test
deploy:
needs: [build, test]
runs-on: ubuntu-latest
steps:
- run: npm run deploy
Ici, needs joue exactement le rôle de depends_on : il dessine les arêtes du graphe. À partir de cette déclaration, l’orchestrateur du workflow en déduit des stratégies d’exécution appropriées : paralléliser ce qui peut l’être, sérialiser ce qui doit l’être, et n’enclencher deploy qu’une fois build et test réussis.
Conclusion
Monorepos, conteneurs, intégration continue : trois domaines en apparence sans rapport, mais qui s’appuient sur la même structure fondamentale. Dès qu’un outil a besoin d’ordonner des tâches, de paralléliser sans casser les dépendances ou de ne retraiter que ce qui a réellement changé, c’est un graphe de dépendances qui fait le travail.
Comprendre cette mécanique, c’est cesser de subir ces outils comme des boîtes noires et commencer à raisonner sur leur comportement : pourquoi tel job attend, pourquoi tel projet est reconstruit, pourquoi tel service démarre en dernier. Une fois qu’on a vu le graphe sous-jacent, on le voit partout.