Modularer Monolith oder Microservices nach Beweislage

18 Min. Lesezeit

Treffen Sie die Wahl zwischen modularem Monolithen und Microservices anhand von Teams, Deployments, Mandantenschutz, Betrieb und Migrationskosten.

Modularer Monolith oder Microservices nach Beweislage

Ein KI-gestütztes SaaS-Produkt mit erwartetem schnellen Wachstum sollte meist als modularer Monolith starten, mit einer bewussten Ausnahme: Isolieren Sie Workloads, die nachweislich ein anderes Skalierungs-, Sicherheits- oder Fehlermodell brauchen. Erwartetes Wachstum beweist nicht, dass jede Geschäftsfunktion einen eigenen Prozess benötigt. Es zeigt, dass der Code Grenzen braucht, die sich messen und später verschieben lassen.

Ich habe Teams erlebt, die eine noch unfertige Domäne wegen einer steilen Prognose in Services zerlegt haben. Sechs Monate später lief jedes Feature durch vier APIs, Releases mussten weiterhin gemeinsam erscheinen und nur ein Entwickler verstand die Nachrichtenabläufe. Ich habe auch erlebt, wie eine einzige Deployment-Einheit zum Bremsklotz wurde, weil ein Inferenz-Worker die gesamte Rechenleistung verbrauchte und sein Release-Rhythmus nichts mit der Abrechnung zu tun hatte. Das Architekturetikett entschied keines der Ergebnisse. Verantwortlichkeiten, Laufzeitverhalten und betriebliche Reife taten es.

Eine brauchbare Entscheidung vergleicht die Koordinationskosten innerhalb eines Prozesses mit denen über ein Netzwerk. Die ersten zeigen sich in Code-Reviews, gemeinsamen Releases und Datenbankkonflikten. Die zweiten zeigen sich in Verträgen, Warteschlangen, Wiederholungen, Traces, Störungsbehebung und doppelter Infrastruktur. Wählen Sie Services nur, wenn eine konkrete Grenze diese zweite Rechnung wieder einspielt.

Schnelles Wachstum ist nicht ein einziger Workload

Schnelles Kundenwachstum sagt nicht, welcher Teil des Systems zuerst unter Druck gerät. Ein SaaS-Produkt kann rasch neue Mandanten gewinnen, während seine API unauffällig bleibt und KI-Hintergrundaufgaben den Großteil der Rechenleistung verbrauchen. Es kann mäßigen Traffic haben, während das Onboarding großer Kunden strenge Anforderungen an die Datentrennung erzeugt. Es kann schnell Nutzer gewinnen, aber einen Preisprozess zehnmal häufiger ändern als alles andere. Das sind verschiedene Belastungen, die jeweils auf andere Grenzen hinweisen.

Beginnen Sie mit einem Lastmodell, auch wenn die Zahlen Schätzungen sind. Trennen Sie interaktive Anfragen, geplante Jobs, Inferenzaufrufe, Datenaufnahme, Suchindexierung, Benachrichtigungen und Berichte. Halten Sie für jeden Workload Nachfrageeinheit, zulässige Latenz, Parallelitätsgrenze, Wiederholungsverhalten, berührte Daten und Fehlerwirkung fest. Eine in Seitenaufrufen gemessene Anfrage verhält sich anders als eine in Tokens und Minuten gemessene Dokumentverarbeitung. Beide hinter demselben Prozess zu skalieren kann Kapazität verschwenden, verlangt aber noch keine Zerlegung der gesamten Domäne.

Teams vermischen oft logische Modularität mit physischer Verteilung. Ein Modul besitzt ein Geschäftsvokabular und eine interne API. Ein Service fügt dieser Grenze ein Netzwerk und eine unabhängige Laufzeit hinzu. Sie brauchen das Erste vor dem Zweiten. Andernfalls bilden Microservices dieselbe Kopplung mit langsameren Aufrufen und schwierigeren Deployments nach.

Microsofts Leitfaden zu gängigen Webanwendungsarchitekturen enthält eine vernünftige Einschränkung, die in Architekturdebatten verloren geht: Wenn die gesamte Anwendung durch das Klonen einer Instanz skalieren kann, bringen getrennte Services möglicherweise wenig. Derselbe Leitfaden sagt, dass natürliche Funktionsgrenzen in einer frühen Produktphase noch unklar sein können. Dem stimme ich zu, ergänze aber einen Test. Unsicherheit sollte heute die Codestruktur verändern und nicht heute eine verteilte Topologie erzwingen. Legen Sie unsichere Funktionen hinter Modulschnittstellen, machen Sie ihren Datenzugriff deutlich und sammeln Sie Nachweise für eine mögliche Ausgliederung.

