Gérer les dépendances Composer dans un projet monorepo

Cet article a été publié depuis plus de 6 mois, cela signifie que le contenu peut ne plus être d'actualité.

Mettre en place un monorepo (aka. un dépôt monolithique), c'est mettre en place un dépôt global regroupant toutes les applications et divers composants de votre projet. Dans ce billet, nous n'allons pas voir les avantages ou inconvénients de ce type d'organisation, certains ont fait des billets sur les avantages, les inconvénients ou des conférences à ce sujet. Nous allons plutôt voir comment mettre en place une gestion de dépendances avec Composer.

Prenons par exemple, un projet ayant l'arborescence suivante :

applications/
    api/
        composer.json
    backend/
        composer.json
    frontend/
        composer.json
    worker/
        composer.json
component/
    package1/
        composer.json
    package2/
        composer.json

La problématique va être de pouvoir gérer simplement dans les différentes applications, les dépendances présentes dans le répertoire component. La première idée qui peut venir à l'esprit pourrait être de configurer l'autoloading de composer afin de faire référence aux différents composants déclarer dans le dépôt.

{
  "autoload": {
    "psr-4": {
      "Vendor\\Package1\\": "../../component/package1/src",
      "Vendor\\Package2\\": "../../component/package2/src"
    }
  }
}

Bien que cette méthode fonctionne, elle n'est cependant pas optimale car avec cette dernière, le fichier composer.json du composant n'est pas utilisé. Or, ce dernier contient diverses informations sur la bibliothèque. Dans le cas présent, nous avons dû redéfinir la configuration de l'autoloading de la bibliothèque.

Composer définit le concept de "repositories". Globalement, un repository correspond à une source de composant. Par défaut et sans que le développeur n'ait besoin de faire quoi que ce soit, Composer définit Packagist comme source principale.

Il est bien évidemment possible d'ajouter de nouvelles sources de repository et Composer supporte différentes sources de données telle que github, gitlab, vcs et bien d'autres encore. Mais celle qui va nous intéresser est path. Cette dernière va permettre de définir une source de composants correspondant à un chemin de notre machine.

Il est donc possible d'utiliser un composant en modifiant le composer.json d'une application de la manière suivante :

{
  "repositories": [
    {
      "type": "path",
      "url": "../../component/package1"
    },
    {
      "type": "path",
      "url": "../../component/package2"
    }
  ],
  "require": {
      "vendor/package1": "*",
      "vendor/package2": "*"
  }
}

Au travers de cette modification, il n'est plus nécessaire de redéfinir la configuration spécifiée dans le composant, puisque Composer va charger ce dernier comme n'importe quelle autre dépendance de votre projet.

En faisant des recherches sur la meilleure manière de gérer les dépendances au sein d'un monorepo, j'ai également découvert un projet expérimental se présentant sous la forme d'un plugin Composer dédié à la gestion de ce type d'organisation. Je n'ai pas expérimenté ce dernier et bien qu'il ne semble plus maintenu, il peut être intéressant d'y jeter un oeil. Son auteur a publié un article expliquant l'objectif et le fonctionnement du projet.