Skalierung eines Fitness-App-Backends: Node.js-Fallstudie
Fallstudie zur Migration eines Fitness-App-Backends von einem Node.js-Monolithen zu Microservices mit Docker und Kubernetes.
Auf dieser Seite
In der wettbewerbsintensiven Welt der Fitness-Technologie sind Anwendungsleistung und Skalierbarkeit von entscheidender Bedeutung. Eine träge App kann den Unterschied zwischen einer treuen Nutzerin bzw. einem treuen Nutzer und einem abgewanderten Abonnenten ausmachen. Diese Fallstudie schildert den Weg von „FitTrack“, einer fiktiven, aber repräsentativen Fitness-Anwendung, bei der Migration ihres Backends von einer einzelnen monolithischen Anwendung zu einem verteilten System aus Microservices. Wir betrachten die Beweggründe für diesen bedeutenden Architekturwechsel, den Prozess der Zerlegung des Monolithen und die Technologien – Node.js, Docker und Kubernetes –, die ihn ermöglicht haben.
Dieser Artikel richtet sich an Entwicklerinnen und Entwickler sowie an Führungskräfte in der Technik, die mit den Herausforderungen einer schnell wachsenden Anwendung konfrontiert sind. Wenn Ihr Team mit langsamen Entwicklungszyklen, Engpässen bei der Bereitstellung und Schwierigkeiten bei der Skalierung einzelner Funktionen zu kämpfen hat, liefert diese praxisnahe Schilderung einen konkreten Fahrplan für eine erfolgreiche Migration.
Voraussetzungen:
- Ein solides Verständnis von Node.js und RESTful APIs.
- Vertrautheit mit den grundlegenden Konzepten von Docker und der Containerisierung.
- Ein grundlegendes Verständnis der Microservices-Architektur.
Das Problem verstehen
FitTrack startete mit einer klassischen monolithischen Architektur. Eine einzelne Node.js-Anwendung übernahm alles: Benutzerauthentifizierung, Trainingsprotokollierung, soziale Interaktionen und Datenberichte. Dieser Ansatz war für den ersten Start ideal und ermöglichte eine schnelle Entwicklung sowie eine einfache Bereitstellung. Als die Nutzerbasis jedoch exponentiell wuchs, zeigten sich die Schwächen des Monolithen.
Die Schmerzen eines wachsenden Monolithen:
- Ineffiziente Skalierung: Ein Anstieg von Nutzerinnen und Nutzern, die soziale Beiträge erstellten, bedeutete, die gesamte Anwendung zu skalieren, obwohl die Bereiche Trainingsverfolgung und Benutzerprofil unter normaler Last standen. Das führte zu unnötigen Infrastrukturkosten.
- Entwicklungsengpässe: Ein wachsendes Entwicklerteam, das alle an derselben Codebasis arbeiteten, führte zu Merge-Konflikten, komplexen Abhängigkeiten und der Angst, Änderungen vorzunehmen, die die gesamte Anwendung beschädigen könnten.
- Bereitstellungsrisiken: Ein kleiner Fehler in der Funktion des sozialen Feeds konnte die gesamte Anwendung zum Absturz bringen und Nutzerinnen und Nutzer daran hindern, ihre Trainings zu protokollieren – eine zentrale Funktion. Bereitstellungen wurden zu stressreichen Alles-oder-nichts-Ereignissen.
- Starre des Technologie-Stacks: Die monolithische Architektur erschwerte es, neue Technologien oder Sprachen für einzelne Funktionen einzuführen, die davon hätten profitieren können.
Das FitTrack-Team erkannte, dass es zur Unterstützung des Wachstums und für schnellere Innovation eine flexiblere, skalierbarere und widerstandsfähigere Architektur brauchte. Der Wechsel zu Microservices war keine Frage mehr des Ob, sondern des Wann und Wie.
Voraussetzungen
Bevor die Migration begann, richtete das Team die erforderlichen Werkzeuge und die lokale Entwicklungsumgebung ein:
- Node.js (v18 oder neuer): Die zentrale Laufzeitumgebung für die Microservices.
- Docker Desktop: Zum Erstellen und lokalen Ausführen containerisierter Dienste.
- Minikube: Zur Simulation eines lokalen Kubernetes-Clusters für Entwicklung und Tests.
- kubectl: Das Befehlszeilenwerkzeug für die Interaktion mit dem Kubernetes-Cluster.
- Ein Git-Repository für jeden neuen Microservice: Zur unabhängigen Verwaltung des Codes.
Schritt 1: Den Monolithen zerlegen – das Strangler-Fig-Muster
Statt einer riskanten und zeitaufwendigen „Big-Bang“-Neuentwicklung entschied sich das Team für das Strangler-Fig-Muster. Bei diesem Ansatz werden schrittweise neue Microservices um den bestehenden Monolithen herum aufgebaut, bis dessen Funktionalität langsam „erwürgt“ und er außer Betrieb genommen werden kann.
Was wir tun
Der erste Schritt bestand darin, klar abgegrenzte Geschäftsfunktionen innerhalb des Monolithen zu identifizieren und sie in logische Dienste zu zerlegen. Dieser von Domain-Driven Design (DDD) geleitete Prozess führte zur folgenden ersten Dienstaufteilung:
- Users Service: Zuständig für Benutzerprofile, Authentifizierung und Einstellungen.
- Workouts Service: Übernimmt das Erstellen, Abrufen, Aktualisieren und Löschen (CRUD) von Trainingsdaten.
- Social Service: Verwaltet den sozialen Feed, einschließlich Beiträgen, Likes und Kommentaren.
Jeder Dienst erhielt eine eigene, unabhängige Datenbank, um eine lose Kopplung sicherzustellen.
Umsetzung
Das Team begann mit dem Users Service. Es erstellte ein neues Node.js-Projekt mit einer eigenen PostgreSQL-Datenbank.
// A simplified look at the initial Users Service with Express.js
// users-service/index.js
const express = require('express');
const app = express();
const port = 3001;
app.use(express.json());
// Dummy user data
const users = [
{ id: 1, name: 'Alex', email: '[email protected]' },
{ id: 2, name: 'Maria', email: '[email protected]' },
];
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).send('User not found');
}
res.json(user);
});
app.listen(port, () => {
console.log(`Users service listening on port ${port}`);
});
So funktioniert es
Ein API-Gateway wurde als einheitlicher Einstiegspunkt für alle Client-Anfragen eingeführt. Anfangs leitete das Gateway den Großteil des Datenverkehrs an den Monolithen weiter. Anfragen an /api/users/:id wurden jedoch nun an den neuen Users Service geleitet. Dieser schrittweise Wechsel blieb für die Endnutzerinnen und Endnutzer unsichtbar.
Schritt 2: Dienste mit Docker containerisieren
Nachdem der erste Microservice definiert war, bestand der nächste Schritt darin, ihn mit Docker zu containerisieren. Dadurch entstand für jeden Dienst eine konsistente und reproduzierbare Umgebung – von der lokalen Entwicklung bis zur Produktion.
Was wir tun
Für jeden Microservice wurde ein Dockerfile erstellt. Diese Datei enthält die Anweisungen zum Erstellen eines Docker-Images, einschließlich des Basis-Node.js-Images, der Installation von Abhängigkeiten und der Angabe des Befehls zum Starten der Anwendung.
Umsetzung
# users-service/Dockerfile
# Use an official lightweight Node.js image
FROM node:18-alpine
# Set the working directory in the container
WORKDIR /usr/src/app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install production dependencies
RUN npm install --only=production
# Copy the rest of the application code
COPY . .
# Expose the port the app runs on
EXPOSE 3001
# Command to run the application
CMD [ "node", "index.js" ]
So funktioniert es
Mit diesem Dockerfile kann eine Entwicklerin oder ein Entwickler den Users Service in einem Container mit zwei einfachen Befehlen erstellen und ausführen:
# Build the Docker image
docker build -t fittrack-users-service .
# Run the container, mapping port 3001 to the host
docker run -p 3001:3001 fittrack-users-service
Dieser Vorgang wurde für den Workouts Service und den Social Service wiederholt, jeweils mit eigenem Dockerfile und auf unterschiedlichen Ports.
Schritt 3: Orchestrierung mit Kubernetes
Mehrere Docker-Container auf einem lokalen Rechner auszuführen ist das eine; sie in einer Produktionsumgebung mit Anforderungen an Skalierung, Fehlertoleranz und Bereitstellungen ohne Ausfallzeit zu verwalten, ist etwas anderes. Hier kommt Kubernetes ins Spiel.
Was wir tun
Das Team nutzte Kubernetes, um die Bereitstellung, Skalierung und Verwaltung seiner containerisierten Microservices zu automatisieren. Es erstellte Kubernetes-Manifest-Dateien (im YAML-Format), um den gewünschten Zustand jedes Dienstes zu definieren.
Umsetzung
Hier ein vereinfachtes deployment.yaml für den Users Service:
# users-service-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: users-service-deployment
spec:
replicas: 2 # Start with 2 instances for high availability
selector:
matchLabels:
app: users-service
template:
metadata:
labels:
app: users-service
spec:
containers:
- name: users-service
image: fittrack-users-service:latest # The Docker image to use
ports:
- containerPort: 3001
Um die Bereitstellung innerhalb des Clusters zugänglich zu machen, wurde eine service.yaml erstellt:
# users-service-service.yaml
apiVersion: v1
kind: Service
metadata:
name: users-service
spec:
selector:
app: users-service
ports:
- protocol: TCP
port: 80
targetPort: 3001
So funktioniert es
Diese YAML-Dateien werden mit kubectl apply -f <filename> auf den Kubernetes-Cluster angewendet. Kubernetes sorgt anschließend dafür, dass stets zwei Instanzen (Replikate) des users-service-Containers laufen. Der Kubernetes-Service stellt eine stabile IP-Adresse und einen DNS-Namen für die Bereitstellung bereit, sodass andere Dienste mit ihm kommunizieren können, ohne die einzelnen Container-IPs kennen zu müssen.
Alles zusammenführen: Kommunikation zwischen Diensten
Eine große Herausforderung in einer Microservices-Architektur ist die Verwaltung der Kommunikation zwischen Diensten. Wie erhält beispielsweise der Social Service den Namen und das Profilbild einer Nutzerin oder eines Nutzers, die bzw. der einen Beitrag erstellt hat?
Das Team entschied sich für einen hybriden Kommunikationsansatz:
-
Synchrone Kommunikation (REST-APIs): Für direkte Echtzeit-Datenanfragen kommunizierten die Dienste über RESTful-HTTP-Aufrufe. Der Social Service stellte beispielsweise eine
GET-Anfrage an den Users Service (http://users-service/users/:id), um Benutzerdetails abzurufen. Der Mechanismus zur Diensterkennung von Kubernetes macht dies nahtlos. -
Asynchrone Kommunikation (Message Broker): Für Ereignisse, die keine unmittelbare Antwort erfordern, implementierte das Team einen Message Broker (RabbitMQ). Wenn eine Nutzerin oder ein Nutzer ein neues Training erstellte, veröffentlichte der Workouts Service ein
workout_created-Ereignis. Andere Dienste, etwa der Social Service (zur Erzeugung eines Beitrags „neues Training“) oder ein künftiger Achievements Service, konnten dieses Ereignis abonnieren und entsprechend reagieren. Das entkoppelt die Dienste und erhöht die Resilienz.
Herausforderungen meistern: Daten, Monitoring und Bereitstellung
Die Migration verlief nicht ohne Schwierigkeiten:
-
Datenkonsistenz: Mit getrennten Datenbanken wurde die Aufrechterhaltung der Datenkonsistenz über Dienste hinweg zur Herausforderung. Das Team nutzte das Saga-Muster für komplexe Transaktionen, die mehrere Dienste umfassten. Dieses Muster zerlegt eine Transaktion in eine Reihe lokaler Transaktionen, mit kompensierenden Transaktionen, um Änderungen zurückzunehmen, wenn ein Schritt fehlschlägt.
-
Monitoring und Protokollierung: Wenn Anfragen zwischen mehreren Diensten hin- und herspringen, wird es schwierig, die Quelle eines Fehlers zu lokalisieren. Eine zentrale Lösung für Protokollierung und Monitoring mit dem ELK-Stack (Elasticsearch, Logstash, Kibana) und Prometheus mit Grafana wurde eingeführt. Sie bot eine einheitliche Sicht auf Zustand und Leistung des gesamten Systems.
-
CI/CD-Pipelines: Das Team richtete unabhängige CI/CD-Pipelines für jeden Microservice mit GitHub Actions ein. Ein Push in den Hauptzweig des Repositorys eines Dienstes löste automatisch Tests aus, erstellte ein neues Docker-Image, übertrug es in eine Container-Registry und stellte das Update im Kubernetes-Cluster ohne Ausfallzeit bereit.
Fazit
Die Migration von FitTrack von einem Monolithen zu einer Microservices-Architektur war ein erhebliches Vorhaben, doch der Nutzen war transformativ:
- Verbesserte Skalierbarkeit: Das Team konnte einzelne Dienste nun bedarfsgerecht skalieren.
- Höhere Entwicklungsgeschwindigkeit: Kleinere, fokussierte Teams konnten ihre Dienste unabhängig entwickeln, testen und bereitstellen.
- Höhere Resilienz: Ein Problem in einem Dienst brachte nicht mehr die gesamte Anwendung zum Erliegen.
- Technologische Flexibilität: Das Team konnte nun für jede Aufgabe die besten Werkzeuge wählen.
Diese Fallstudie zeigt, dass mit sorgfältiger Planung, den richtigen Werkzeugen und einem schrittweisen Vorgehen eine erfolgreiche Migration von einem Monolithen zu Microservices erreichbar ist und erhebliches Potenzial für eine wachsende Anwendung freisetzen kann.
Ressourcen
- Offizielle Dokumentation:
- Weiterführende Literatur:
Kurzreferenz
| Aspekt | Wichtige Information |
|---|---|
| Thema | Entwicklung |
| Relevanz | Patientenaufklärung und Bewusstsein |
| Maßnahme | Besprechen Sie dies mit Ihrer Ärztin bzw. Ihrem Arzt oder Ihrer Gesundheitsfachkraft |
| Häufigkeit | Wie von Ihrem Behandlungsteam empfohlen |
| Evidenz | Basierend auf aktuellen klinischen Leitlinien |
Häufig gestellte Fragen
F: Woran erkenne ich, ob das auf mich zutrifft? A: Besprechen Sie Ihre persönliche und familiäre Krankengeschichte mit Ihrer Ärztin bzw. Ihrem Arzt oder Ihrer Gesundheitsfachkraft, um Ihre individuellen Risikofaktoren einzuschätzen und die geeigneten nächsten Schritte festzulegen.
F: Was sollte ich meine Ärztin oder meinen Arzt fragen? A: Gute Fragen sind unter anderem: „Was bedeuten meine Ergebnisse?“, „Welche Möglichkeiten habe ich?“, „Welche Änderungen des Lebensstils würden helfen?“ und „Wann sollte ich zur Nachkontrolle kommen?“
F: Gibt es Änderungen des Lebensstils, die helfen können? A: Den meisten Erkrankungen kommen grundlegende Maßnahmen zugute: ausgewogene Ernährung, regelmäßige körperliche Aktivität (150+ Minuten/Woche), erholsamer Schlaf (7–9 Stunden), Stressbewältigung und der Verzicht auf Tabak.
Medizinischer Haftungsausschluss
Dieser Artikel dient ausschließlich Bildungszwecken und stellt keine medizinische Beratung dar. Konsultieren Sie stets eine qualifizierte Gesundheitsfachkraft, bevor Sie Entscheidungen über Ihre Gesundheit, Behandlung oder Versorgung treffen. Individuelle Umstände unterscheiden sich, und was für eine Person angemessen ist, muss für eine andere nicht angemessen sein.
Wenn Sie einen medizinischen Notfall erleben, rufen Sie sofort 911 (oder Ihre örtliche Notrufnummer) an.