Ein modularer Monolith ist kein Ordner namens modules um einen gemeinsam genutzten Codeklumpen. Er braucht durchgesetzte Importregeln, klare Eigentümer für Tabellen oder Schemas und keine Hintertüren in interne Teile anderer Module. Wenn jeder Controller jede Tabelle abfragen darf, ist das Deployment monolithisch und der Entwurf unstrukturiert. Eine spätere Aufteilung wird teuer, weil Abhängigkeiten bis zur Migration unsichtbar bleiben.

Teamverantwortung setzt die praktische Grenze

Die Teamstruktur bestimmt meist die erste sinnvolle Obergrenze für die Zahl der Services. Eine kleine Produktgruppe kann zehn Services keine zehn unabhängigen Eigentümer geben. Sie gibt derselben Gruppe zehn Repositories, zehn Pipelines und zehn Alarmquellen. Dieselben Personen koordinieren weiter jede Änderung, also besteht die vermeintliche Autonomie nur im Diagramm.

Für ein Produktteam hält ein modularer Monolith die Rückkopplung kurz. Ein Entwickler kann eine Domänenregel, ihre Transaktion und das sichtbare Nutzerverhalten in einem Branch und einem Testlauf ändern. Die Verantwortung für Code kann trotzdem streng sein. Das Abrechnungsmodul kann Importe aus Workspace oder KI-Orchestrierung ablehnen, und seine Reviewer können Änderungen an der öffentlichen Schnittstelle genehmigen. Unabhängigkeit beginnt mit Entscheidungsrechten und Grenzen, nicht mit Repositories.

Mehrere stabile Teams verändern die Rechnung. Ein Service wird sinnvoll, wenn ein Team eine Geschäftsfunktion verantwortet, betreiben kann und nur selten zeitgleiche Änderungen am Code eines anderen Teams benötigt. Der Service braucht einen eindeutigen Bereitschaftsdienst, eine eigene Release-Entscheidung und einen Vertrag, den andere Teams nutzen. Geteilte Verantwortung ist keine Verantwortung. Wenn jeder Vorfall einen Gruppenchat mit Vertretern aus fünf Teams eröffnet, hat die Grenze den Koordinationsaufwand nicht gesenkt.

Stellen Sie bei der Planung drei Fragen. Wer genehmigt eine Vertragsänderung? Wer erhält nachts den Alarm? Wer kann einen Fix ohne Warten auf eine andere Gruppe deployen? Wenn die Antworten verschiedene Gremien oder niemanden nennen, behalten Sie die Grenze im Prozess, bis die Organisation sie tragen kann. Das verbietet keine frühe Ausgliederung. Ein Zweierteam kann einen Inferenz-Worker wegen seiner völlig anderen Laufzeit isolieren, sollte aber zugeben, dass es weiterhin ein Produktsystem betreibt.

Verteilte Entwicklung bringt einen weiteren Aspekt mit. Zeitzonen können Modulverantwortung nützlich machen, weil Teams weniger kollidierende Änderungen haben. Netzwerkgrenzen lösen Kommunikation jedoch nicht automatisch. SaaS Production koordiniert Entwickler in Kazakhstan und Eastern Europe sowie California. Daher behandeln wir schriftliche Verträge, eindeutige Verantwortung und Review-Regeln als technische Arbeit. Diese Disziplin hilft dem modularen Monolithen sofort und macht eine spätere Ausgliederung deutlich ruhiger.

Ich widerspreche der beliebten Empfehlung, pro kleinem Team einen Service zu bauen. Sie wirkt ordentlich, weil das Architekturdiagramm den Organisationsplan nachzeichnet. Sie scheitert, wenn Teams wechseln, eine Funktion mehrere Fachgebiete braucht oder ein dünner Service nur seine Box rechtfertigt. Lassen Sie stabile Geschäftsgrenzen die Teamverantwortung beeinflussen und die Teamverantwortung einen unabhängigen Betrieb tragen. Verwandeln Sie einen vorläufigen Personalplan nicht in ein dauerhaftes Netzwerk.

Unabhängige Deployments müssen sich rechnen

Unabhängige Deployments zählen nur, wenn Teams tatsächlich unabhängig veröffentlichen. Wenn eine Änderung an Service A zuerst ein Deployment von Service B, eine koordinierte Datenmigration und ein gemeinsames Abnahmefenster verlangt, betreiben Sie einen verteilten Monolithen. Er bringt die Fehlermöglichkeiten eines Netzwerks, ohne Release-Autonomie zu liefern.

