Zum Inhalt springen
14. Aug. 2026·8 Min. Lesezeit

Monolith vs. Microservices durch die Datenbank betrachtet

Betrachten Sie Monolith vs. Microservices als Frage des Datenbesitzes: Transaktionen erfassen, gemeinsame Tabellen aufdecken, Grenzen gezielt ziehen.

Monolith vs. Microservices durch die Datenbank betrachtet

Eine Servicegrenze ist nur glaubwürdig, wenn sich die Daten auf beiden Seiten ohne koordinierten Datenbank-Commit ändern können. Wenn zwei Codeteile dieselben Zeilen sperren, dieselben Tabellen aktualisieren oder dasselbe Deployment-Fenster abstimmen müssen, hat eine HTTP-Grenze den Transport verändert, aber nicht die Architektur.

Deshalb beginnt die nützliche Betrachtung von Monolith vs. Microservices beim Schema. Code lässt sich an einem Nachmittag hinter einen Endpunkt verschieben. Eigentum, Invarianten, historische Daten, Berichtsabfragen, Wiederholungen und Fehlerbehebung bleiben an den Tabellen hängen. Teams, die bei Klassen und Paketen anfangen, merken das meist erst, nachdem sie einen verteilten Monolithen gebaut haben: mehr Netzwerkaufrufe, mehr Betriebsarbeit und darunter weiterhin dieselbe Datenbankkopplung.

Ich habe nichts gegen Microservices. Ich lehne nur die Behauptung ab, eine Prozessgrenze erzeuge automatisch eine Datengrenze. Das Schema zeigt, wo das System bereits als Einheit arbeitet, wo es Speicher nur aus Bequemlichkeit teilt und wo eine Trennung auch ein fehlerhaftes Deployment um 2 Uhr morgens überstehen könnte.

Eine Transaktionskarte ist nützlicher als ein Abhängigkeitsgraph

Ordnen Sie vor der Wahl von Servicegrenzen jeden Geschäftsvorgang den Zeilen zu, die er liest und schreibt. Ein Code-Abhängigkeitsgraph zeigt, welches Modul ein anderes aufruft. Er zeigt nicht, dass das Buchen einer Rechnung, das Reservieren von Bestand und das Schreiben eines Audit-Eintrags entweder gemeinsam gelingen oder gemeinsam scheitern müssen. Die Datenbank kennt diese Tatsache, weil diese Schreibvorgänge dieselbe Transaktion verwenden.

Beginnen Sie mit Vorgängen, nicht mit Tabellen. Halten Sie für jeden zustandsändernden Befehl den auslösenden Akteur, gelesene und geschriebene Tabellen, Sperren, verwendete Constraints und die Folgen einer teilweisen Ausführung fest. Berücksichtigen Sie Jobs, Trigger, gespeicherte Prozeduren, Dateiimporte und Administratorskripte. Dort verschwindet die vermeintliche Grenze besonders oft.

Eine kompakte Transaktionskarte könnte Bestellung aufgeben so erfassen: Der Vorgang liest Kunde, Produkt und Bestand und schreibt Bestellung, Bestellposition und Bestand. Seine Invariante besagt, dass der Bestand nicht unter null fallen darf; bei einem Teilausfall bleibt eine angenommene, aber nicht erfüllbare Bestellung zurück. Zahlung einziehen liest Bestellung und frühere Versuche und schreibt Zahlung, Buchungseintrag und Bestellstatus unter der Bedingung, dass es pro Bestellung nur einen erfolgreichen Einzug gibt. Bestellung stornieren berührt Versand-, Zahlungs-, Bestands-, Erstattungs- und Bestelldaten, weil versandte Ware einen Rückgabeweg braucht.

Diese Karte deckt zwei verschiedene Kopplungsarten auf. Transaktionale Kopplung bedeutet, dass Schreibvorgänge gemeinsam committen müssen, um eine Invariante zu erhalten. Lesekopplung bedeutet, dass ein Vorgang Daten eines anderen Eigentümers abfragt. Erstere kann eine Trennung verhindern. Letztere braucht oft eine Replik, eine ereignisgespeiste Projektion oder eine explizite Anfrage, verlangt aber nicht automatisch gemeinsames Eigentum. Teams vermischen beides und halten dann entweder alles für immer zusammen oder verteilen eine Transaktion, die nie verteilt werden musste.

Verfolgen Sie Produktionsabfragen ebenso wie Quellcode. Dynamisches SQL, vom ORM erzeugte Anweisungen, Nachtjobs und Aufrufe gespeicherter Prozeduren können statischer Analyse entgehen. Eine Tabelle ohne erkennbare Anwendungsreferenzen kann trotzdem den Monatsabschluss speisen. Wenn das Löschen eines Moduls drei Tage später den Bericht der Buchhaltung verfälscht, ist dieses Modul nicht isoliert.

