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