Messen Sie den Bedarf vor jeder Ausgliederung. Prüfen Sie die letzten zwanzig Produktionsänderungen am Kandidaten. Zählen Sie, wie viele nur dieses Modul betrafen, wie viele koordinierte Änderungen an anderer Stelle verlangten, wie oft das Gesamt-Release sie verzögerte und wie oft ein modulspezifisches Rollback den Schaden begrenzt hätte. Eine Grenze verdient einen Service, wenn unabhängige Änderungen oft genug vorkommen, dass das gemeinsame Deployment wiederholt bremst und nicht nur eine vorgestellte künftige Unannehmlichkeit ist.

Kompatibilität ist der schwierige Teil. Ein unabhängig deployter Service muss während eines Rollouts Aufrufer mit älteren und neueren Vertragsversionen vertragen. Fügen Sie Felder hinzu, statt ihre Bedeutung zu ändern. Akzeptieren Sie beide Formen, während Verbraucher migrieren. Halten Sie Datenbankänderungen mit der vorherigen Anwendungsversion kompatibel, bis kein Rollback mehr nötig ist. Eine Pipeline pro Service schafft keine Unabhängigkeit, wenn Verträge Gleichschritt erzwingen.

Die Kubernetes-Dokumentation zeigt Rolling Updates für ein Deployment, doch rollende Container lösen nur den Austausch der Laufzeit. Sie machen eine inkompatible API nicht sicher. Sie koordinieren keinen Event-Produzenten mit alten Verbrauchern und machen keine zerstörerische Schemaänderung rückgängig. Manche Teams kaufen einen Orchestrator und verwechseln dessen Rollout-Steuerung mit einer Architektur.

Auch ein Monolith kann die Release-Unabhängigkeit verbessern, ohne verteilt zu werden. Feature-Flags trennen Veröffentlichung und Freigabe. Modulspezifische Tests verkürzen Rückmeldungen. Eine klare interne Schnittstelle begrenzt die Auswirkung von Änderungen. Sie können ein Anwendungs-Image verpacken, mehrere Instanzen ausführen und einen Worker-Einstiegspunkt aus demselben Repository nutzen. Diese Schritte sind günstig und umkehrbar.

Gliedern Sie aus, wenn das Deployment-Protokoll einen wiederholten Konflikt zeigt: Eine Funktion ändert sich häufig, trägt ein eigenes Rollback-Risiko oder muss eine Version ausführen, die der Rest nicht übernehmen kann. Behalten Sie sie intern, wenn Releases aus guten Geschäftsgründen koordiniert bleiben. Eine Pipeline ist oft ein Vorteil, solange das Team noch entdeckt, wie das Produkt funktioniert.

Mandantenschutz beginnt beim Datenzugriff

Mandantentrennung ist vor allem eine Eigenschaft von Datenzugriff und Autorisierung, nicht der Servicezahl. Hundert Services können Mandantendaten offenlegen, wenn jeder einer ungeprüften Mandantenkennung vertraut. Ein Prozess kann starke logische Trennung bieten, wenn jeder Zugriffsweg den Mandantenkontext erzwingt und mandantenübergreifende Ablehnung testet.

Entscheiden Sie, welche Trennstufe Produkt und Verträge verlangen. Gemeinsame Tabellen mit einer Mandantenspalte bieten effiziente Dichte, erfordern aber konsequente Filter. Getrennte Schemas reduzieren versehentliche Joins und vereinfachen manche Exporte, erhöhen jedoch den Migrationsaufwand. Getrennte Datenbanken bilden eine stärkere Betriebsgrenze und erlauben Wiederherstellung oder Verschiebung eines Mandanten, erhöhen aber Verbindungs-, Upgrade- und Flottenkosten. Dedizierte Deployments gehen weiter, wenn ein Mandant getrennte Rechenleistung oder Release-Zeitpunkte braucht. Keine dieser Entscheidungen verlangt für jede Geschäftsfunktion einen Microservice.

Die PostgreSQL-Dokumentation zur Zeilensicherheit erklärt, dass Richtlinien einschränken können, welche Zeilen normale Abfragen liefern oder verändern. Wenn Zeilensicherheit aktiv ist und keine passende Richtlinie existiert, verweigert PostgreSQL standardmäßig den Zugriff. Das ist eine nützliche zusätzliche Abwehr, hat aber einen wichtigen Haken: Tabelleneigentümer umgehen die Zeilensicherheit normalerweise. Eine Anwendung mit der Eigentümerrolle kann sich geschützt glauben, obwohl jede Abfrage weiterhin alle Zeilen sieht.