Gemeinsame Tabellen verschieben Koordinationskosten in die Zukunft

Mit einer gemeinsamen Tabelle können zwei Services schnell liefern, weil sie die Datenbank zu ihrer privaten Integrations-API machen. Der Rechnungsservice fügt eine Zeile ein, der Berichtsservice liest sie direkt und der Fulfillment-Service ergänzt eine Statusspalte. Teuer wirkt das erst, wenn ein Team eine Spalte ändern, einen Wert neu deuten, alte Zeilen nachpflegen oder ein Backup wiederherstellen muss. Dann gehört jeder Leser zur Änderung.

Die Kosten entstehen nicht dadurch, dass zwei Prozesse technisch dieselbe Tabelle abfragen können. Sie entstehen durch unklare Zuständigkeit. Welcher Service darf einen Constraint ergänzen? Wer entscheidet, ob status = 4 verpackt oder versendet bedeutet? Welches Deployment besitzt das Backfill? Wer stellt die Tabelle wieder her, wenn ein Service eine zeitpunktbezogene Wiederherstellung braucht, ein anderer aber bereits neueren Zustand geschrieben hat? Gemeinsamer Speicher macht aus normaler Schemaarbeit eine teamübergreifende Terminfrage.

Bei Fremdschlüsseln ist eine genaue Unterscheidung nötig. Ein Fremdschlüssel innerhalb einer Eigentumsgrenze ist nützliche ausführbare Dokumentation. Ein Fremdschlüssel über geplante Servicegrenzen hinweg bedeutet, dass die Datenbank weiterhin eine serviceübergreifende Invariante erzwingt. Ihn zu entfernen beseitigt die Invariante nicht; die Erkennung wandert nur in Anwendungscode und ungültige Verweise werden möglich. Lassen Sie die Systeme zusammen, bis Sie sagen können, was diese Garantie ersetzt.

Diese Eigentumsabfrage ist ein sinnvoller erster Durchlauf in PostgreSQL:

SELECT table_schema, table_name,
       array_agg(DISTINCT application_name ORDER BY application_name) AS writers
FROM audit_statement_usage
WHERE command IN ('INSERT', 'UPDATE', 'DELETE')
GROUP BY table_schema, table_name
HAVING count(DISTINCT application_name) > 1
ORDER BY table_schema, table_name;

PostgreSQL liefert audit_statement_usage nicht als eingebaute Tabelle. Erzeugen Sie sie aus Datenbank-Audit-Logs oder Anweisungstelemetrie mit mindestens application_name, Befehl, Schema und Tabelle. Entscheidend ist die Form der Ausgabe: Jede Zeile mit mehreren Schreibern braucht eine Eigentumsentscheidung, keinen Endpunkt. Leiten Sie Eigentum nicht allein aus Datenbankrollen ab, wenn mehrere Anwendungen dieselben Zugangsdaten teilen. Diese Gewohnheit macht die Belege unbrauchbar.

Ein gesundes Ziel hat für jede Tabelle genau einen maßgeblichen Schreiber. Andere Komponenten dürfen Kopien erhalten, die für ihre eigenen Abfragen gedacht sind. Eine Kopie hat eine Aktualitätszusage und lässt sich neu aufbauen. Bei einer gemeinsamen Tabelle hängen mehrere Parteien stillschweigend von ihrer aktuellen Form ab, ein wesentlich schwerer sichtbarer Vertrag.

Die Konsistenzanforderung bestimmt die Grenze

Halten Sie Daten in einer Transaktionsgrenze, wenn das Geschäft keinen sichtbaren Zwischenzustand duldet. Trennen Sie sie, wenn eine verzögerte Übereinstimmung akzeptabel ist und Sie festlegen können, wie die Verzögerung behoben wird. Das ist eine Produktentscheidung, die sich im Datenbankverhalten ausdrückt, keine Vorliebe für synchronen oder asynchronen Code.

Zahlungs- und Buchungseinträge machen das deutlich. Wenn das System eine eingezogene Zahlung auch nur kurz ohne zugehörigen Buchungseintrag speichern kann, könnte ein anderer Job sie erstatten, abrechnen oder falsch melden. Sie können diese Schreibvorgänge auf getrennte Services verteilen, brauchen dann aber ein ausdrückliches Protokoll für atomare Absicht, Wiederholungen, Deduplizierung und Abstimmung. Das Netzwerk hat eine lokale Invariante in einen verteilten Ablauf verwandelt. Das kann berechtigt sein, ist aber keine kostenlose Entkopplung.

Die beliebte Empfehlung, zwischen alles einen Message Broker zu setzen, ist falsch, wenn sie vor der Konsistenzentscheidung kommt. Ein Broker transportiert Nachrichten unter festgelegten Bedingungen zuverlässig. Er entscheidet nicht, ob eine Reservierung einer Bestellung hinterherhinken darf, was bei doppelter Zustellung geschieht oder wer eine fehlende Projektion repariert. Das ist Anwendungssemantik. Sie hinter publish() zu verstecken liefert dem Team weniger Belege, nicht mehr.

