Wie man eine Microservice-Umgebung lokal betreibt
Einen Microservice lokal zu starten ist leicht: sein README sagt es. Der Ärger fängt an, wenn der Ablauf, den du testen willst, durch vier geht und jeder davon zwei weitere aufruft, die nicht deine sind.
Diese Seite ist kein Tutorial zu einem bestimmten Werkzeug: sie sagt, welche Teile es braucht, warum der schwierige Teil nicht der ist, nach dem er aussieht, und wie jeder gelöst wird — mit Aseptic am Ende, weil das ist, was diese Seite dokumentiert, aber die vier Probleme sind dieselben, was immer du benutzt.
Die vier Teile
Abschnitt betitelt „Die vier Teile“Eine lokale Umgebung, die etwas taugt, muss vier Dinge lösen. In dieser Reihenfolge der tatsächlichen Schwierigkeit, die fast nie die Reihenfolge ist, in der man sie angeht:
- Gemeinsame Infrastruktur. Kafka, Redis, PostgreSQL, was dein System eben nutzt. Der sichtbarste Teil und der am besten gelöste: Container.
- Start jedes Service. Sein Befehl, sein Profil, seine Variablen, sein Port. Repetitive Arbeit, aber mechanisch.
- Kommunikation zwischen den Services. Wohin jeder ruft. Hier geht der Vormittag drauf, und darüber redet fast niemand.
- Daten. Ein angelegtes Schema und etwas darin, womit sich testen lässt.
1. Die Infrastruktur
Abschnitt betitelt „1. Die Infrastruktur“Eine docker-compose.yml mit den gemeinsamen Teilen und docker compose up. Das machen
alle, und das ist richtig: Container sind genau das passende Werkzeug dafür.
Zwei Details, die später beißen:
- Ports kollidieren. Jedes Team deklariert 5432 und 6379. Sobald du zwei Projekte offen hast, oder Dinge hoch- und runterfährst, fangen die Konflikte an.
- Infrastruktur wird zwischen Projekten geteilt, Daten nicht. Dasselbe Postgres für zwei verschiedene Systeme wiederzuverwenden endet in einem Schema, von dem niemand weiß, wem es gehört.
2. Der Start jedes Service
Abschnitt betitelt „2. Der Start jedes Service“Jeder Service hat seine eigene Art: ./mvnw quarkus:dev,
./gradlew bootRun -Dspring.profiles.active=local, npm start, eine Binärdatei. Mit
unterschiedlichen Profilen, Variablen und Optionen.
Das ist es, was üblicherweise in einer vierzigschrittigen LOCAL_SETUP.md landet: es
funktioniert, aber es altert schlecht. Niemand aktualisiert das Dokument, wenn sich eine
Option ändert, und die nächste Person entdeckt die Lücke über Fehler.
Ein Detail, das viel mehr entscheidet, als es aussieht: nicht jeder Service will gleich laufen. Den, an dem du arbeitest, willst du nativ, mit Hot Reload und angehängtem Debugger. Die anderen drei müssen nur oben sein: im Container ist gut, isoliert und ohne deine Maschine vollzumüllen.
3. Die Kommunikation: der schwierige Teil
Abschnitt betitelt „3. Die Kommunikation: der schwierige Teil“Hier liegt die eigentliche Arbeit, und sie ist die, die dir kein Container-Werkzeug abnimmt.
Dein Service bff ruft orders auf. In der Produktion kommt diese URL von der Service
Discovery oder vom Ingress. Lokal muss jemand entscheiden, wohin sie zeigt, und die
Optionen sind nicht gleichwertig:
- Auf den Service, der auf deiner Maschine läuft. Das willst du, wenn du beide Seiten änderst. Aber dann musst du ihn auch starten, mit seinem ganzen Rattenschwanz an Abhängigkeiten.
- Auf die gemeinsame Entwicklungsumgebung. Das erspart dir den Start, im Tausch gegen eine Verbindung zu ihrem Netzwerk, gegen Daten, die sich unter deinen Füßen ändern, und dagegen, dass du auch unten bist, wenn jene Umgebung unten ist.
- Auf einen Mock. Perfekt, wenn dir die Antwort jenes Service egal ist, oder wenn du ihn gar nicht erreichen kannst. Im Tausch dafür, den Stub zu pflegen.
Teuer ist nicht das Wählen: teuer ist, die Wahl anzuwenden. Jede Option heißt, eine URL in der Konfiguration eines Repositories zu ändern, das nicht deines ist, daran zu denken, sie nicht zu committen, und das rückgängig zu machen, wenn du etwas anderes probieren willst. Multipliziere mit Services und mit Malen pro Tag.
Und es gibt eine Falle: die richtige URL hängt davon ab, wo der Aufrufer läuft. Ein
nativer Service erreicht einen Container über localhost:port; ein Container, der einen
anderen Container aufruft, über den Servicenamen; und ein Container, der etwas Natives auf
deiner Maschine aufruft, braucht den speziellen Hostnamen. Dieselbe Abhängigkeit, drei
verschiedene URLs, je nachdem, wie jedes Ende läuft.
4. Die Daten
Abschnitt betitelt „4. Die Daten“Schema angelegt, Migrationen gelaufen und ein paar minimale Daten. Das löst man üblicherweise mit den Migrationen der Anwendung selbst plus einem Seed-Skript.
Die Regel, die man besser nicht bricht: die lokale Umgebung fasst keine gemeinsamen Datenbanken an. Eine versehentlich gegen die gemeinsame Entwicklungsumgebung abgefeuerte Migration ist ein verlorener Nachmittag für das ganze Team.
Die Wege, es zu bauen
Abschnitt betitelt „Die Wege, es zu bauen“| Ansatz | Gut für | Wo er zu kurz greift |
|---|---|---|
docker-compose für alles |
Infrastruktur; ein kleines, stabiles System | Bei jeder Änderung das Image deines Service bauen; kein bequemer Hot Reload und kein bequemer Debugger; die Kommunikation bleibt handgeschriebene Konfiguration |
Skripte + LOCAL_SETUP.md |
Schnell loslegen | Es altert: niemand aktualisiert es und es scheitert auf jeder Maschine anders |
| Testcontainers | Echte Integrationstests | Die Umgebung entsteht und stirbt mit dem Test; sie ist kein Ort zum Arbeiten |
| Lokales Kubernetes (Kind, minikube) | Der Produktion ähneln | Viel Maschine und viel Build-Zyklus für den Alltag; die Änderungsschleife wird länger |
| Lokaler Orchestrator | Die tägliche Schleife mit mehreren Services | Jeder Service muss einmal beschrieben werden |
Keiner davon ist dumm: sie lösen verschiedene Dinge und koexistieren oft. Was keiner der ersten vier tut, ist pro Abhängigkeit zu entscheiden, wohin jeder Service ruft, und das anzuwenden, ohne die Repositories anzufassen.
Wie Aseptic es löst
Abschnitt betitelt „Wie Aseptic es löst“Aseptic ist ein Desktop-Orchestrator, der sich um die vier Teile kümmert und den dritten als das Hauptproblem behandelt, das er ist.
- Du beschreibst jeden Service einmal. Du zeigst auf den Repository-Ordner, und seine Technologie, seine Ports, die Infrastruktur, die er braucht, und wen er aufruft werden autoerkannt. Du prüfst es, und fertig.
- Du gruppierst in einem Szenario die Services eines Ablaufs — „Checkout“, „Kundenanlage“ — und sie starten zusammen, der Reihe nach und wartend, bis jeder gesund ist.
- Für jede Abhängigkeit wählst du lokal, Cloud oder Mock, und die Engine
schreibt die URL beim Start um, indem sie sie als
Konfiguration injiziert —
-D-Optionen oder Umgebungsvariablen—, mit der richtigen Perspektive je nachdem, wo jedes Ende läuft. Deine Repositories werden nicht angefasst. - Die gemeinsame Infrastruktur (Kafka, Redis, PostgreSQL) verwaltet die App in Docker, mit automatischem Port-Remapping bei Kollisionen.
- Jeder Service, nativ oder im Container, nach deiner Wahl: nativ der, an dem du arbeitest, im Container die im Hintergrund.
Und was du baust, wird geteilt: ein Szenario wird in eine in sich geschlossene Datei
exportiert, sodass wer neu dazukommt klont, importiert und startet, statt die
LOCAL_SETUP.md zu lesen.
Loslegen
Abschnitt betitelt „Loslegen“- Aseptic installieren — es ist kostenlos und du brauchst nur Docker.
- Dein erstes Szenario in 5 Minuten — die konkreten Schritte.
- Eine Abhängigkeit mocken — wenn dich heute die fehlende Cloud-Verbindung blockiert.