Artikel

Microservices-Architektur

Microservices-Architektur

Die Microservices-Architektur strukturiert eine Anwendung als eine Sammlung lose gekoppelter, unabhängig voneinander bereitstellbarer Dienste. Jeder Dienst verfügt über eine bestimmte Geschäftsfunktion, verfügt über einen eigenen Datenspeicher und kommuniziert über ein Netzwerk. Obwohl Microservices eine erhebliche betriebliche Komplexität mit sich bringen, ermöglichen sie Unternehmen die Skalierung von Entwicklungsteams, die unabhängige Bereitstellung und den Aufbau widerstandsfähigerer Systeme.

5 Min. LesezeitSprache: DE DeutschKostenlos0 Claps0 Kommentare
TechnologieEngineering-ArtikelDevOpsArchitectureMicroservicesTechnologyEngineering Articles
Leseoptionen

Einführung

Die Microservices-Architektur strukturiert eine Anwendung als eine Sammlung lose gekoppelter, unabhängig voneinander bereitstellbarer Dienste. Jeder Dienst verfügt über eine bestimmte Geschäftsfunktion, verfügt über einen eigenen Datenspeicher und kommuniziert über ein Netzwerk. Obwohl Microservices eine erhebliche betriebliche Komplexität mit sich bringen, ermöglichen sie Unternehmen die Skalierung von Entwicklungsteams, die unabhängige Bereitstellung und den Aufbau widerstandsfähigerer Systeme.


Monolith vs. Microservices

Der Monolith

Eine monolithische Anwendung verfügt über alle Funktionen in einer einzigen Codebasis und Bereitstellungseinheit:

┌─────────────────────────────────────────────┐
│              Monolith Application           │
├──────────────┬──────────────┬───────────────┤
│  User Module │  Order Module│  Payment      │
│  (controllers, services, repos, DB tables)  │
└──────────────┴──────────────┴───────────────┘

Monolith-Vorteile:

  • Einfache Entwicklung und Tests (einzelner Prozess).
  • Einfaches Debuggen (keine Netzwerkaufrufe zwischen Modulen).
  • Schnelle Kommunikation (In-Memory-Methodenaufrufe).
  • Keine verteilte Systemkomplexität.
  • Ideal für kleine Teams und Produkte im Frühstadium.

Nachteile des Monolithen:

  • Skalierung bedeutet, alles zu skalieren, nicht nur den Engpass.
  • Ein einzelner Fehler kann die gesamte Anwendung zum Absturz bringen.
  • Der Technologie-Stack ist gesperrt.
  • Eine große Codebasis wird schwer zu verstehen und zu warten.
  • Die Bereitstellung dauert länger, wenn die Codebasis wächst.
  • Der Aufwand für die Teamkoordination steigt.

Die Microservices-Architektur

┌──────────────────┐   ┌──────────────────┐   ┌──────────────────┐
│   User Service   │   │   Order Service  │   │  Payment Service │
│   (own DB, API)  │   │  (own DB, API)   │   │  (own DB, API)   │
└────────┬─────────┘   └────────┬─────────┘   └────────┬─────────┘
         │                      │                      │
         └──────────────────────┼──────────────────────┘
                                │
                    ┌───────────▼───────────┐
                    │     API Gateway       │
                    │   (auth, routing)     │
                    └───────────────────────┘

Grundprinzipien

1. Einzelverantwortung

Jeder Dienst besitzt genau eine Geschäftsfähigkeit. Wenn Sie den Zweck eines Dienstes beschreiben können, ohne „und“ zu verwenden, ist er wahrscheinlich gut abgegrenzt.

Gut:

  • „Payment-Service“ – kümmert sich um die Zahlungsabwicklung und Rückerstattungen.
  • „notification-service“ – Sendet E-Mails, SMS und Push-Benachrichtigungen.
  • „inventory-service“ – Verwaltet Lagerbestände und Reservierungen.

Schlecht:

  • „Benutzer-und-Bestelldienst“ – Zwei unterschiedliche Funktionen kombiniert (verstößt gegen SRP).
  • „Backend-Service“ – Alles in einem Service (überhaupt keine Microservices).

2. Dezentrales Datenmanagement

Jeder Dienst besitzt seine Datenbank. Dienste teilen niemals Datenbanken – sie kommunizieren über APIs.

-- Service A has its own database
CREATE TABLE users (
    id UUID PRIMARY KEY,
    email VARCHAR(255) UNIQUE,
    name VARCHAR(255)
);