Stellen Sie für jede geplante Grenze vier konkrete Fragen:

  1. Welchen Zustand können Nutzer oder Jobs zwischen den beiden Commits sehen?
  2. Wie lange darf dieser Zustand inkonsistent bleiben?
  3. Welche Seite wiederholt und wie erkennt der Empfänger ein Duplikat?
  4. Welcher Prozess erkennt und repariert eine Nachricht, die nie den beabsichtigten Zustand erzeugt hat?

Wenn die Antwort auf die zweite Frage praktisch null lautet, bevorzugen Sie eine Transaktion, außer regulatorische Isolation, Skalierung oder organisatorische Zuständigkeit rechtfertigen die verteilte Koordination stärker. Wenn Sekunden oder Minuten akzeptabel sind, schreiben Sie den Grenzwert auf und überwachen ihn. Das Wort eventual ist kein Service-Level-Ziel.

Eine Datenbank bedeutet nicht einen Eigentümer

Sie können echte Servicezuständigkeit innerhalb einer Datenbank schaffen, indem Sie Schemas, Rollen, Migrationen und Schreibrechte trennen. Umgekehrt können getrennte Datenbankserver eng gekoppelt bleiben, wenn Services jede Veröffentlichung abstimmen und synchrone Aufrufe nutzen, um alte Joins nachzubauen. Physische Trennung belegt eine Grenze nur, wenn daraus betriebliche Unabhängigkeit folgt.

Ein sinnvolles Übergangsmodell gibt jeder Komponente ein Schema und einen Login, der nur dort schreiben darf. Schemaübergreifende Lesezugriffe sind erfasste, vorübergehende Ausnahmen. Das Berechtigungsmodell macht versehentliche Schreibvorgänge als Fehler sichtbar:

REVOKE ALL ON SCHEMA billing FROM fulfillment_app;
GRANT USAGE ON SCHEMA billing TO fulfillment_app;
GRANT SELECT ON billing.invoice_summary TO fulfillment_app;
REVOKE INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA billing FROM fulfillment_app;

Geben Sie Zugriff auf eine zweckgebundene Sicht wie invoice_summary, nicht auf jede Billing-Tabelle. Die Sicht bietet dem Eigentümer eine kleine Kompatibilitätsfläche, während Verbraucher zu einer API oder lokalen Projektion wechseln. Erfassen Sie jede Freigabe mit Eigentümer und Entfernungskriterium. Sonst wird die vorübergehende Brücke zur nächsten gemeinsamen Datenbank.

Database-per-Service wird ebenfalls oft missverstanden. Es bedeutet, dass ein Service seinen Persistenzvertrag besitzt und andere Services ihn nicht umgehen können. Es verlangt nicht für jeden kleinen Prozess einen eigenen Datenbankcluster. Getrennte logische Datenbanken können bei Wiederherstellungsgrenzen und Rechten helfen, während Schemas in einer PostgreSQL-Instanz während der Extraktion genügen können. Wählen Sie die Isolation, die Eigentum durchsetzt, ohne den Betrieb zu vervielfachen, bevor sich die Grenze bewährt hat.

Für grenzüberschreitende Joins muss vor der Extraktion ein benannter Ersatz feststehen. Ein lokaler Join kann in einem konsistenten Snapshot filtern, sortieren und paginieren. Mehrere API-Aufrufe können Tausende Datensätze abrufen, ein N-plus-eins-Muster erzeugen und Ergebnisse aus unterschiedlichen Zeitpunkten verbinden. Der Endpunkt kann im Unit-Test korrektes JSON liefern und sich bei realer Kardinalität trotzdem schlecht verhalten.

Wählen Sie den Ersatz passend zur Abfrage. Eine Anzeige, die einen aktuellen Abrechnungsstatus braucht, kann eine direkte Anfrage mit Timeout und festgelegtem Fallback verwenden. Eine Suche, die Bestellungen nach Kundenmerkmalen filtert, braucht meist eine lokale, dafür gebaute Projektion. Ein Offline-Bericht gehört in einen Analysespeicher. Ausgewählte Fakten bewusst zu kopieren ist beabsichtigte Datenduplikation; Live-Aufrufe zu stapeln, bis zufällig ein verteilter Join entsteht, ist versteckte Kopplung.

Paginierung deckt einen häufigen Fehler auf. Angenommen, ein Aufrufer fordert die ersten 50 Bestellungen sortiert nach Kundenrisiko an, aber Bestell- und Risikodaten haben nun andere Eigentümer. Erst 50 Bestellungen zu laden und danach das Risiko abzurufen kann nicht die richtigen ersten 50 liefern. Mehr Bestellungen zu laden ist eine Vermutung mit instabiler Leistung. Verschieben Sie die Rangfolge in ein eigenes Lesemodell oder ändern Sie den Produktvertrag. Netzwerk-Fan-out bewahrt Datenbanksemantik nicht durch Optimismus.