Dieses Fragment zeigt eine durchsetzbare Konfiguration für gemeinsame Tabellen. Die Anwendung setzt den Mandantenkontext an der Transaktionsgrenze, und die Richtlinie prüft Lese- sowie Schreibvorgänge:

ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents FORCE ROW LEVEL SECURITY;

CREATE POLICY tenant_documents ON documents
  USING (tenant_id = current_setting('app.tenant_id')::uuid)
  WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);

BEGIN;
SET LOCAL app.tenant_id = '8bd3659e-64f6-4f0d-92c7-49e79ad86a2b';
SELECT id, status FROM documents;
COMMIT;

Führen Sie einen negativen Integrationstest mit zwei Mandanten aus. Legen Sie für beide ein Dokument an, setzen Sie den Kontext des ersten Mandanten und prüfen Sie, dass die Auswahl des zweiten Dokuments null Zeilen liefert. Versuchen Sie dann, die Mandantenkennung des ersten Dokuments zu ändern, und prüfen Sie, dass die Datenbank dies ablehnt. Führen Sie den Test mit derselben Datenbankrolle wie in Produktion aus. Eine als Administrator geprüfte Richtlinie beweist wenig.

Eine Service-Ausgliederung ist begründet, wenn eine Isolationsgrenze eigene Zugangsdaten, Datenspeicher, Verschlüsselungsverwaltung, Datenregionen, Wiederherstellungsverfahren oder Operatorzugriffe verlangt. Ein gesundheitsbezogenes Teilsystem kann etwa eine eigene Betriebsgrenze verdienen, weil seine Zugriffs- und Änderungskontrollen anders sind als bei einem öffentlichen Marketingablauf. Separate Services für Mandanten, Nutzer und Einstellungen isolieren Mandanten nicht automatisch. Sie geben einem Kontextfehler nur mehr Verstecke.

KI-Ausführung verdient oft die erste Trennung

KI-Ausführung ist oft die erste sinnvolle Laufzeitgrenze, weil Ressourcen- und Fehlerverhalten vom transaktionalen SaaS-Kern abweichen. Modellaufrufe können viel länger als normale Datenbankanfragen dauern, Anbieter begrenzen Parallelität und Rate, Eingaben unterscheiden sich stark in der Größe und Kunden können einen Job verlassen, während die Berechnung weiterläuft. Diese Arbeit in einer Webanfrage zu halten bindet die Nutzerlatenz an eine externe Abhängigkeit und macht die Kapazitätsplanung unklar.

Die erste Trennung muss keine Flotte aus Geschäfts-Microservices sein. Eine Webanwendung kann Zugriff prüfen, einen Jobdatensatz anlegen und in einer Transaktion ein Outbox-Ereignis speichern. Ein Worker-Prozess kann den Job übernehmen, den Modellanbieter aufrufen, Fortschritt speichern und den Abschluss veröffentlichen. Der Worker kann zunächst dasselbe Repository und Release-Artefakt nutzen. Die Warteschlange bildet eine Ausführungsgrenze, während Module die Domänenverantwortung bewahren.

Entwerfen Sie das Jobprotokoll vor der Deployment-Topologie. Jeder Job braucht unveränderliche Mandanten- und Akteurkennungen, eine Eingabereferenz, Modellrichtlinie, Versuchsnummer und einen Idempotenzschlüssel. Speichern Sie große Prompts oder Dokumente kontrolliert, statt sie in Warteschlangennachrichten zu stecken. Der Worker muss den Mandantenbereich erneut prüfen und darf nicht annehmen, eine Nachricht sei autorisiert, nur weil sie aus einer internen Queue kommt.

Wiederholungen brauchen klare Regeln. Wiederholen Sie Zeitüberschreitungen, vorübergehende Anbieterfehler und Ratenbegrenzungen mit begrenzten Wartezeiten. Wiederholen Sie ungültige Prompts, Autorisierungsfehler oder Jobs über einer festgelegten Grenze nicht blind. Markieren Sie jeden Versuch, damit ein Operator einen langsamen ersten Lauf von der fünften Ausführung derselben teuren Anfrage unterscheiden kann. Wenn ein Anbieter die Arbeit annimmt und dann nicht antwortet, muss die Idempotenzstrategie klären, ob eine Wiederholung Wirkungen verdoppeln kann.

Menschliche Prüfung ist ein Workflow-Zustand und kein Kommentar am Modellergebnis. Speichern Sie den erzeugten Vorschlag, die Entscheidung, die Identität des Prüfers, Zeitpunkte und die genehmigte Version. Der Prüfer sollte ein stabiles Artefakt genehmigen, keinen Wert, den das System danach unbemerkt neu erzeugen kann. So wird Human-in-a-Loop testbar und Produktteams erhalten eine klare Grenze für folgenreiche Aktionen.

