Case Study

Von monolithisch zu Microservices: Erfahrungsbericht aus einem deutschen Industrieunternehmen

Ein authentischer Einblick in die Migration eines Legacy-Monolithen zu einer Microservices-Architektur. Herausforderungen, Lösungen und messbare Ergebnisse.

P
Philipp Joos
Autor
18. Dezember 2025
15 min Lesezeit

Die Ausgangssituation: Ein Monolith am Limit

Ein mittelständischer Maschinenbauer aus Baden-Württemberg – nennen wir ihn "TechMach GmbH" – stand vor einer kritischen Herausforderung. Das zentrale Produktionssystem, über 12 Jahre gewachsen, war zum Flaschenhals geworden.

Die Symptome waren typisch:

  • Deployments nur noch alle 6-8 Wochen möglich
  • Ein kleiner Fehler konnte das gesamte System lahmlegen
  • Neue Features brauchten Monate statt Wochen
  • Entwickler verbrachten mehr Zeit mit Firefighting als mit Innovation
  • Die Dokumentation war veraltet, Wissen nur in einzelnen Köpfen

Die Kennzahlen sprachen eine deutliche Sprache:

  • 2,3 Millionen Zeilen Java-Code
  • 47 verschiedene Datenbanktabellen in einer Oracle-DB
  • Durchschnittlich 4 kritische Incidents pro Monat
  • Time-to-Market für neue Features: 4-6 Monate

Die Entscheidung: Warum Microservices?

Die Geschäftsführung stand vor der Frage: Weitermachen wie bisher, das System komplett neu schreiben oder schrittweise modernisieren?

Option 1: Weitermachen (Business as Usual)

  • Kurzfristig günstig
  • Langfristig unhaltbar
  • Risiko steigt kontinuierlich

Option 2: Big Bang Rewrite

  • Hohes Risiko (70% der Rewrites scheitern)
  • Lange Zeit ohne neue Features
  • Parallelbetrieb zweier Systeme

Option 3: Strangler Fig Pattern

  • Schrittweise Migration
  • Kontinuierlicher Geschäftsbetrieb
  • Lerneffekte fließen in spätere Phasen ein

Die Wahl fiel auf Option 3 – und das erwies sich als richtige Entscheidung.

Phase 1: Analyse und Domänenmodellierung (8 Wochen)

Bevor eine Zeile Code geschrieben wurde, investierten wir in das Verständnis des Systems.

Domain-Driven Design Workshop

Mit allen Stakeholdern – Entwicklung, Produktion, Vertrieb, Support – erarbeiteten wir eine gemeinsame Sprache und identifizierten Bounded Contexts:

flowchart TB
    subgraph Domain["TechMach Domain"]
        subgraph Row1[" "]
            subgraph Auftrags["Auftragsmanagement"]
                A1["Angebote"]
                A2["Aufträge"]
                A3["Kunden"]
            end
            subgraph Produktion["Produktionssteuerung"]
                P1["Maschinensteuerung"]
                P2["Qualität"]
                P3["Wartung"]
            end
            subgraph Logistik["Logistik-Context"]
                L1["Wareneingabe"]
                L2["Lager"]
                L3["Versand"]
            end
        end
        subgraph Row2[" "]
            subgraph Stammdaten["Stammdaten-Context"]
                S1["Produkte"]
                S2["Materialien"]
                S3["Lieferanten"]
            end
            subgraph Reporting["Reporting & Analytics"]
                R1["KPIs"]
                R2["Dashboards"]
                R3["Prognosen"]
            end
            subgraph Integration["Integration-Context"]
                I1["ERP-Anbindung"]
                I2["Lieferanten"]
                I3["EDI"]
            end
        end
    end

Abhängigkeitsanalyse