Legen Sie die Aktualität nach der Entscheidung fest, die eine Abfrage unterstützt. Eine Betrugssperre kann aktuellen Status verlangen und bei Unerreichbarkeit des Eigentümers geschlossen ausfallen. Ein Vertriebsdashboard kann einige Minuten Rückstand akzeptieren. Speichern Sie die beobachtete Version oder Aktualisierungszeit in der Projektion, damit Aufrufer diese Regel durchsetzen können. Ohne Herkunftsnachweis wirken zwischengespeicherte Daten aktuell, selbst wenn der Feed seit Stunden steht.

Teilen Sie eine Datenbank nicht nur deshalb, weil eine Tabelle groß ist. Partitionierung, Indizierung, Archivierung und Workload-Isolation lösen Speicher- und Abfrageprobleme direkter. Eine Servicegrenze rechtfertigt ihre Kosten, wenn sie Änderungszuständigkeit oder Fehlerverhalten trennt. Größe allein sagt über beides wenig aus.

Ereignisse brauchen eine atomare Wahrheitsquelle

An echten Datengrenzen neu bauen
CodeHero erfasst den gesamten Legacy-Bestand und baut die Architektur um eigene Postgres-Daten neu.

Verwenden Sie eine transaktionale Outbox, wenn eine bestätigte Datenbankänderung zuverlässig ein Ereignis erzeugen muss. Erst Geschäftsdaten zu schreiben und danach an einen Broker zu senden hinterlässt eine Lücke: Der Commit kann gelingen und die Veröffentlichung scheitern. Erst zu veröffentlichen erzeugt die umgekehrte Lücke. Eine verteilte Transaktion kann sie schließen, erhöht aber die betriebliche Kopplung und wird über die beteiligten Systeme selten sauber unterstützt.

Die Outbox legt Geschäftsschreibvorgang und Ereigniseintrag in denselben lokalen Commit:

BEGIN;
UPDATE orders
SET status = 'confirmed', version = version + 1
WHERE id = :order_id AND status = 'pending';

INSERT INTO outbox_event (event_id, aggregate_id, event_type, payload, created_at)
VALUES (:event_id, :order_id, 'order.confirmed', :payload, CURRENT_TIMESTAMP);
COMMIT;

Ein Relay veröffentlicht nicht gesendete Zeilen und markiert den Fortschritt. Wenn es nach dem Senden, aber vor dem Speichern des Erfolgs abstürzt, kann es ein Ereignis mehrfach senden. Verbraucher brauchen deshalb weiterhin Idempotenz. Speichern Sie eine stabile Ereignis-ID und ändern Sie den Verbraucherzustand nur, wenn diese ID noch nicht verarbeitet wurde. Genau-einmal-Versprechen bedeuten oft mindestens-einmalige Zustellung plus deduplizierte Wirkungen. Benennen Sie, was Sie tatsächlich liefern.

Auch Reihenfolge zählt. Eine einzige globale Sequenz begrenzt den Durchsatz und schafft falsche Kopplung, während ganz ohne Ordnungsregel eine Stornierung die Bestätigung überholen kann. Ordnen Sie Ereignisse pro Aggregat, wo das Geschäft es braucht, fügen Sie eine Aggregatversion hinzu und weisen Sie Lücken zurück oder parken Sie sie. Der Reparaturweg muss so nüchtern sein, dass ein Operator ihn ohne erfundenen Zustand ausführen kann.

Change Data Capture kann ein Outbox-Relay speisen, aber rohe Tabellenänderungen ersetzen keine Domänenereignisse. Eine Zeilenaktualisierung sagt, dass sich Speicher verändert hat. Sie erklärt nicht, ob eine Bestellung bestätigt, korrigiert, importiert oder repariert wurde. Verbraucher, die Absicht aus Spalten zurückrechnen, koppeln sich an genau das Schema, das Sie befreien wollten.

Reporting legt Zuständigkeit offen, die Befehle verbergen können

Betriebliche Servicegrenzen passen selten zu analytischen Fragen. Reporting sollte deshalb eigene Kopien verbinden, statt wieder direkten Zugriff auf jede Produktionstabelle zu erhalten. Ein Bericht zur Kundenrentabilität kann Bestellungen, Erstattungen, Supportkosten und Buchungsdaten benötigen. Lässt man einen Reporting-Service dafür synchron vier operative Services aufrufen, entsteht ein verteilter Join mit schlechterer Latenz und mehr Fehlerarten.