Gliedern Sie den KI-Worker als eigenen Service aus, wenn er getrennte Skalierung, eine andere Sprache oder Abhängigkeitsumgebung, strengeren Netzwerkzugang, anbieterspezifische Zugangsdaten oder einen eigenen Release-Rhythmus braucht. Behalten Sie Orchestrierungsregeln in der Produktdomäne, solange kein anderes Team sie wirklich verantwortet. Sonst wird der Worker zu einem entfernten Hilfsdienst, der zu viel über jedes Feature weiß und als neuer Monolith über das Netzwerk wächst.

Beobachtbarkeit wächst an jeder Grenze

Microservices erzeugen keine Beobachtbarkeit. Sie erzeugen mehr Ereignisse, die zusammengeführt werden müssen. In einem Prozess können Stacktrace und Anfrageprotokoll den vollständigen Weg zeigen. Über API, Queue, Worker und Callback kann derselbe Fehler wie vier Teilerfolge aussehen, wenn das System nicht über jeden Übergang Kontext mitführt.

OpenTelemetry definiert Traces, Metriken, Logs und Baggage als verschiedene Signale. Der Unterschied zählt. Metriken können zeigen, dass die Joblatenz gestiegen ist. Traces können zeigen, wo Stichproben von Anfragen Zeit verbracht haben. Logs können eine bestimmte Anbieterantwort oder Zustandsänderung festhalten. Keines ersetzt die anderen, und alle ohne konkrete Frage zu sammeln erzeugt einen teuren Haufen.

Instrumentieren Sie den modularen Monolithen vor der Service-Ausgliederung. Geben Sie jedem Modul einen stabilen Namen in Traces und Logs. Erfassen Sie Anfragekennungen, sichere Mandantenreferenzen, Jobkennungen, Deployment-Versionen, Ergebniszustände und Laufzeiten. Schreiben Sie niemals Prompts, Gesundheitsdaten, Zugriffstokens oder unbeschränkte Modellergebnisse in die Telemetrie. Die W3C-Spezifikation Trace Context standardisiert die Header traceparent und tracestate für den Transport einer Trace-Identität zwischen Systemen und warnt ausdrücklich vor personenbezogenen oder sensiblen Informationen darin.

Ein praktischer Bereitschaftstest verfolgt eine Nutzeraktion durch ihren gesamten Lebenszyklus. Starten Sie mit der HTTP-Anfrage, die einen KI-Job erzeugt, folgen Sie Datenbank-Commit und Outbox-Veröffentlichung, gehen Sie durch den Worker-Versuch und enden Sie beim gespeicherten Ergebnis. Ein Operator sollte vier Fragen ohne manuelle Suche in getrennten Konsolen beantworten: Welche Mandantenaktion startete die Arbeit, welche Version bearbeitete jede Stufe, wo sammelte sich Zeit und welcher Zustand lässt sich sicher fortsetzen?

Queues bringen eine häufige Falle mit. Ein erfolgreich beendeter Produzenten-Span bedeutet nur, dass die Nachricht angenommen wurde. Er bedeutet nicht, dass der Job erfolgreich war. Übertragen Sie Trace-Kontext in den Nachrichtenmetadaten, erzeugen Sie pro Versuch einen Verbraucher-Span und verknüpfen Sie Wiederholungen, ohne sie als einen durchgehenden Netzwerkaufruf darzustellen. Erfassen Sie Wartezeit getrennt von Ausführungszeit. Sonst kann ein Dashboard dem Modell zehn Minuten anlasten, die ein Job auf Kapazität wartete.

Zu den Betriebskosten eines neuen Services gehören Alarme, Dashboards, Service-Level-Ziele, Runbooks, Regeln zur Logaufbewahrung, Sampling-Richtlinien und ein Eigentümer, der sie deuten kann. Wenn die Kandidatengrenze kein nutzersichtbares Erfolgsmaß nennen kann, ist sie nicht bereit für einen Service. Verteilung vergrößert Unklarheit.

Datenverantwortung legt die versteckte Rechnung offen

Ein echter Microservice besitzt seine Daten und lädt andere Services nicht zu Abfragen seiner Tabellen ein. Microsofts Microservices-Leitfaden nennt dies Datensouveränität und beschreibt die Folge: Ein Geschäftsprozess über mehrere Services kann sich nicht auf eine Datenbanktransaktion stützen, daher müssen Teams mit Eventual Consistency umgehen. Diesen Teil lassen viele Architekturdiagramme weg.