Mit Tools wie JDepend und ArchUnit analysierten wir die tatsächlichen Abhängigkeiten im Code – und entdeckten unerwartete Verflechtungen:

  • Der Reporting-Code griff direkt auf Produktionstabellen zu
  • Auftragslogik war mit Lagerlogik vermischt
  • Keine klare API-Schicht, direkter Datenbankzugriff überall

Phase 2: Infrastruktur und Plattform (12 Wochen)

Parallel zur weiteren Analyse bauten wir die Zielplattform auf.

Kubernetes-Cluster

Wir entschieden uns für eine On-Premise Kubernetes-Installation auf bestehender Hardware:

  • 3 Master Nodes für Hochverfügbarkeit
  • 12 Worker Nodes
  • Harbor als Container Registry
  • GitLab CI/CD für Automatisierung

Warum On-Premise? Produktionsdaten durften aus Compliance-Gründen das Firmengelände nicht verlassen. Mit Cloud-Anbindung für nicht-kritische Workloads.

Observability Stack

Von Tag 1 an implementierten wir umfassende Beobachtbarkeit:

  • Prometheus + Grafana für Metriken
  • Loki für Log-Aggregation
  • Jaeger für Distributed Tracing
  • PagerDuty für Alerting

Phase 3: Der erste Service – Proof of Concept (6 Wochen)

Für den Proof of Concept wählten wir bewusst einen Service mit begrenztem Risiko: das Benachrichtigungssystem.

Warum dieser Service?

  • Klar abgegrenzte Funktionalität
  • Keine kritischen Geschäftsprozesse
  • Überschaubare Integration
  • Schnell messbare Ergebnisse

Technologie-Stack

flowchart TB
    subgraph NS["Notification Service"]
        Runtime["Runtime: Node.js + TypeScript"]
        Framework["Framework: NestJS"]
        Queue["Queue: RabbitMQ"]
        Database["Database: PostgreSQL"]
        Container["Container: Docker"]
        Orchestration["Orchestration: Kubernetes"]
    end

Ergebnis nach 6 Wochen

  • Service in Produktion
  • 10x schnellere Deployments als im Monolithen
  • Keine Ausfälle durch andere System-Teile
  • Team hatte Kubernetes-Erfahrung gesammelt

Phase 4: Kritische Services migrieren (9 Monate)

Mit den Erkenntnissen aus dem PoC begannen wir die Migration der Kerndomänen.

Strangler Fig in der Praxis

Das Pattern funktioniert so: Ein Proxy vor dem Monolithen leitet Requests entweder an den alten oder neuen Code weiter.

