2) Fertigung: Visuelle Qualitätskontrolle
Problem: Echtzeitnahe Bildaufnahme, Inferenz, Feedback an Liniensteuerung. Air-Gap, deterministische Latenzen, Upgrade nur am Wochenende.
Entscheidung: Monolith in C++/Qt für UI und Gerätesteuerung mit eingebettetem Python-Inferenzmodul (über stabile C-ABI/pybind11), Modellverwaltung als internes Modul mit Signaturprüfung.
Trade-off: Gemischter Stack in einem Prozess; dafür keine Netzwerkgrenzen im kritischen Pfad. Gewonnen: Harte Latenzgarantien, vereinfachter Field-Support.

3) Vermessungssysteme: Skalierbare Datenplattform
Problem: Große Punktwolken, Offline-Synchronisation, Nutzerrechte On-Prem, lange Produktlebenszyklen.
Entscheidung: Monolithische Kernplattform mit Plugin-System für neue Sensor-Adapter. Datenmodell stabil, Domain-Events intern. Spätere Auslagerung von Rechenjobs auf Worker-Knoten via Batch-Farm (gleiches Artefakt, anderer Startmodus).
Trade-off: Höhere Komplexität im Plugin-ABI-Design. Gewonnen: Upgrade-Sicherheit und Erweiterbarkeit ohne Service-Zoo.

4) Luftfahrt: Cloudbasierte Trainingsplattform
Problem: Multimandanten, variable Lastspitzen, globale Nutzer, Integrationen mit Drittanbietern.
Entscheidung: Kernmonolith für Domain-Backbone plus gezielte Microservices für Lastspitzenbereiche (z. B. Einreichung/Video-Transcodierung) und Integrationen mit abweichender Änderungsrate. Kubernetes sinnvoll wegen dynamischer Skalierung und Rollouts.
Trade-off: Teilweise verteilte Komplexität, dafür echte Skalierungs- und Teamautonomie in volatilen Bereichen.

Die Gemeinsamkeit: Nicht “entweder oder”, sondern “Monolith als Default, Services dort, wo Variabilität, Last oder organisatorische Grenzen es rechtfertigen”.

Design-Checkliste: Monolith mit Option auf Services

  • Domänenschnitt: Sind Bounded Contexts sauber beschrieben? Gibt es klare Ownership pro Modul?
  • Volatilität: Welche Teile ändern sich am häufigsten? Können diese entlang von Ports/Adapters isoliert werden?
  • Latenz/Determinismus: Welche Pfade erfordern deterministische Latenzen? Diese möglichst in-proc halten.
  • Daten: Gibt es harte transaktionale Konsistenzanforderungen? Wenn ja, lieber lokal als eventual verteilen.
  • Packaging/Deploy: Wie sieht das reale Update-Fenster aus? Welche Artefakte sind zulässig (DEB/RPM/MSI/OCI)?
  • Observability: Welche Metriken, Logs, Health-Checks braucht Betrieb? Korrelation ohne Service-Mesh vorsehen.
  • Security: Wie werden Secrets verwaltet (On-Prem PKI, HSM)? Minimale Anzahl an Zertifikaten/Endpunkten.
  • Migration: Wo sind Seams, an denen ein Modul später als Service herausgeschnitten werden kann? ADRs führen.