Bauen Sie einen Reporting-Speicher aus Ereignissen, Änderungsfeeds oder geplanten Extrakten und geben Sie ihm klare Aktualitäts- und Abstimmungsregeln. Er darf stark denormalisieren, weil er nicht die operative Wahrheit besitzt. Wenn sich eine Definition ändert, bauen Sie die Projektion aus aufbewahrten Fakten oder einem kontrollierten Snapshot neu auf, statt jede Quelle zu zwingen, jede historische Abfrageform zu erhalten.

Vorsicht vor der gemeinsamen Tabelle customer. Identität, Rechnungsempfänger, Versandempfänger, Kontologin und rechtliche Gegenpartei beginnen oft als eine Zeile und laufen mit wachsendem Geschäft auseinander. Ein universeller Kundenservice kann zur Abhängigkeit fast jeder Anfrage werden. Legen Sie fest, welche Fakten jede Domäne besitzt und welche Kennungen sie verbinden. Den Anzeigenamen eines Kunden zu duplizieren ist oft billiger als die synchrone Verfügbarkeit eines zentralen Profilservice.

Abstimmung macht eventual consistency überprüfbar. Vergleichen Sie Quellanzahlen und Geldsummen anhand stabiler Geschäftsschlüssel, verfolgen Sie das älteste unverarbeitete Ereignis und halten Sie ein Replay-Verfahren bereit. Ein grünes Broker-Dashboard beweist nicht, dass Berichtszeilen mit dem Hauptbuch übereinstimmen. Die Prüfung muss Geschäftsergebnisse vergleichen, nicht Transportaktivität.

In regulierten Umgebungen betreffen Kopien auch Aufbewahrung, Zugriff und Löschpflichten. Eine Projektion bleibt Datenbestand. Erfassen Sie, wohin sensible Felder fließen, begrenzen Sie die Daten pro Verbraucher und weisen Sie Löschung oder Aufbewahrung über Repliken hinweg nach. Getrennter Speicher lässt Governance nicht verschwinden.

Eine sichere Extraktion verschiebt Eigentum vor dem Traffic

Numerische Kerne nach Rust verschieben
CodeHero kann numerischen Legacy-Code nach Rust übertragen und das Umfeld als moderne Services bauen.

Verschieben Sie eine Geschäftsfähigkeit, indem Sie ihren Datenvertrag festlegen, Verhalten im Schattenbetrieb prüfen und Schreibzuständigkeit übertragen, bevor Sie den gesamten Produktionstraffic umleiten. Wer zuerst Code extrahiert, lässt den neuen Service von alten Tabellen abhängig. So kommt der riskanteste Teil spät und unter Termindruck.

Eine verlässliche Reihenfolge lautet:

  1. Wählen Sie eine Fähigkeit mit einem plausiblen Eigentümer und einer tragbaren Konsistenzgrenze. Erfassen Sie jeden Schreiber und Leser ihrer Tabellen.
  2. Sichern Sie das heutige Verhalten mit Charakterisierungstests ab, einschließlich Fehlern, Wiederholungen, Rundung, Nullwerten und Operator-Eingriffen. Erfassen Sie repräsentative Produktionsanfragen nur unter den Datenschutzregeln der Organisation.
  3. Führen Sie das künftige Schema ein und befüllen Sie es mit wiederholbaren Jobs und Checkpoints. Lesen Sie im Schattenbetrieb doppelt und vergleichen Sie Ergebnisse, ohne sie auszuliefern.
  4. Wechseln Sie zu einem maßgeblichen Schreibpfad. Ist vorübergehendes Doppelschreiben unvermeidlich, protokollieren Sie beide Ergebnisse und lassen Sie eine Abstimmung laufen; nehmen Sie nicht an, zwei unabhängige Schreibvorgänge blieben gleich.
  5. Leiten Sie Lesezugriffe und danach Traffic zum neuen Eigentümer, behalten Sie einen getesteten Rollback-Weg und entfernen Sie alte Rechte erst, wenn Verzögerung und Parität innerhalb der vereinbarten Grenzen bleiben.

Schwierig ist die Verbindung von Backfill und laufenden Änderungen. Ein Snapshot beginnt zu einem Zeitpunkt, während die Produktion weiterläuft. Verwenden Sie einen datenbankkonsistenten Snapshot und eine Änderungsposition, danach spielen Sie spätere Änderungen der Reihe nach ein. Machen Sie das Backfill idempotent, damit ein erneut ausgeführter Bereich Zeilen weder dupliziert noch zurücksetzt. Erfassen Sie abgewiesene Zeilen mit genug Kontext für eine Reparatur; eine Migration, die fehlerhafte Altdaten still überspringt, erzeugt ein saubereres Schema und einen falschen Geschäftsdatensatz.

Vermeiden Sie bidirektionale Synchronisierung. Sie sieht nach sicherem Rollback aus, doch Konfliktregeln werden bald zu einer zweiten Anwendung. Benennen Sie während der Umstellung für jedes Feld und jede Phase eine Autorität. Ein Rollback sollte Routing zurückdrehen und ein bekanntes Log wiedergeben, nicht beiden Systemen erlauben, dieselbe Tatsache zu bearbeiten.

