TL;DR
- Geteilte Tabellen machen aus deinem Persistenzmodell einen Integrationsvertrag – damit geht Information Hiding verloren und die Systeme koppeln sich eng.
- Die gemeinsame DB wird zum systemübergreifenden Engpass: Contention, Locks und Fehlermodi eines moduls ziehen sich durch Teams und Services.
- Tabellen liefern Daten, aber keine Intention: CRUD ohne Semantik macht Audits, Debugging und Weiterentwicklung unnötig schmerzhaft.
- Geteilte Strukturen fördern duplizierte Business-Logik und widersprüchliche abgeleitete Werte – selbst wenn die Rohdaten „konsistent“ sind.
- Empfehlung: Setze auf explizite APIs mit klaren Grenzen und Ownership; expose nur, was Consumer wirklich brauchen (und exportiere nicht automatisch dein Datenmodell).
This blog post is also available in English
Vielleicht denkst du gerade: Echt jetzt? Dass gemeinsam genutzte Datenbanktabellen ein schlechter Weg sind, Systeme zu integrieren, ist seit Ewigkeiten bekannt. Haben wir nicht schon vor Jahren aufgehört, Shared Databases zur Systemintegration zu verwenden?
Leider nein! Manchmal ist es der „klassische” Fall: Zwei Systeme teilen sich eine Tabelle oder sogar ein komplettes Schema. Immer öfter ist es aber subtiler: „APIs”, die Objekte nach außen geben, die direkt aus Datenbankschemata abgeleitet sind oder daran kleben. Wenn du also entweder überzeugt bist, dass Datenkbankintegration ein sinnvoller Integrationsweg ist, oder gerade erst anfängst und die Leiden deiner Vorgänger verstehen willst: Dieser Artikel ist für dich.
Was meine ich mit Datenkbankintegration?
Wenn verteilte Systeme über eine gemeinsame Datenbank integrieren, heißt das meist: Sie tauschen Daten aus, indem sie dieselben physischen Tabellen und/oder Views aus demselben Schema lesen und schreiben.
Am Anfang ist Datenbankintegration simpel, manchmal sogar bequem. Du musst überhaupt nicht über API-Design sprechen, sondern sagst einfach: „Wenn du Kundendaten brauchst, hier ist der Connection String. Schau in die Customer-Tabelle.“ Selbst wenn sich Anforderungen ändern – und wir plötzlich nur Kund:innen wollen, die in den letzten 12 Monaten etwas bestellt haben – freut man sich vielleicht, dass sich „die API“ nicht ändern musste.
Auch über Konsistenzgarantien muss man zunächst nicht nachdenken, weil Daten immer aus der „Single Source of Truth“ gelesen werden. Du musst dich nicht mit Eventual Consistency herumschlagen oder befürchten, dass Daten veraltet sein könnten. Wenn beide Systeme dieselben Daten schreiben, können sie Datenbankfeatures nutzen, um alles in einer atomaren Transaktion zu ändern. Relationale Datenbanken sind außerdem gut darin, konkurrierende Änderungen zu erkennen. Es wirkt so, als könnte man viele harte Probleme (API-Design, Concurrency) einfach umgehen, indem man eine Tabelle als API benutzt. Also: Was ist so schlimm daran, eine Tabelle zu teilen?
Schlechtere Performance und Verfügbarkeit
Eine gemeinsam genutzte relationale Datenbank wird zwangsläufig zum Engpass für alle angeschlossenen Systeme. Wer eine Datenbank teilt, teilt auch dieselben Rechenressourcen. Mit jedem neuen Client und jeder weiteren Verbindung steigen CPU- und RAM-Verbrauch. Die Skalierungsgrenze deiner verteilten Anwendung ist damit die Skalierungsgrenze der geteilten Infrastruktur. Und auch wenn „mehr Hardware draufwerfen“ günstig sein kann[1], stößt du irgendwann an physische Grenzen.
Und während du anfangs nicht über Konsistenz nachdenken musstest, konkurrieren die Systeme nun um virtuelle Ressourcen wie Row- und Table-Locks. Selbst wenn du bei Hardware noch Luft hast: Eine lange Transaktion, die viele Tabellen sperrt, kann alle anderen Nutzer:innen der Shared DB effektiv aussperren, weil sie diese ewig auf virtuelle Ressourcen warten lässt. Deine Konsistenzgarantie führt am Ende zu schlechteren Antwortzeiten oder sogar zu Ausfällen.
Früher oder später wird jedes System zum verteilten Systemen
Spätestens in dem Fall verwandeln wir die gemeinsam genutzte Datenbank in ein verteiltes System. Entweder du shardest die Datenbank (also verteilst Daten auf mehrere Server) oder du nutzt Read Replicas und/oder Cluster-Features, um Last von einer Maschine auf mehrere zu verteilen. Je nach Datenbankplattform kann das teuer und/oder komplex werden. Eine Weile funktioniert das vielleicht – aber der Ansatz hat schwere Einschränkungen.
Das CAP-Theorem sagt vereinfacht: Du bekommst immer nur zwei der folgenden drei Garantien gleichzeitig: Alle Daten an einem Ort, Daten sind garantiert konsistent oder ungeinschränkt verfügbar für Reads und/oder Writes. Oder wie Brewer es formuliert hat:
Any distributed data store can provide at most two of the following three guarantees: Consistency [..] Availability [..] or Partition Tolerance.
https://en.wikipedia.org/wiki/CAP_theorem
Nun stehst di vor einem harten Trade-off – und zwar für alle deine Daten. Selbst wenn Eventual Consistency an manchen Stellen okay wäre, musst du dich jetzt für „Alles oder nichts” entscheiden. Aber dieser Trade-off ist nicht einmal das schlimmste an der Datenbankintegration. Denn Datenbanktabellen sind als APIs schlicht grauenhaft.
Warum Tabellen so schlecht als API funktionieren
Es gibt mehrere Nachteile, wenn Datenbanktabellen als Quasi-API zwischen Systemen dienen. Security-Kontrollen sind allenfalls grob. Deine „APIs” transportieren dann keine Intentionen, sondern nur nackte Fakten. Und am schlimmsten: Geteilte Datenstrukturen führen fast zwangsläufig zu massiver Duplikation von Business-Logik und enger Kopplung zwischen verteilten Systemen.
Security-Schmerzen
Ein gemeinsam genutzter Satz Tabellen bedeutet meist ein stumpfes Sicherheitsmodell: Entweder eine App darf eine Tabelle lesen/schreiben oder eben nicht. Das reicht selten. Beispiel: Das Support-Tool soll Adresse und Bestellstatus sehen, aber nicht den internen Risk Score oder Notizen aus Fraud-Investigations. Wenn alles in Shared Tables liegt, ist der bequemste Weg oft: „Gib der Support-App Zugriff auf die ganze Kundenzeile“ – und schon reichen ein Bug, eine zu breite Query oder ein interner Fehler, um Daten offenzulegen, die nie hätten verfügbar sein müssen.
Saubere APIs erlauben fein granulare Zugriffsregeln – bis dahin, sensible Details zu redigieren, außer ein bestimmter Client-Typ fragt sie explizit an. Und das Beste: Du kannst ein Berechtigungsmodell wählen, das zu deinen Anforderungen passt – nicht nur das, was dein Datenbankhersteller gerade unterstützt.
Invarianten lassen sich schwer schützen
Invarianten sind Annahmen im Code, die immer gelten sollen. Vielleicht gehen wir davon aus, dass bestimmte Datenkombinationen nie auftreten dürfen, oder wir nutzen Konventionen, die das System vereinfachen – z.B. beginnen Express-Bestellnummern mit einer 1. Wenn du Tabellen teilst, kannst du Invarianten nur dann erzwingen, wenn auch dein Schema sie erzwingen kann. Für einfache Regeln wie „Pflichtfeld“ (z. B. NOT NULL) geht das noch. Es kommt aber schnell an seine Grenzen, sobald wichtige Prüfungen Dinge verlangen wie: „Lieferungen ab 50 kg dürfen nur dienstags oder donnerstags abgeholt werden“.
Eine echte API schützt Invarianten – und damit Datenintegrität – indem sie Änderungen nur zulässt, wenn alle Invarianten erfüllt sind.
Moment – warum ist der Wert jetzt anders?
Tabellen sagen dir nur, was der aktuelle Wert ist – nicht warum er sich geändert hat. Wenn Systeme in dieselben Tabellen schreiben, bekommst du reines CRUD (Create, Read, Update, Delete) ohne Kontext. Beispiel: Ein credit_limit fällt von 5,000 auf 500. War das ein Kundenwunsch, eine automatische Regel, eine Reaktion auf Betrug oder ein Fehler eines Support-Mitarbeiters? Die Änderung an einer Tabellenspalte sagt es nicht. Audits werden zum Ratespiel, Debugging zur Log-Archäologie, und Teams fangen an, „notes“-Spalten und Sonder-Flags einzubauen.
Bitte zieh deine eigenen Schlüsse
Wer Tabellen teilt, neigt außerdem dazu, nur „Rohfakten“ zu speichern und abgeleitete Werte zu vermeiden, da man gefühlte Duplikation verhindern will. Manchmal ist nicht einmal klar, wer die Berechnungen eigentlich „besitzt“. Und dann leitet jedes System selbst berechnte Werte ab.
Beispiel: Systeme teilen sich eine Transaktions-Tabelle für Ein- und Auszahlungen. Sobald sie den Kontostand brauchen, berechnet ihn jedes System selbst – inklusive duplizierter Logik. Und sie sind sich nicht unbedingt einig: Das eine System zieht ausstehende Zahlungen ab, das nächste ignoriert sie, ein drittes rundet Centbeträge anders. Alle lesen dieselbe Tabelle, aber jede zeigt dem Kunden einen anderen Kontostand. Die Tabelle ist konsistent – die Business-Zahlen sind es nicht. Ergebnis: Streitfälle, manuelle Korrekturen und „Warum passt das nicht zusammen?“-Meetings.
Saubere APIs erlauben es, abgeleitete Werte anzubieten, ohne sie zwingend in der Datenbank speichern zu müssen. Sie können auch je nach Client-Typ unterschiedliche Berechnungen liefern, passend zu dessen Anforderungen. Vor allem aber ermöglichen sie, dass ein System die „Ownership“ für bestimmte Daten und/oder Berechnungen übernimmt und damit die Quelle der Wahrheit für Berechnung und Bedeutung des Modells ist.
Enge Kopplung
Damit sind wir beim größten Nachteil geteilter Tabellen in verteilten Systemen – und der gilt generell für jede geteilte Datenstruktur. Manche der genannten Probleme kann man vielleicht tolerieren oder umschiffen. Aber wer durch weit geteilte Datenstrukturen integriert, schafft dadurch unkontrollierte, enge Kopplung.
Systeme sind eng gekoppelt, wenn eine Änderung in System A ebenfalls eine Änderung in einem eigentlich unabhängigen System B erzwingt. In unserem Kontostand-Beispiel haben wir schon eine subtile Wirkung gesehen: die gemeinsame Nutzung der Transaktionen zwingt uns dazu, dieselbe Business-Logik immer wieder neu zu implementieren. Das erzeugt nicht nur unnötige Arbeit – es macht Änderungen am Modell brutal, weil Dinge überall geändert werden müssen und undklar ist, wer überhaupt was verwendet.
Und selbst wenn unser System gar nicht wissen muss, wie etwas berechnet, entschieden oder abgeleitet wird, so kennen wir trotzdem alle Implementierungsdetails des Datenmodells aus dem Ursprungssystem – weil uns nichts anderes bleibt. Weil eine Änderung am Datenmodell tendenziell jedes System brechen können, führen Unternehmen irgendwann strenge Governance ein und erlauben Änderungen nur, wenn jedes irgendwie verbundene System zustimmt. Damit sind wir nur noch einen Schritt davon entfernt, das Canonical-Data-Model-Anti-Pattern neu zu entdecken.
Kein Information Hiding
Anders gesagt: Unserer „API“ fehlt eine der wichtigsten Eigenschaften überhaupt: Information Hiding. Als konsumierendes System wollen wir nicht an Implementierungsdetails hängen, die uns egal sind. Diese Details sollten verborgen bleiben – sodass wir beispielsweise Änderungen an der Berechnungslogik für einen Kontostand nicht einmal bemerken und erst recht nichts bei uns anpassen müssen. Genau das ist die Art von loser Kopplung, die wir für modulare, unabhängige Systeme wollen. Und das Datenmodell zur Persistenz ist genau das: ein unwichtiges Implementierungsdetail.
Wenn wir jeodch explizite APIs einführen, reicht es leider nicht, unsere Datenstrukturen einfach in einem anderen Format/Protokoll zu präsentieren (z.B. JSON über HTTP). Solange wir Standardformate und -protokolle verwenden, verstecken wir damit höchstens die Programmiersprache in der etwas umgesetzt wurde. Wir sind dann weniger an eine bestimmte Technologie gekoppelt – aber immer noch viel zu stark an die Implementierungsdetails des Datenmodells.
Wir müssen bewusst entscheiden, welche Daten wir anderen Systemen geben – und diese Details in eine explizit dafür entworfene API-Datenstruktur mappen. Und genau daran scheitern selbst manche gut entworfene verteilte Systeme, die keine Datenbank teilen: Sie nehmen eine bequeme Abkürzung. Diese subtile Form der „Datenbankintegration durch die Hintertüre“ hat in den letzten Jahren an Fahrt aufgenommen: APIs werden über isomorphe Projektionen[2] generiert.
Subtil versteckte Datenbankintegration
Generative Ansätze versprechen Tempo und Bequemlichkeit, weil sie repetitive, langweilige Aufgaben reduzieren – zum Beispiel das Mapping von Datenobjekten. Besonders beliebt sind sie in Rapid-Development-Frameworks (etwa Django für Python oder Symfony für PHP). In RAD Frameworks generierst du dein DB-Schema direkt aus einem Objektmodell (z. B. im Django ORM).
Es ist verlockend, den gleichen Mechanismus auch fürs API zu nutzen und es aus demselben Modell zu generieren. Es ist ein populäres Feature im Django Rest Framework. Aber Vorsicht: Diese generativen Ansätze teilen zwar nicht alle oben genannten Nachteile der Datenbankintegration – sie teilen aber den größten: Sie legen das zugrunde liegende Datenmodell deiner Anwendung für anderen Systemen offen. Damit brichst du wieder unser Designprinzip für lose gekoppelte, modulare Systeme: Information Hiding.
Es ist nichts falsch daran, Isomorphismen zu nutzen, um repetitiven, langweiligen Code zu reduzieren. Aber du solltest entscheiden, was und wie gemappt wird – und was nicht. Eine praktische Methode ist, deine API-Datenstrukturen (manche nennen diese Objekte DTOs) separat zu modellieren und dann etwas „Automagie“ zu nutzen, um diese Objekte zu befüllen. Aber woher weißt du, was du zeigen solltest und was nicht? Sprich mit deinen API-Konsument:innen!
Fazit
Datenbankintegration ist eine riskante Integrationsstrategie – besonders dann, wenn es auf die unabhängige Weiterentwicklung von verteilten Systemen ankommt. In gut gestalteten verteilten Systemen fließen Daten durch die Systeme und sind selten zentralisiert.
Auch wenn es legitime Anwendungsfälle gibt um Datenstrukturen zu teilen, sollte es nicht deine erste oder einzige Integrationsstrategie sein. Sei vorsichtig. Achte auf die Kopplung, die du dir durch geteilte Datenstrukturen einhandelst, und veröffentliche nicht einfach alles – nur weil du keine Lust auf API-Design-Trade-offs hast oder dein Framework dir das bequem macht.
Wenn du nicht manuell entscheidest, welche Daten du in deiner API nach außen gibst, gibst du sehr wahrscheinlich mehr preis, als du solltest. Mit den Worten von Alberto Brandolini: „No, you don’t need to access my data!” – und das gilt vermutlich für jedes verteilte System, in dem mehr als ein Team arbeitet oder Modularität besonders wichtig ist.
Echte APIs haben natürlich ihren Preis (Versionierung, Mapping-Logik, Umgang mit Fehlermodi, die passenden Konsistenzgarantien finden – um nur ein paar zu nennen). Aber diese Kosten sind es wert, weil sie dir die Unabhängigkeit geben, die aus klar definierten Grenzen, Ownership und expliziten Verträgen entsteht.
-
Was keineswegs garantiert für immer so bleibt. Während ich das Anfang 2026 schreibe, erleben wir einen Preisanstieg bei DRAM, ausgelöst durch den massiven Ausbau von KI-Rechenzentren. ↩︎
-
Das ist eine schicke mathematische Art zu sagen: Man kann in beide Richtungen abbilden – man kann die API-Struktur aus dem Anwendungsdatenmodell ableiten und umgekehrt das Anwendungsmodell aus der API-Struktur. Das Mapping passiert automatisch und oft implizit durch ein Tool. ↩︎