Betrachten Sie die Aktivierung eines Abonnements. In einer Transaktion könnte die Anwendung das Abonnement anlegen, Berechtigungen zuweisen, einen Rechnungsdatensatz schreiben und über eine Outbox das Onboarding einreihen. Teilen Sie diese Funktionen in Services und ein Timeout kann auftreten, nachdem die Abrechnung die Anfrage angenommen hat, aber bevor Berechtigungen sie bestätigt. Der Aufrufer weiß ohne definierte Idempotenz und Statusabfrage nicht, ob er wiederholen, kompensieren oder warten soll.

Eine Queue entscheidet nicht über die Geschäftssemantik. Das Team muss festlegen, welcher Zustand maßgeblich ist, wie Duplikate wirken, wie lange Zwischenzustände bestehen dürfen, welche Fehler eine Kompensation auslösen und was ein Operator reparieren darf. Jedes Ereignisschema wird zu einem Kompatibilitätsversprechen. Jedes replizierte Feld kann veralten. Diese Arbeit kann korrekt und lohnend sein, aber sie ist keine kostenlose Skalierung.

Gemeinsame Datenbanken zwischen Services sind ein Übergang und keine volle Unabhängigkeit. Sie können das Migrationsrisiko senken, doch eine Schemaänderung kann weiterhin Releases koordinieren und ein Service kann die Regeln eines anderen umgehen. Wenn zwei Komponenten bei fast allen Schreibvorgängen transaktionale Konsistenz benötigen, gehören sie möglicherweise in eine Grenze. Ersetzen Sie keine zuverlässige lokale Transaktion durch einen verteilten Ablauf, nur um ein Diagramm zu erfüllen.

Das Outbox-Muster ist eine nützliche Brücke. Schreiben Sie Domänenänderung und Ereignisdatensatz in dieselbe lokale Transaktion und lassen Sie einen Publisher das Ereignis mit Wiederholungen zustellen. Verbraucher brauchen weiterhin Idempotenz, weil eine Zustellung mehrfach erfolgen kann. Das Muster schließt die Lücke zwischen Commit und Veröffentlichung, garantiert aber weder den Abschluss aller Folgeaktionen noch eine einmalige Zustellung.

Schreiben Sie vor der Ausgliederung eine Fehlertabelle. Halten Sie für jeden entfernten Schritt fest, was bei einem Timeout vor der Annahme, nach der Annahme, während der Antwort und während einer Wiederholung geschieht. Nennen Sie die Abgleichaktion und die Person oder Automatik, die sie ausführt. Wenn die Tabelle für die Geschäftsfunktion unverhältnismäßig wirkt, ist es die Netzwerkgrenze wahrscheinlich ebenfalls.

Migrationskosten hängen von heutigen Grenzen ab

Die günstigste Migration beginnt vor jeder Service-Ausgliederung. Ein modularer Monolith kann Abhängigkeiten sichtbar machen, Datenverantwortung zuweisen und Verträge definieren, während Aufrufe noch lokal sind. Mit diesen Grenzen kann ein Team die Domäne testen, ohne am selben Tag Netzwerk, Deployment und Konsistenz zu debuggen.

Beginnen Sie mit der Durchsetzung von Abhängigkeiten. Jedes Modul legt eine kleine öffentliche Schnittstelle offen, und Build-Regeln lehnen Importe aus internen Paketen ab. Geben Sie jeder Tabelle ein besitzendes Modul. Andere Module fordern Verhalten über die Schnittstelle an, statt fremde Tabellen zu verbinden. Veröffentlichen Sie Domänenereignisse im Prozess, wenn mehrere Module eine Tatsache brauchen, aber halten Sie deren Vertrag eindeutig und versioniert. Diese Regeln zeichnen die Kopplung auf.

Sammeln Sie dann Nachweise zur Grenze. Ein Kandidat wird stärker durch häufige Änderungen, eigene Skalierung, eine andere Sicherheitslage, einen separaten Eigentümer oder wiederkehrende Release-Konflikte. Er wird schwächer, wenn die meisten Features synchronisierte Änderungen über ihn hinweg verlangen, seine Daten ständig an modulübergreifenden Transaktionen teilnehmen oder seine Schnittstelle hauptsächlich CRUD nachbildet. Ein CRUD-förmiger Service bewegt oft Datensätze, ohne eine Geschäftsentscheidung zu besitzen.