-- Service B has its own database, stores only the user ID
CREATE TABLE orders (
    id UUID PRIMARY KEY,
    user_id UUID NOT NULL,
    total DECIMAL(10,2),
    status VARCHAR(50)
);

Warum nicht Datenbanken teilen?

  • Enge Kopplung – Schemaänderungen in einem Dienst unterbrechen andere.
  • Unterschiedliche Dienste haben unterschiedliche Speicheranforderungen (relational vs. Dokument vs. Diagramm).
  • Ein eigenständiger Einsatz wird unmöglich.
  • Die Skalierung erfordert koordinierte Schemamigrationen.

3. Belastbarkeit und Fehlerisolierung

Ein Ausfall in einem Dienst sollte nicht auf andere übergreifen.

Leistungsschaltermuster:

// Using a circuit breaker to handle service failures gracefully
const breaker = new CircuitBreaker({
  failureThreshold: 5,
  resetTimeout: 30000, // 30 seconds
});

async function getPaymentStatus(orderId: string) {
  try {
    return await breaker.call(() => paymentService.getStatus(orderId));
  } catch (error) {
    // Fallback: return cached status or default
    return { status: 'pending', note: 'Payment service unavailable' };
  }
}

Schottmuster: Weisen Sie verschiedenen Diensten separate Thread-Pools zu, damit ein langsamer Dienst nicht alle Ressourcen erschöpft:

// Separate thread pools per service
const paymentPool = new ThreadPool({ maxThreads: 10 });
const inventoryPool = new ThreadPool({ maxThreads: 20 });
const notificationPool = new ThreadPool({ maxThreads: 5 });

4. Unabhängige Einsatzfähigkeit

Jeder Dienst kann unabhängig bereitgestellt werden. Dies ist der Hauptvorteil von Microservices.

Bereitstellungsautomatisierung:

# GitHub Actions for a single microservice
name: Deploy Payment Service
on:
  push:
    paths:
      - 'services/payment/**'
      - '.github/workflows/payment.yml'
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t payment-service .
      - run: docker push registry.example.com/payment-service:${{ github.sha }}
      - run: kubectl set image deployment/payment-service payment-service=registry.example.com/payment-service:${{ github.sha }}

Kommunikationsmuster

Synchrone Kommunikation (REST, gRPC)

Vorteile: Einfach zu implementieren und zu überdenken. Die Semantik von Anfragen und Antworten ist bekannt.

Nachteile: Erstellt Laufzeitkopplung – wenn der Downstream-Dienst ausfällt, ist der Aufrufer betroffen.

// REST call using fetch
const order = await fetch(`http://order-service:8080/api/orders/${orderId}`);
// gRPC definition
service OrderService {
  rpc GetOrder (GetOrderRequest) returns (Order);
}
message GetOrderRequest {
  string order_id = 1;
}

Asynchrone Kommunikation (Nachrichtenwarteschlangen)

Vorteile: Lose Kopplung, bessere Ausfallsicherheit, unterstützt ereignisgesteuerte Architekturen.

Nachteile: Eventualkonsistenz, schwieriger zu debuggen, erfordert Nachrichteninfrastruktur.

// Kafka producer
await producer.send({
  topic: 'order.created',
  messages: [{ value: JSON.stringify({ orderId, userId, total }) }]
});

// Kafka consumer in notification service
await consumer.run({
  eachMessage: async ({ message }) => {
    const { orderId, userId } = JSON.parse(message.value.toString());
    await emailService.sendOrderConfirmation(userId, orderId);
  }
});

Wählen Sie zwischen synchron und asynchron

Kriterien Synchron Asynchron
Benötigt der Anrufer eine sofortige Antwort? Ja NEIN
Kann die Operation verschoben werden? NEIN Ja
Ist der Downstream-Dienst immer verfügbar? Ja Nicht erforderlich
Benötigen Sie eine starke Konsistenz? Ja Letztendlich ist es in Ordnung

Zersetzungsstrategien

So teilen Sie einen Monolithen

  1. Begrenzte Kontexte identifizieren – Verwenden Sie Domain-Driven Design (DDD), um natürliche Grenzen zu finden.
  2. Beginnen Sie mit dem Dienst mit dem höchsten Wert – Extrahieren Sie einen Dienst, der einen klaren unabhängigen Wert hat.
  3. Extrahieren Sie jeweils einen Dienst – Versuchen Sie keine Big-Bang-Migration.
  4. Verwenden Sie das Strangler Fig-Muster – Leiten Sie den Verkehr schrittweise vom Monolithen zum neuen Dienst weiter.