Eine Strangler-Fassade kann Aufrufer routen, löst aber keine Datenzuständigkeit. Wenn sie neue Anfragen an einen Service schickt, der weiterhin die Tabellen des Monolithen aktualisiert, hat sich nur die Deployment-Topologie verändert. Messen Sie Fortschritt an stillgelegten Schreibern, entfernten schemaübergreifenden Rechten und entfallenen koordinierten Migrationen.

Paritätstests müssen Geschäftswirkungen vergleichen

Prüfen Sie eine Neufassung anhand beobachtbaren Verhaltens und des entstehenden Zustands, nicht nur anhand ähnlicher HTTP-Statuscodes. Altsysteme enthalten Regeln in Triggern, geplanten Jobs, Standardwerten, Zeichenkettenkürzung, Sortierreihenfolgen, Zeitzonen und manuellen Reparaturen. Eine saubere Neufassung kann logisch überzeugend wirken und trotzdem Rechnungen oder Bestand verändern.

Bauen Sie einen Paritätstest, der dieselbe aufgezeichnete oder synthetische Anfrage an alten und neuen Pfad sendet, erlaubte Unterschiede normalisiert und Antworten sowie Datenbankwirkungen vergleicht. Vergleichen Sie bei einem Befehl erzeugte Zeilen, gebuchte Beträge, gesendete Ereignisse und Fehlerklassen. Vergleichen Sie bei einer Abfrage Sortierung, Paginierung, Nullwertverhalten und Berechtigungsfilter. Maskieren Sie sensible Werte, bevor sie in den Test gelangen, und bewahren Sie das Vergleichsergebnis als Migrationsbeleg auf.

Definieren Sie Toleranzen feldweise. Zeitstempel dürfen innerhalb eines festgelegten Fensters abweichen. Erzeugte Kennungen dürfen anders sein, müssen aber referenzielle Beziehungen erhalten. Dezimalbeträge sollten nach der erklärten Rundungsregel meist exakt übereinstimmen. Ein pauschaler JSON-Diff erzeugt so lange Rauschen, bis Entwickler ihn ignorieren, und macht den Test damit wirkungslos.

Prüfen Sie Fehlerfälle, nicht nur den Normalweg. Beenden Sie das Relay nach dem Veröffentlichen, wiederholen Sie eine Anfrage nach Timeout, liefern Sie Ereignisse in falscher Reihenfolge, stellen Sie einen Datenbank-Snapshot wieder her und setzen Sie einen alten Verbraucher gegen eine neue Ereignisversion ein. Die Grenze ist glaubwürdig, wenn diese Fehler begrenzte Folgen und eine dokumentierte Reparatur haben, nicht wenn das Diagramm ordentlich aussieht.

Hier ist CodeHeros Ansatz für Legacy-Neufassungen relevant: Die Plattform liest den ganzen Codebestand, modernisiert die Architektur und prüft Verhalten mit einem Paritätstest gegen aufgezeichneten Produktionstraffic. Das Versprechen einer Lieferung in unter 30 Tagen wäre leichtfertig, wenn Datenbankwirkungen nicht als Teil des Verhaltens gälten und Quelldateien nur einzeln übersetzt würden.

Kompatible Schemaänderungen ermöglichen unabhängige Deployments

Den gemeinsamen Release-Zug ersetzen
CodeHero macht aus Legacy-Web- und Desktop-Monolithen eigene Go-Services und TypeScript-Clients.

Eine Grenze ist nicht unabhängig deploybar, wenn jede Schemaänderung alle Produzenten und Verbraucher zum Wechsel im selben Release zwingt. Gestalten Sie Datenbank- und Ereignisänderungen so, dass alte und neue Versionen für einen festgelegten Zeitraum parallel funktionieren. Diese Überlappung ermöglicht Deployment, Beobachtung und Rollback, ohne jeden anderen Eigentümer in das Release-Fenster zu holen.

Nutzen Sie Expand and Contract für Felder, die eine Grenze überschreiten. Fügen Sie zuerst die neue Spalte oder das neue Ereignisfeld hinzu, ohne das alte zu entfernen. Lassen Sie Schreiber die neue Darstellung füllen, pflegen Sie historische Zeilen nach und lassen Sie Leser den neuen Wert bevorzugen, aber den alten akzeptieren. Prüfen Sie, dass kein alter Leser übrig ist, beenden Sie dann die Erzeugung des alten Feldes und entfernen Sie es in einer späteren Änderung. Jede Phase braucht ein messbares Endkriterium, etwa null Lesezugriffe auf die alte Spalte über einen vollständigen Betriebszyklus, nicht eine Kalenderschätzung.