AWS Prescriptive Guidance beschreibt das Strangler-Fig-Muster als schrittweisen Ersatz durch Routing und eine Anti-Corruption-Layer. Das ist sicherer als eine Neuentwicklung, doch der Proxy ist nicht der schwierige Teil. Es ist die Datenhoheit. Wählen Sie während der Ausgliederung genau einen Schreiber pro Datensatzart, vermeiden Sie doppelte Schreibvorgänge und lassen Sie das alte Modul einen Adapter aufrufen, der zwischen lokaler und entfernter Implementierung wechseln kann.

Eine kontrollierte Ausgliederung folgt dieser Reihenfolge:

  1. Frieren Sie den öffentlichen Modulvertrag ein und ergänzen Sie Verbrauchertests für das tatsächliche Verhalten.
  2. Verschieben Sie den Datenzugriff hinter die Modulschnittstelle und entfernen Sie direkte modulübergreifende Abfragen.
  3. Führen Sie die neue Implementierung für schreibgeschützte Vergleiche im Schattenmodus aus und schließen Sie sensible Felder aus Vergleichslogs aus.
  4. Leiten Sie einen kleinen, umkehrbaren Anteil durch den Adapter und behalten Sie ein System als Schreibautorität.
  5. Entfernen Sie die lokale Implementierung erst, wenn Rollback, Datenabgleich und Bereitschaftsverfahren in Produktion funktioniert haben.

Beginnen Sie nicht mit Identität, geteilter Autorisierung oder einem Ablauf, der jedes Modul berührt. Wählen Sie eine Grenze mit klaren Eingaben, sichtbaren Ergebnissen und erträglicher vorübergehender Inkonsistenz. KI-Dokumentverarbeitung, Medienkonvertierung, Benachrichtigungsversand und Suchindexierung passen oft. Abrechnung passt möglicherweise erst nach Stabilisierung des Produktmodells. Die erste Ausgliederung sollte den Servicebetrieb lehren, ohne das ganze Unternehmen hinter die Lektion zu stellen.

Migrationskosten enthalten mehr als Entwicklungszeit. Zählen Sie den Zeitraum doppelter Infrastruktur, Vertragsunterstützung, Datenabgleich, zusätzlicher Tests, Schulung und langsamerer Feature-Arbeit. Zählen Sie auch die Kosten der Nichtmigration: verzögerte Releases, verschwenderische Skalierung, wiederkehrende Konflikte und größere Störungsauswirkungen. Eine glaubwürdige Entscheidung vergleicht beide Summen über einen genannten Zeitraum, statt Microservices als unvermeidliches Ziel zu behandeln.

Eine Servicegrenze braucht fünf Belege

Wählen Sie standardmäßig den modularen Monolithen und verlangen Sie Belege für jede physische Trennung. Das ist keine konservative Architektur. Es setzt Komplexität dort ein, wo sie ein Ergebnis ändert.

Bewerten Sie einen Kandidaten anhand von fünf Belegen. Erstens kann ein Team Vertrag, Deployment, Alarme und Störungen verantworten. Zweitens braucht die Funktion in der Praxis häufig unabhängige Releases. Drittens unterscheidet sich ihre Last oder Laufzeit so stark, dass getrennte Skalierung Kapazität spart oder Latenz schützt. Viertens vertragen Daten- und Fehlersemantik eine Netzwerkgrenze ohne ständige verteilte Transaktionen. Fünftens kann die Organisation den resultierenden Ablauf beobachten und wiederherstellen.

Behandeln Sie Mandantentrennung als eigene Entscheidungsachse. Ein Service kann wegen vertraglicher Isolation eine eigene Datenbank oder ein eigenes Deployment verdienen, auch wenn sein Traffic gering ist. Eine andere stark genutzte Komponente kann im Monolithen bleiben, weil horizontale Skalierung funktioniert und ihre Daten zur Kerntransaktion gehören. Wachstum macht nicht jede Antwort gleich.

Setzen Sie Prüfauslöser, statt eine endgültige Architektur vorherzusagen. Bewerten Sie ein Modul neu, wenn Release-Koordination es wiederholt verzögert, seine Ressourcenkurve abweicht, ein anderes Team dauerhaft die Verantwortung übernimmt, sich Mandantenpflichten ändern oder Vorfälle zeigen, dass ein gemeinsamer Prozess den Schaden vergrößert. Bringen Sie Deployment-Verlauf, Traces, Kapazitätsdaten und Fehlerberichte zur Prüfung mit.

Die Entscheidung kann auch zurückgehen. Wenn zwei Services immer gemeinsam deployen, einen Eigentümer teilen und sich bei den meisten Anfragen gegenseitig aufrufen, kann ihre Zusammenführung Fehlerquellen entfernen, ohne Autonomie zu verlieren. Architektur sollte in beide Richtungen auf Belege reagieren.