// Strangler Fig pattern
const gateway = express();

// Route to new service if available, fall back to monolith
gateway.use('/api/users', async (req, res, next) => {
  try {
    const response = await fetch('http://user-service/api/users' + req.path);
    return res.json(await response.json());
  } catch {
    next(); // Fall through to monolith handler
  }
});

Richtlinien zur Servicegranularität

Servicegröße Teamgröße Bereitstellungshäufigkeit Beispiel
⚡ Klein 1-3 Entwickler Mehrmals/Tag Benutzerüberprüfung
📐 Mittel 3-6 Entwickler Täglich/wöchentlich Auftragsabwicklung
🏗️ Groß 6-10 Entwickler Wöchentlich Zahlungsplattform

Faustregel: Ein Service sollte klein genug sein, dass ein einzelnes Team ihn vollständig besitzen kann, und groß genug, um einen sinnvollen Geschäftswert zu bieten.


Operative Herausforderungen

Beobachtbarkeit

Microservices erzeugen riesige Mengen an Telemetrie. Drei Säulen:

1. Protokollierung:

// Structured logging (JSON)
{
  "timestamp": "2026-05-24T10:30:00.123Z",
  "level": "error",
  "service": "payment-service",
  "trace_id": "abc123",
  "message": "Payment processing failed",
  "error": "stripe: insufficient funds",
  "metadata": { "order_id": "ord_456", "amount": 2999 }
}

2. Metriken (Prometheus-Format):

# HELP http_requests_total Total HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="POST",path="/api/payments",status="500"} 42

3. Verteiltes Tracing (OpenTelemetry):

# OpenTelemetry auto-instrumentation
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector:4318"
OTEL_SERVICE_NAME: "payment-service"
OTEL_TRACES_SAMPLER: "parentbased_traceidratio"
OTEL_TRACES_SAMPLER_ARG: "0.1"

Diensterkennung

In dynamischen Umgebungen (Kubernetes) ändern sich IP-Adressen. Dienste finden sich über DNS:

  • Kubernetes DNS – „ payment-service.namespace.svc.cluster.local“.
  • Consul – Dienstregister mit Gesundheitschecks.
  • Eureka – Netflix-Diensterkennung (Spring Cloud).

API-Gateway

Ein einziger Einstiegspunkt für alle Kundenanfragen:

Funktion Beispiel
Routenführung /api/users → Benutzerdienst, /api/orders → Bestellservice
Authentifizierung Validieren Sie JWT-Tokens vor dem Routing
Ratenbegrenzung 1000 Anforderungen/Minute pro Kunde
Fordern Sie eine Transformation an Konvertieren Sie JSON in Protobuf
Antwort-Caching GET-Antworten zwischenspeichern
Stromkreisunterbrechung Stoppen Sie die Weiterleitung an fehlerhafte Dienste

Konfigurationsmanagement

Alle Konfigurationen externalisieren:

# ConfigMap in Kubernetes
apiVersion: v1
kind: ConfigMap
metadata:
  name: payment-service-config
data:
  DATABASE_URL: "postgresql://..."
  STRIPE_API_KEY: "${STRIPE_API_KEY}"  # from Secret
  MAX_RETRIES: "3"
  FEATURE_FLAG_NEW_CHECKOUT: "true"

Container-Orchestrierung mit Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: payment-service
  template:
    metadata:
      labels:
        app: payment-service
    spec:
      containers:
      - name: payment-service
        image: registry.example.com/payment-service:latest
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5

Wann Sie Microservices NICHT verwenden sollten

  • Kleine Teams (< 10 Entwickler) – Der kognitive Aufwand von Microservices überwiegt die Vorteile.
  • Einfache Anwendungen – CRUD-App mit wenigen Funktionen. Ein Monolith ist schneller zu bauen.
  • Startups im Frühstadium – Iterationsgeschwindigkeit ist wichtiger als Skalierbarkeit.
  • Unklare Domänengrenzen – Wenn Sie Servicegrenzen nicht identifizieren können, erstellen Sie einen verteilten Monolithen.
  • Anwendungen mit geringem Datenverkehr – Der Mehraufwand für Microservices (Netzwerk, Bereitstellung, Überwachung) ist nicht gerechtfertigt.

Conways Gesetz: Organisationen entwerfen Systeme, die ihre Kommunikationsstruktur widerspiegeln. Wenn Ihr Team nicht entlang der Servicegrenzen organisiert ist, werden Microservices zu Reibungsverlusten führen.


Migrationsstrategie

