Just a paradox
Comment, en refusant de céder aux solutions simplifiées des outils dédiés à l’expérience développeur·euse, on peut finir par investir du temps à améliorer cette même expérience.
Mes collègues et moi discutons parfois de l’expérience développeur·euse (Developer Experience ou DX), et il faut bien admettre que les échanges portent souvent sur ses limites. Selon nous, l’un des problèmes majeurs réside dans le fait que la multitude d’outils disponibles sert avant tout à améliorer la productivité, plutôt qu’à enrichir l’apprentissage et donc l’expérience de la personne qui développe.
Ce constat en tête, nous devions démarrer un nouveau projet développé en Go, reposant sur une base de données MariaDB. Nous avons donc choisi de laisser chaque développeur·euse installer ellui-même les prérequis sur son système, comme Go, plutôt que de fournir un environnement pré-configuré dans un conteneur Docker.
Cependant, si nous sommes d’accord sur de nombreux points, je dois reconnaître que, personnellement, je n’aime pas devoir installer un serveur de base de données spécifique pour travailler sur un projet. Mais je ne veux pas non plus imposer l’utilisation de Docker à tout le monde.
Enfin, je tiens à conserver la simplicité d’une commande unique pour lancer tout mon environnement de développement.
Cet article décrit les solutions que j’ai mises en place pour améliorer mon expérience développeur, tout en restant fidèle à l’idée d’un projet qui encourage une compréhension approfondie de l’environnement d’exécution de l’application.
Juste quelques recettes
Lorsqu’on travaille sur un projet informatique, il y a toujours une liste plus ou moins longue de commandes à exécuter régulièrement : installer ou mettre à jour les dépendances, lancer ou arrêter un ou plusieurs serveurs, effectuer une migration de la base de données, charger des données de développement, exécuter les tests, etc.
Pour qu’une nouvelle personne rejoignant le projet puisse s’y retrouver, il faut qu’elle comprenne ces tâches. Cela nécessite au minimum une documentation. Dans une démarche d’apprentissage, on pourrait même considérer que documenter ces tâches est suffisant. Un peu comme recommander d’installer Arch Linux pour sa documentation exemplaire, et parce qu’elle offre une maîtrise complète d’un système que l’on installe à la main, plutôt que d’opter pour une Debian via son installateur automatisé. L’argument se tient. Pourtant, je reste encore sur Ubuntu.
J’aime donc bien avoir un utilitaire pour automatiser certaines de ces tâches : elles sont décrites dans un fichier de recettes, ce qui constitue en soit une documentation, mais en plus elles peuvent être exécutées d’une simple commande.
En temps normal, j’utilise un Makefile, mais pour ce projet, j’ai préféré Just, découvert en explorant le projet Bonfire. Le pari est qu’il sera plus simple à prendre en main pour quelqu’un sous Windows que make
, et comme on souhaite que notre projet puisse intéresser des utilisateur·ices Windows, cela semblait un bon choix.
Voici donc le justfile
de base, incluant une commande just install
et just start
:
// justfile
# Installation des dépendances Go
install:
cd src && go mod tidy
# Lancement de l'application
start:
cd src && air
Le problème de la base de données
Jusqu’ici, nous avons laissé les développeurs·euses installer elleux-mêmes Go (et air
, mais j’y reviendrai) ainsi que Just
sur leur machine. Passons maintenant à la gestion de la base de données.
Avant cela, une petite digression. Même si nous ne savons pas encore exactement comment notre projet sera distribué, nous suivons la méthodologie Twelve-Factor, ce qui implique de gérer la configuration via des variables d’environnement. Chacun·e est libre de sa méthode, mais personnellement, j’utilise direnv
, malgré quelques déboires occasionnels. Donc un conseil si vous utilisez direnv
, n’oubliez pas que le .envrc
n’est pas dans le dépôt git lorsque vous déciderez de reformater votre disque pour refaire une installation de votre machine.
Pour en revenir à la base de données, la solution la plus directe est de l’installer sur son système via un gestionnaire de paquets. Mais si une version incompatible avec le projet est déjà installée, les ennuis commencent.
On pourrait s’appuyer sur un outil comme asdf
, un gestionnaire de versions universel pour outils de développement. Il permet de gérer différentes versions d’interpréteurs, de compilateurs ou encore de bases de données comme MariaDB. J’ai aussi découvert asdf
en explorant Bonfire, où cet outil est souvent recommandé pour installer Elixir (Bonfire étant développé en Elixir). Mais pour être honnête, je n’ai pas du tout aimé l’expérience.
Pour installer et gérer des serveurs, je trouve Docker particulièrement pertinent. Cela dit, je comprends que tout le monde ne l’apprécie pas, et c’est important que chaque développeur·euse puisse choisir sa propre méthode. Mais ce qui m’embête, c’est qu’avec Docker, il va falloir se rappeler de lancer la base de données avant de lancer le just start
. Et j’aime qu’une commande comme just start
s’occupe de tout.
J’ai donc mis en place une méthode s’appuyant sur une variable d’environnement (MARIADB_WITH_DOCKER
), permettant de lancer automatiquement une base de données MariaDB dans un conteneur Docker, uniquement si on le souhaite.
// justfile
DcDev := "docker compose -p my-project -f docker-compose.db.yml"
# Lancement de l'application
start:
if [ "$MARIADB_WITH_DOCKER" = "true" ]; then just start-db; fi
cd src && air
# Arret de l'environnement de dev local
stop:
if [ "$MARIADB_WITH_DOCKER" = "true" ]; then just stop-db; fi
echo "Environnement de dev arrêté."
# Démarrage de la db dans un conteneur Docker
start-db:
{{DcDev}} up -d
# Arret de Docker de db
stop-db:
{{DcDev}} down
Tout démarrer et tout éteindre en une seule commande
On s’approche du but, mais une dernière chose me chiffonnait : la base de données démarrait seule, mais ne s’éteignait pas en même temps que le serveur Go.
En effet, nous utilisons air
pour relancer la compilation du serveur Go lors des modifications de code en développement. Oui, nous aurions pu nous en passer, mais c’est … juste un paradoxe. Toujours est-il que l’on stoppe le serveur Go contrôlé par air
avec un ctrl
+c
. Mais cela n’arrête donc pas la base, et il faut penser à lancer la commande just stop-db
. Autant dire que le risque est grand de l’oublier.
J’espérais pouvoir régler ce problème directement dans le justfile
, en m’appuyant sur la commande trap
afin de capter le signal de fin du processus de air
.
// justfile
start:
trap 'just stop; exit' INT
if [ "$MARIADB_WITH_DOCKER" = "true" ]; then just start-db; fi
cd src && air
Mais cela ne fonctionnait pas, sans que je sache vraiment pourquoi. Ma recette just
ne capturait jamais le signal d’interruption…
C’est donc avec la configuration de air
que j’ai trouvé une solution de repli. En effet, on peut lui indiquer une commande à lancer lors de l’arrêt :
// in .air.toml
post_cmd = ["just stop"]
Enfin, la magie du partage, lors de la relecture de ce post de blog, Thomas B. m’a donné la vraie jolie solution, permettant d’émuler n’importe quel script shell dans just en utilisant #!/usr/bin/env sh
en début de recette, et donc de faire fonctionner correctement trap
. Voilà ce que cela donne au final :
//in justfile
DcDev := "docker compose -p my-project -f docker-compose.db.yml"
# Lancement de l'application
start:
#!/usr/bin/env sh
if [ "${MARIADB_WITH_DOCKER}" = true ]; then
trap 'just stop; exit' INT
just start-db
cd src && air
else
cd src && air
fi
# Arret de l'environnement de dev local
stop:
if [ "$MARIADB_WITH_DOCKER" = "true" ]; then just stop-db; fi
echo "Environnement de dev arrêté."
# Démarrage de la db dans un conteneur Docker
start-db:
{{DcDev}} up -d
# Arret de Docker de db
stop-db:
{{DcDev}} down
Sans oublier d’indiquer à air
de bien envoyer le signal d’interruption de script :
// in .air.toml
post_cmd = []
send_interrupt = true
Conclusion
Chez INCAYA, s’intéresser à l’expérience développeur·euse, c’est naviguer entre la simplicité d’utilisation et la compréhension des conditions d’exécution de l’applicatif à développer. Dans notre projet en Go, l’intégration de recettes exécutables et d’un choix flexible pour la gestion de la base de données est l’une des solutions qui nous a permis de trouver un équilibre entre automatisation, documentation implicite et maîtrise de l’environnement.
Et quoi de mieux qu’un post de blog pour partager en plus le cheminement qui nous a menés à ce “livre de recettes” ?