Eine Spalte direkt umzubenennen wirkt attraktiv, weil das endgültige Schema sofort sauber aussieht. Zugleich brechen alte Binärdateien, vergessene Jobs und Rollback-Versionen. Fügen Sie den neuen Namen hinzu, kopieren Sie Daten unter einer Autorität und verkleinern Sie später. Die zusätzliche Spalte ist vorübergehende Komplexität mit einem Stilllegungsplan; ein koordinierter Ausfall ist betriebliche Komplexität ohne solchen Plan.

Ereignisschemas brauchen dieselbe Disziplin. Verbraucher sollten unbekannte Felder ignorieren, Produzenten die Bedeutung bestehender Felder nicht ändern und Pflichtfelder einen gültigen Einführungsweg haben. Versionieren Sie den semantischen Vertrag, nicht jede harmlose Ergänzung. Für jedes neue optionale Attribut order.cancelled.v2 zu veröffentlichen füllt das System mit Konvertierungscode und schützt trotzdem nicht vor der gefährlichen Änderung, nämlich einer neuen Bedeutung von cancelled.

Datenbanksichten können für umbenannte oder umgeformte Lesezugriffe ein kurzes Kompatibilitätsfenster bieten. Als dauerhafte APIs taugen sie wenig, wenn Verbraucher von undokumentierten Joins oder Abfrageplänen abhängen. Geben Sie jeder Kompatibilitätssicht ein Ablaufkriterium und beobachten Sie, wer sie noch abfragt. Eine Sicht allein aufgrund einer Repository-Suche zu entfernen übersieht Ad-hoc-Reporting und Binärdateien außerhalb des Haupt-Builds.

Der Rollback zeigt, ob der Kompatibilitätsplan ehrlich ist. Schreibt die neue Anwendung einen Wert, den die alte Version nicht lesen kann, stellt ein Rollback des Binaries den Dienst nicht wieder her. Testen Sie alten Code gegen Zustand aus neuem Code, einschließlich Enum-Werten, Nullfähigkeit, längeren Zeichenketten und Ereignisvarianten. Rückwärtskompatible DDL ist nur die Hälfte; gespeicherte Daten müssen während des gesamten Rollback-Fensters lesbar bleiben.

Unabhängiges Deployment ist deshalb eine Behauptung, die Belege braucht. Zeigen Sie, dass jede Anwendungsversion mit jeder kompatiblen Schemaphase arbeitet, das Backfill fortgesetzt werden kann und Vertragstelemetrie verbleibende Verbraucher identifiziert. Besteht diese Matrix nicht, teilen die Services weiterhin einen Release-Zug, auch wenn Repositorys und Datenbanken verschiedene Namen haben.

Manche Monolithen sollten Monolithen bleiben

Behalten Sie einen Monolithen, wenn ein Team ihn zusammenhängend ändert, seine Transaktionen den Geschäftsinvarianten entsprechen, das Deployment-Risiko beherrscht wird und Skalierung keine unabhängige Platzierung verlangt. Ein modularer Monolith mit erzwungenem Paket- und Schemaeigentum kann fast dieselbe organisatorische Klarheit wie Services bieten, ohne Netzwerkfehler und Flottenaufwand.

Der stärkste Trennungsgrund ist unabhängige Änderungszuständigkeit, gestützt durch eine echte Datengrenze. Weitere gute Gründe sind die Isolation einer fehleranfälligen Last, ein eigener Sicherheitsbereich oder Rechenanforderungen mit anderer Skalierung. Keiner davon entschuldigt ein undefiniertes Konsistenzmodell, kann dessen Kosten aber rechtfertigen.

Entscheiden Sie nicht nach Formeln zur Teamgröße. Zwei Teams können gut in einem Repository arbeiten, während ein Team eine miserable Sammlung winziger Services bauen kann. Teamstruktur zählt, weil Zuständigkeit und Kommunikation das Design prägen, aber die Datenbank erzwingt weiterhin die Fakten. Verlangt jedes Release koordinierte Migrationen, hat die Organisation keine unabhängige Lieferung gewonnen.

Verlangen Sie vor der Freigabe einer Trennung einen kurzen Grenzdatensatz mit den eigenen Tabellen, dem maßgeblichen Schreiber, grenzüberschreitenden Lesezugriffen, Konsistenzfenster, Ereignisvertrag, Wiederherstellungseinheit, Migrationsfolge und Rollback-Autorität. Lehnen Sie den Vorschlag ab, wenn eine Antwort beide Services oder eventually ohne Grenze und Reparaturverfahren lautet. Dieser Maßstab verhindert mehr Schaden als Debatten über die ideale Zahl von Services.

