Aller au contenu

Monter un environnement local de microservices

Démarrer un microservice en local, c’est facile : son README le dit. Les ennuis commencent quand le flux que vous voulez tester en traverse quatre, et que chacun en appelle deux autres qui ne sont pas les vôtres.

Cette page n’est pas le tutoriel d’un outil en particulier : c’est quelles pièces sont nécessaires, pourquoi la partie difficile n’est pas celle qu’on croit, et comment chacune se résout — avec Aseptic à la fin, parce que c’est ce que documente ce site, mais les quatre problèmes sont les mêmes quoi que vous utilisiez.

Un environnement local qui serve à quelque chose doit résoudre quatre choses. Dans cet ordre de difficulté réelle, qui n’est presque jamais l’ordre dans lequel on les aborde :

  1. Infrastructure commune. Kafka, Redis, PostgreSQL, ce qu’utilise votre système. C’est la partie la plus visible et la plus résolue : les conteneurs.
  2. Démarrage de chaque service. Sa commande, son profil, ses variables, son port. Du travail répétitif, mais mécanique.
  3. Communication entre services. Où appelle chacun. C’est là que part la matinée, et c’est ce dont presque personne ne parle.
  4. Données. Un schéma créé et quelque chose dedans avec quoi tester.

Un docker-compose.yml avec les pièces communes et docker compose up. C’est ce que fait tout le monde et c’est bien : les conteneurs sont exactement l’outil adéquat pour cela.

Deux détails qui mordent plus tard :

  • Les ports entrent en collision. Chaque équipe déclare le 5432 et le 6379. Dès que vous avez deux projets ouverts, ou que vous montez et descendez, les conflits commencent.
  • L’infrastructure se partage entre projets, les données non. Réutiliser le même Postgres pour deux systèmes différents finit en un schéma dont personne ne sait à qui il appartient.

Chaque service a sa façon : ./mvnw quarkus:dev, ./gradlew bootRun -Dspring.profiles.active=local, npm start, un binaire. Avec des profils, des variables et des options différents.

C’est ce qui finit d’habitude écrit dans un LOCAL_SETUP.md de quarante étapes : ça marche, mais ça vieillit mal. Personne ne met le document à jour quand une option change, et la personne suivante découvre le décalage à coups d’erreurs.

Un détail qui décide bien plus qu’il n’y paraît : tous les services ne veulent pas tourner pareil. Celui que vous modifiez, vous le voulez natif, avec rechargement à chaud et le débogueur attaché. Les trois autres doivent seulement être debout : en conteneur c’est très bien, isolés et sans encombrer votre machine.

C’est ici qu’est le vrai travail, et c’est celui qu’aucun outil de conteneurs ne résout à votre place.

Votre service bff appelle orders. En production, cette URL vient de la découverte de services ou de l’ingress. En local, quelqu’un doit décider où elle pointe, et les options ne sont pas équivalentes :

  • Vers le service démarré sur votre machine. C’est ce que vous voulez quand vous changez les deux côtés. Mais il faut alors le démarrer aussi, avec toute sa queue de dépendances derrière.
  • Vers l’environnement de développement partagé. Cela vous évite de le démarrer, en échange d’une connexion à son réseau, de données qui changent sous vos pieds, et du fait que si cet environnement est en panne, vous aussi.
  • Vers un mock. Parfait quand la réponse de ce service vous est égale, ou quand vous ne pouvez même pas l’atteindre. En échange de maintenir le stub.

Ce qui coûte cher, ce n’est pas de choisir : c’est d’appliquer le choix. Chaque option signifie changer une URL dans la configuration d’un dépôt qui n’est pas le vôtre, penser à ne pas la commiter, et défaire tout cela quand vous voulez essayer autre chose. Multipliez par services et par fois dans la journée.

Et il y a un piège : la bonne URL dépend de où tourne celui qui appelle. Un service natif atteint un conteneur par localhost:port ; un conteneur qui en appelle un autre, par le nom du service ; et un conteneur qui appelle quelque chose de natif sur votre machine a besoin du nom spécial de l’hôte. La même dépendance, trois URLs différentes selon la façon dont tourne chaque extrémité.

Schéma créé, migrations passées et quelques données minimales. Cela se résout d’habitude avec les migrations de l’application elle-même plus un script de semence.

La règle qu’il vaut mieux ne pas enfreindre : l’environnement local ne touche pas aux bases de données partagées. Une migration lancée par mégarde contre l’environnement de développement commun, c’est un après-midi perdu pour toute l’équipe.

Approche Bien pour Où elle reste courte
docker-compose de tout L’infrastructure ; un système petit et stable Construire l’image de votre service à chaque changement ; pas de rechargement à chaud ni de débogueur commodes ; la communication reste de la configuration à la main
Scripts + LOCAL_SETUP.md Démarrer vite Cela vieillit : personne ne le met à jour et cela échoue différemment sur chaque machine
Testcontainers De vrais tests d’intégration L’environnement naît et meurt avec le test ; ce n’est pas un endroit où travailler
Kubernetes local (Kind, minikube) Ressembler à la production Beaucoup de machine et beaucoup de cycle de build pour le quotidien ; la boucle de changement s’allonge
Orchestrateur local La boucle quotidienne avec plusieurs services Il faut décrire chaque service une fois

Aucun n’est bête : ils résolvent des choses différentes et cohabitent souvent. Ce qu’aucun des quatre premiers ne fait, c’est décider par dépendance où appelle chaque service et l’appliquer sans toucher aux dépôts.

Aseptic est un orchestrateur de bureau qui s’occupe des quatre pièces, et traite la troisième comme le problème principal qu’elle est.

  1. Vous décrivez chaque service une fois. Vous pointez vers le dossier du dépôt et sa technologie, ses ports, l’infrastructure dont il a besoin et qui il appelle sont autodétectés. Vous relisez et c’est tout.
  2. Vous regroupez dans un scénario les services d’un flux — « Checkout », « Création de client » — et ils démarrent ensemble, dans l’ordre et en attendant que chacun soit sain.
  3. Pour chaque dépendance vous choisissez local, cloud ou mock, et le moteur réécrit l’URL au démarrage en l’injectant comme configuration —options -D ou variables d’environnement—, avec la bonne perspective selon où tourne chaque extrémité. Vos dépôts ne sont pas touchés.
  4. L’infrastructure commune (Kafka, Redis, PostgreSQL) est gérée par l’app dans Docker, avec remappage automatique des ports quand ils entrent en collision.
  5. Chaque service, natif ou en conteneur, à votre choix : natif celui que vous modifiez, en conteneur ceux de fond.

Et ce que vous montez se partage : un scénario s’exporte dans un fichier autonome, de sorte que celui qui arrive clone, importe et démarre au lieu de lire le LOCAL_SETUP.md.