Konkrete technische Patterns

  • Packaging On-Prem
  • Single OCI-Image mit Multi-Process via Supervisor oder systemd-nativ als einzelner Dienst.
  • Immutable Builds, signierte Artefakte, Offline-Registry oder Paket-Repo.
  • Konfigurationsmanagement per deklarativen Dateien (TOML/YAML) mit Schemavalidierung.
  • Persistenz
  • Eine robuste relationale Datenbank (z. B. PostgreSQL) reicht oft. Schema-Migration via Expand/Contract, Feature-Flags für neue Felder, Rollbacks im Notfall.
  • Für Messdaten/Time-Series intern eigener Speicherbereich oder eine bewusste TSDB-Integration als Adapter, nicht als separater Service.
  • Interne Asynchronität
  • In-proc Event-Bus (Pub/Sub) zur Entkopplung von Modulen, später austauschbar gegen IPC/externen Broker.
  • Job-Queues für rechenintensive Aufgaben, Worker-Pools parametrierbar, Backpressure definieren.
  • Schnittstellen
  • Interne Ports klar tipisiert. Wenn Netzwerk später nötig ist: gRPC/Protobuf-Definitionen von Anfang an pflegen, zunächst nur in-proc verwenden.
  • Externe APIs stabil halten, Versionierung semantisch, Breaking Changes vermeiden oder parallel fahren.
  • Sprache/Runtime
  • Performance-kritisches im nativen Stack (C++/Rust), ML/Prototyping in Python über stabile FFI.
  • Plugin-ABI klar definieren (C-ABI, Versionierungsstrategie), um Drittmodule sicher laden zu können.
  • Observability
  • Strukturierte Logs mit Korrelations-IDs. Metriken pro Modul-Namespace (z. B. Prometheus-Format).
  • Health/Readiness-Endpoints auch im Monolithen, z. B. /healthz per eingebettetem HTTP-Server.
  • Für KI/LLM-Komponenten: Agenten-Calls und Tool-Invocations explizit protokollieren und richtlinienbasiert steuern. In On-Prem-Setups nutzen wir Governance-Ansätze wie in Alpi-M, um Agenten-Verhalten nachvollziehbar und auditierbar zu machen – unabhängig davon, ob sie im Monolithen laufen oder als separater Dienst.

Antipatterns vermeiden

  • Premature Microservices: 12 Services, jedes “UserService”, “OrderService”-Skelett, alle reden synchron via HTTP, plus gemeinsame Datenbank. Ergebnis: verteilter Monolith, schlechter als jeder echte Monolith.
  • Entity Services: Services entlang von Tabellen schneiden statt entlang von Bounded Contexts → hoher Chattiness, schlechte Autonomie.
  • Überentkopplung in der Persistenz: “Database-per-Service” in On-Prem mit strikter Datenhoheit vervielfacht Backup, Restore, Verschlüsselung, Audits.
  • Hard Realtime über Netzwerk: Kritische Pfade über gRPC/REST mit Retries und Circuit Breakern – deterministisch wird das selten.
  • Tool-Overload: Service-Mesh, verteiltes Tracing, zentraler Broker, Feature-Flag-Service, Secrets-Manager, obwohl nur ein Host und ein Artefakt betrieben wird. Betrieb frisst Entwicklung auf.

Skalierung und Hochverfügbarkeit im Monolithen

  • Horizontal: Mehrere Instanzen des gleichen Artefakts hinter einem Load Balancer. State in DB/Queue; Sticky Sessions nur, wenn nötig.
  • Vertikal: Ressourcen pro Prozess konfigurieren (Thread-Pools, Async IO), CPU-Pinning für Echtzeitanteile.
  • HA: Aktive/Passive-Failover für DB, Read-Replicas für Reporting. Applikation stateless halten, soweit möglich.
  • Backpressure: Explizite Kapazitätsgrenzen und Überlaufstrategien (Drop, Degradieren, Queue-Limits).
  • Graceful Degradation: Nicht-kritische Module bei Ressourcenknappheit drosseln oder abschalten.

Migration: Vom Monolith zu gezielten Services

Ein pragmatischer Weg, den wir wiederholt gegangen sind:

1) Stabilisieren: Monolith modularisieren, technische Schulden abbauen, interne Ports definieren, Observability verbessern.
2) Identifizieren: Welches Modul hat abweichende Volatilität/Last oder erfordert separate Betriebspolicies (z. B. Drittintegration)?
3) Extrahieren: Adapter an der Port-Grenze in einen Prozess heben (zunächst lokales IPC), Protokoll hart einfrieren (gRPC/Protobuf), dann über Netzwerk routen.
4) Strangler-Pattern: Neuen Service vor den alten Pfad hängen, schrittweise Traffic umlenken, Rollback-Pfade offenhalten.
5) Datenentkopplung: Event-Carving – Änderungen an zentralen Entitäten über Domain-Events publizieren, Consumer-Readmodels aufbauen, eventual consistency bewusst akzeptieren.
6) Governance: ADRs pflegen, Service-Katalog, Versionierung und Kompatibilitätsmatrix dokumentieren. Keine Extraktion ohne klaren Owner und SLO.

Cloud-native vs. On-Prem im Kontext des Monolithen