Für ein junges KI-SaaS-Produkt würde ich einen stark modularen Kern, eine dauerhafte Queue und getrennte Worker-Ausführung dort ausliefern, wo KI-Workloads sie verlangen. Ich würde früh in die Durchsetzung des Mandantenkontexts, Outbox-Zustellung, Trace-Weitergabe und Tests von Modulabhängigkeiten investieren. Ich würde nicht für jedes Substantiv im Produktvokabular einen Service erstellen.

Schnelles Wachstum belohnt ein System, das seine Form ändern kann. Saubere Grenzen im Prozess erhalten diese Option zu geringen Kosten. Sobald eine Grenze eigene Verantwortung, Deployment, Skalierung, Datensemantik und Wiederherstellung belegen kann, verschieben Sie sie mit gutem Grund über das Netzwerk. Bis dahin ist das Netzwerk nicht gerechtfertigter Aufwand.

Häufig gestellte Fragen

Eignet sich ein modularer Monolith für ein schnell wachsendes SaaS-Produkt?

Ja, wenn die Anwendung horizontal skalieren kann und ihre Modulgrenzen durchgesetzt werden. Wachstum allein verlangt keine Microservices; messbare Unterschiede bei Verantwortung, Deployment, Laufzeit oder Isolation können sie begründen.

Wie groß sollte ein Team vor der Einführung von Microservices sein?

Es gibt keine magische Kopfzahl. Führen Sie einen Service ein, wenn ein stabiles Team Vertrag, Releases, Alarme und Störungen ohne ständige Abstimmung mit der restlichen Organisation verantworten kann.

Kann ein modularer Monolith Module unabhängig deployen?

Nicht als getrennte Laufzeiteinheiten, aber Feature-Flags und modulspezifische Tests können Release und Freigabe trennen. Wenn ein Modul wiederholt einen eigenen Rollback und Rhythmus braucht, stützt dieser Verlauf die Ausgliederung.

Bieten Microservices bessere Mandantentrennung?

Nur wenn die Servicegrenze eigene Zugangsdaten, Daten, Rechenleistung oder Operatorzugriffe umfasst. Code in Services zu teilen behebt keine Anwendung, die ungeprüfte Mandantenkennungen akzeptiert oder Autorisierungen überspringt.

Sollte KI-Inferenz in der zentralen SaaS-Anwendung laufen?

Kurze, vorhersehbare Aufrufe können dort beginnen, aber lange oder variable Arbeit gehört hinter eine dauerhafte Jobgrenze. Ein eigener Worker schützt die Weblatenz und skaliert Rechenleistung, ohne das gesamte Produkt zu zerlegen.

Welche Beobachtbarkeit wird vor einer Service-Trennung benötigt?

Verfolgen Sie eine Nutzeraktion durch Anfragen, Queues, Worker und gespeicherte Ergebnisse. Operatoren brauchen Versionen, sicheren Mandantenkontext, Jobzustand, Zeitangaben und Wiederholungen, bevor ein Netzwerk die Fehlerpunkte vervielfacht.

Dürfen Microservices eine Datenbank teilen?

Als vorübergehender Migrationsschritt ja, aber dies begrenzt unabhängige Schemaänderungen und ermöglicht das Umgehen fremder Regeln. Langfristige Autonomie braucht eindeutige Datenverantwortung, auch wenn mehrere Speicher denselben Server nutzen.

Welcher Service lässt sich zuerst am sichersten ausgliedern?

Wählen Sie eine Funktion mit klaren Eingaben, sichtbaren Ausgaben, eigener Skalierung und erträglicher Zwischeninkonsistenz. KI-Verarbeitung, Medienkonvertierung, Benachrichtigungen oder Indexierung vermitteln häufig Betriebserfahrung mit begrenztem Transaktionsrisiko.

Wie verhindert ein Team einen verteilten Monolithen?

Verlangen Sie kompatible Verträge, unabhängige Release-Entscheidungen und einen Eigentümer pro Service. Wenn normale Änderungen synchronisierte Deployments oder Datenbankzugriffe über Grenzen hinweg brauchen, korrigieren oder entfernen Sie die Grenze.

Kann ein Unternehmen von Microservices zu einem Monolithen zurückkehren?

Ja, und manchmal sollte es das. Services, die immer gemeinsam releasen, einen Eigentümer teilen und sich ständig aufrufen, können als eine gut strukturierte Einheit einfacher und sicherer werden.