Behandeln Sie auch Wiederherstellung und Notfallvorsorge als Grenztests. Wenn ein Service seine Daten nicht wiederherstellen kann, ohne einen anderen zurückzusetzen, teilen beide eine betriebliche Einheit. Wenn ein Replay nach der Wiederherstellung undokumentierte Änderungen an fremden Tabellen braucht, ist das geplante Eigentumsmodell unvollständig. Üben Sie die Wiederherstellung mit vorhandenen Ereigniswarteschlangen und nachgelagerten Projektionen. Prüfen Sie, dass Verbraucher alte Replays ablehnen und ihre Kopien ohne direkte Schreibzugriffe in das wiederhergestellte Schema neu aufbauen können. Halten Sie dann fest, welche Seite den Wiederherstellungspunkt bestimmt und wie spätere Schreibvorgänge zurückkehren.

Die erste nützliche Änderung ist oft kleiner als eine Serviceextraktion: Beenden Sie gemeinsame Zugangsdaten, protokollieren Sie Schreiber pro Anwendung, geben Sie jeder Tabelle einen Eigentümer und machen Sie Zugriffe über Eigentumsgrenzen sichtbar. Sobald das Schema ehrliche Grenzen zeigt, kann der Code folgen. Wo sich das Schema nicht trennen lässt, sollten Sie darauf hören.

FAQ

Sollte jeder Microservice eine eigene Datenbank haben?

Jeder Microservice sollte seine Persistenz besitzen und verhindern, dass andere Services seinen Vertrag mit direkten Schreibzugriffen umgehen. Das kann getrennte Datenbanken bedeuten, doch Schemas und Rollen in einer Datenbank können während einer Migration ausreichend isolieren.

Ist eine gemeinsame Datenbank für Microservices immer schlecht?

Ein gemeinsamer Server ist nicht automatisch schlecht; gemeinsame Schreibzuständigkeit schon. Wenn Services getrennte Schemas besitzen und über ausdrückliche Leseverträge zugreifen, kann ein Server dauerhaft oder im Übergang sinnvoll sein.

Wie finde ich Transaktionsgrenzen in einem Monolithen?

Verfolgen Sie jeden Geschäftsbefehl zu allen gelesenen und geschriebenen Zeilen, einschließlich Triggern, Jobs, gespeicherten Prozeduren und Administratorskripten. Gruppieren Sie Änderungen, die gemeinsam committen müssen, damit eine Geschäftsinvariante wahr bleibt.

Kann eine API Datenbankkopplung beseitigen?

Eine API verbirgt ein Schema, beseitigt aber keine Kopplung, wenn Aufrufer weiterhin koordinierte Commits, Releases oder Wiederherstellungen brauchen. Die Grenze wird erst besser, wenn der Anbieter die Daten besitzt und Aufrufer seinen Verfügbarkeits- und Konsistenzvertrag tragen können.

Wann sollte ich eventual consistency verwenden?

Nutzen Sie sie, wenn das Geschäft einen benannten Zeitraum der Abweichung akzeptiert und das Team Wiederholung, Deduplizierung, Überwachung und Reparatur beherrscht. Ist die akzeptable Verzögerung null, ist eine lokale Transaktion meist klarer.

Wie teile ich eine gemeinsame Tabelle am sichersten?

Bestimmen Sie einen maßgeblichen Schreiber, schaffen Sie ein eigenes Zielschema, führen Sie ein Backfill ab einer konsistenten Position durch und wenden Sie spätere Änderungen an. Schattenlesevorgänge und Abstimmung sollten vor dem Wechsel Parität belegen.

Löst ein Message Broker verteilte Transaktionen?

Nein. Ein Broker transportiert Nachrichten, aber die Anwendung bestimmt weiterhin Zwischenzustände, Duplikatbehandlung, Reihenfolge und Reparatur. Eine transaktionale Outbox schließt die Lücke zwischen lokalem Commit und Ereigniserzeugung; Verbraucher brauchen trotzdem Idempotenz.

Wie sollte Reporting nach der Aufteilung eines Monolithen funktionieren?

Speisen Sie einen separaten Reporting-Speicher mit Ereignissen, Änderungsfeeds oder kontrollierten Extrakten. Geben Sie ihm Aktualitäts- und Abstimmungsregeln, statt jede operative Datenbank abzufragen oder Services synchron zu verbinden.

Was muss ein Microservice-Rollback umfassen?

Ein Rollback muss Routing, Schemakompatibilität, wartende Ereignisse und Schreibvorgänge des fehlerhaften Releases abdecken. Behalten Sie eine Autorität pro Feld und spielen Sie ein bekanntes Log wieder ein; bidirektionale Schreibzugriffe erschweren Konflikte.

Ist ein modularer Monolith besser als Microservices?

Er ist besser, wenn Transaktionen, Teamzuständigkeit und Deployment weiterhin gemeinsam laufen. Erzwungene Module und Schemarechte schaffen klare Zuständigkeit ohne zusätzliche Netzwerkaufrufe, verteilte Fehlerbehebung und Einzelbetrieb jeder Komponente.