flowchart TB
    Proxy["Proxy
(Nginx)"] Auftrags["Neuer
Auftrags-
Service"] Logistik["Neuer
Logistik-
Service"] Monolith["Monolith
(Legacy)"] Proxy --> Auftrags Proxy --> Logistik Proxy --> Monolith

Anti-Corruption Layer

Um die neuen Services vor dem Legacy-Datenmodell zu schützen, implementierten wir einen Anti-Corruption Layer:

// Beispiel: Legacy-Auftrag zu neuem Modell
class OrderAntiCorruptionLayer {
  translateLegacyOrder(legacyOrder: LegacyOrderEntity): Order {
    return {
      id: OrderId.create(legacyOrder.AUFTR_NR),
      customerId: CustomerId.create(legacyOrder.KD_NR),
      items: legacyOrder.POSITIONEN.map(this.translateLineItem),
      status: this.mapLegacyStatus(legacyOrder.STATUS_CD),
      createdAt: this.parseGermanDate(legacyOrder.ANL_DATUM),
    };
  }
}

Event-Driven Communication

Für die Kommunikation zwischen Services implementierten wir ein Event-System:

  • Apache Kafka als Event-Backbone
  • Jeder Service published Domain Events
  • Lose Kopplung, keine direkten Abhängigkeiten
  • Event Sourcing für kritische Aggregates

Die Herausforderungen

Herausforderung 1: Data Consistency

Mit verteilten Services wurde Datenkonsistenz komplex. Unsere Lösungen:

  • Saga Pattern für verteilte Transaktionen
  • Eventual Consistency wo möglich akzeptieren
  • Compensating Transactions für Fehlerfall

Herausforderung 2: Organisatorischer Widerstand

Nicht alle Entwickler waren begeistert. Wir investierten in:

  • Intensive Schulungen (2 Wochen pro Entwickler)
  • Pair Programming mit erfahrenen Consultants
  • Brown Bag Sessions zum Wissensaustausch
  • Erfolge sichtbar machen und feiern

Herausforderung 3: Performance

Die erste Version hatte Performance-Probleme durch zu viele Service-Calls. Optimierungen:

  • Caching-Strategie mit Redis
  • Bulk-APIs für häufige Abfragen
  • Asynchrone Verarbeitung wo möglich

Herausforderung 4: Debugging in verteilten Systemen

"Wo ist der Fehler?" wurde komplexer. Lösungen:

  • Correlation IDs durch alle Services
  • Distributed Tracing obligatorisch
  • Runbooks für häufige Fehlerszenarien

Die Ergebnisse nach 18 Monaten

Quantitative Verbesserungen

MetrikVorherNachherVerbesserung
Deployment-Frequenz8/Jahr50/Woche325x
Lead Time for Changes4-6 Monate2-5 Tage40x schneller
Mean Time to Recovery4-8 Stunden15 Minuten20x schneller
Kritische Incidents4/Monat0,3/Monat93% weniger
Entwickler-Zufriedenheit4,2/107,8/10+85%

Qualitative Verbesserungen

  • Autonome Teams: Jedes Team verantwortet seine Services vollständig
  • Innovation: Neue Features in Wochen statt Monaten
  • Recruitment: Attraktiverer Tech-Stack zieht Talente an
  • Resilienz: Ausfall eines Services betrifft nicht mehr das Gesamtsystem

Lessons Learned

Was wir richtig gemacht haben

  1. Früh in Observability investiert – unerlässlich für verteilte Systeme
  2. Klein angefangen – PoC hat Vertrauen geschaffen
  3. Domain-Driven Design ernst genommen – klare Bounded Contexts
  4. Kulturellen Wandel nicht unterschätzt – Menschen vor Technik

Was wir anders machen würden

  1. Früher automatisierte Tests – haben uns am Anfang zu sehr auf manuelle Tests verlassen
  2. Mehr Kapazität für Platform Team – Kubernetes-Expertise war Engpass
  3. Klarere API-Verträge – Consumer-Driven Contracts von Anfang an

Fazit: Die Investition lohnt sich

Die Migration von einem Monolithen zu Microservices ist kein Spaziergang. Es ist ein Marathon, der Geduld, Investition und organisatorischen Wandel erfordert.

Aber die Ergebnisse sprechen für sich. TechMach GmbH ist heute ein anderes Unternehmen:

  • Schneller am Markt als der Wettbewerb
  • Attraktiver Arbeitgeber für IT-Talente
  • Resilient gegen Störungen
  • Vorbereitet auf zukünftige Anforderungen

Der wichtigste Rat: Unterschätzen Sie nicht den organisatorischen Aspekt. Technologie ist das einfache. Menschen mitzunehmen ist die wahre Herausforderung.

Stehen Sie vor einer ähnlichen Herausforderung? Wir begleiten Sie gerne auf Ihrem Weg – mit Erfahrung aus zahlreichen Migrationsprojekten.

Tags:
Microservices
Migration
Legacy
Industrie 4.0
DevOps
Architektur
P

Philipp Joos

Geschäftsführer und Gründer von CFTools Software GmbH. Leidenschaftlich in der Entwicklung skalierbarer Softwarelösungen und Cloud-Native-Architekturen.

Artikel nicht verfügbar

Dieser Artikel ist für Ihren Zugangstyp nicht verfügbar.

Alle Artikel anzeigen