Phase 1: Vorbereiten

  1. Fügen Sie dem Monolithen Überwachung und zentrale Protokollierung hinzu.
  2. Identifizieren Sie begrenzte Kontexte mithilfe von DDD-Workshops.
  3. Extrahieren Sie gemeinsam genutzte Bibliotheken (Authentifizierung, Protokollierung, Dienstprogramme).
  4. CI/CD und Containerisierung einrichten.

Phase 2: Extrahieren

  1. Extrahieren Sie den ersten Dienst (geringstes Risiko, höchster Wert).
  2. Implementieren Sie das Strangler Fig-Muster.
  3. Führen Sie Monolith + neuen Dienst parallel aus.
  4. Fügen Sie Leistungsschalter und Fallbacks hinzu.

Phase 3: Skalierung

  1. Extrahieren Sie zusätzliche Dienste iterativ.
  2. Implementieren Sie gegebenenfalls ereignisgesteuerte Kommunikation.
  3. Fügen Sie Service Mesh (Istio, Linkerd) für erweiterte Netzwerke hinzu.
  4. Investieren Sie in Plattform-Engineering (Backstage, Entwicklerportale).

Empfehlungen zum Technologie-Stack

Kategorie Optionen Notizen
Rahmen Spring Boot, NestJS, FastAPI, Go-Kit Wählen Sie basierend auf der Teamkompetenz
API REST, gRPC, GraphQL gRPC für intern, REST für extern
Nachrichten Kafka, RabbitMQ, NATS Kafka für hohen Durchsatz
Datenbank PostgreSQL, DynamoDB, MongoDB Passen Sie die Datenbank an die Serviceanforderungen an
Container Docker Universal
Orchestrierung Kubernetes EKS, AKS, GKE oder selbstverwaltet
API-Gateway Kong, Envoy, AWS API Gateway Gesandter für Service-Mesh-Integration
Beobachtbarkeit OpenTelemetry, Prometheus, Jaeger OpenTelemetry ist der Standard
CI/CD GitHub-Aktionen, GitLab CI, ArgoCD ArgoCD für GitOps
Service-Mesh Istio, Linkerd, Cilium Der Einfachheit halber verlinkt
Geheimes Management HashiCorp Vault, AWS Secrets Manager Tresor für Multi-Cloud

Abschluss

Microservices sind ein leistungsstarkes Architekturmuster, das unabhängige Bereitstellung, Teamautonomie und skalierbare Systeme ermöglicht. Allerdings sind sie mit einer erheblichen betrieblichen Komplexität verbunden. Die erfolgreichsten Microservices-Einführungen:

  1. Beginnen Sie mit einem Monolithen – Machen Sie sich mit Ihrer Domain vertraut, bevor Sie sie aufteilen.
  2. Dienste schrittweise extrahieren – jeweils ein Dienst.
  3. Investieren Sie stark in Automatisierung – CI/CD, Überwachung und Infrastruktur als Code.
  4. Organisieren Sie Teams rund um Services – Richten Sie die Teamstruktur an der Architektur aus.
  5. Empfehlen Sie eventuelle Konsistenz – Nicht alles muss streng konsistent sein.

Microservices sind nicht die Standardarchitektur – sie sind eine Lösung für spezifische Skalierungs- und Organisationsprobleme. Wählen Sie sie bewusst und nicht standardmäßig aus.

Empfohlene Artikel

6 Coding-Patterns, die ich von Senior Engineers gelernt habe
EditorialDE
8 Min.Kostenlos

6 Coding-Patterns, die ich von Senior Engineers gelernt habe

TechnologieCodeCoding
0 Claps
Lesen
Einen privaten KI-Assistenten für Notizen bauen
EmpfohlenDE
9 Min.Kostenlos

Einen privaten KI-Assistenten für Notizen bauen

Mache aus Markdown-Notizen einen durchsuchbaren Assistenten, ohne private Entwürfe offenzulegen.

TechnologiePlattform-LeitfädenKI-SchreibenAIAutomationData
0 Claps
Lesen
Rechnungsdaten mit OCR automatisiert auslesen
EditorialDE
8 Min.Kostenlos

Rechnungsdaten mit OCR automatisiert auslesen

Baue eine Pipeline, die Rechnungen liest, Felder extrahiert und unsichere Werte markiert.

TechnologieEngineering-ArtikelKI-SchreibenAIAutomationData
0 Claps
Lesen

Kommentare

0 Kommentare

Noch keine freigegebenen Kommentare sichtbar. Neue Antworten können moderiert werden.