Datenarchitektur · Referenzaufbau
Drei Abteilungen beantworten dieselbe Frage und legen drei verschiedene Zahlen vor. Niemand hat sich verrechnet. Das ist kein Rechenproblem, sondern ein Architekturproblem — und die Lösung dafür heißt seit dreißig Jahren im Kern gleich, auch wenn die Werkzeuge jedes Jahr neue Namen bekommen.
01 — Das Problem
Diese Frage begleitet die ganze Seite. Sie klingt trivial, und in einem Unternehmen mit einem Abo-Produkt bekommt man auf sie regelmäßig drei verschiedene Zahlen.
Marketing
Zählt jeden, der im Juli ein gültiges Abo hatte — auch wenn er am 3. Juli gekündigt hat und am 31. Juli nicht mehr da war.
Finance
Zählt nur, wer am Monatsletzten zahlend war. Gekündigte in der Restlaufzeit zählen nicht mit, Testphasen auch nicht.
Product
Zählt nur, wer sich im Juli tatsächlich eingeloggt hat. Wer zahlt, aber nicht nutzt, ist für Product nicht aktiv.
Alle drei Zahlen sind richtig. Keine der drei Abteilungen hat sich verrechnet. Sie beantworten drei verschiedene Fragen, die zufällig denselben Namen tragen — und keine von ihnen hat je aufgeschrieben, welche.
Genau dafür gibt es die Schichten, um die es im Folgenden geht. Sie sind keine Bürokratie und keine Mode. Sie sind der Ort, an dem aus Daten ein Begriff wird — und der Grund, warum die Frage am Ende genau eine Antwort hat.
Diese Seite beschreibt ein Referenzmuster, keine konkrete Installation. Alle Zahlen, Tabellennamen und Beispiele sind erfunden. Die Werkzeuge — dlt, BigQuery, dbt, Metabase, Terraform — sind austauschbar; das Muster funktioniert genauso mit Airbyte, Snowflake, SQLMesh oder Superset.
Was nicht austauschbar ist, sind die Regeln zwischen den Schichten. Um die geht es.
02 — Der Aufbau
Ein Data Warehouse hat Schichten aus demselben Grund, aus dem ein Wasserwerk Filter hat: Man will an einer Stelle wissen, wie sauber das ist, was herauskommt. Jede Schicht hat genau eine Aufgabe und genau eine Regel darüber, was sie nicht tun darf.
Interaktiv
Klick auf eine Schicht: Was steht dort, wer darf sie abfragen, und wie sieht unser Beispiel dort aus?
Die eine Regel
Jede Schicht liest nur aus der Schicht direkt darunter. Kein Datamart greift auf Bronze zu, kein Dashboard auf Silver. Wer diese Regel bricht, spart einmal eine Stunde und zahlt sie ein Jahr lang zurück.
Der Preis dafür ist Redundanz: Dieselbe Information liegt in vier Formen herum. Das ist Absicht. Speicher ist billig, Rekonstruktion ist teuer, und eine Schicht, die man jederzeit neu bauen kann, ohne das Quellsystem zu fragen, ist die eigentliche Versicherung.
03 — Die Ladung
Die klassische Reihenfolge hieß ETL: extrahieren, transformieren, laden. Transformiert wurde unterwegs, im Ladeprozess. Heute lädt man zuerst und transformiert danach — ELT — und der Grund dafür ist weniger technisch als organisatorisch.
ETL, alte Ordnung
Was der Ladeprozess wegwirft, ist weg. Ein Denkfehler in der Logik von 2023 lässt sich 2026 nicht mehr reparieren — die Rohdaten wurden nie gespeichert.
ELT, heutige Ordnung
Das Rohmaterial bleibt liegen. Stellt sich eine Logik als falsch heraus, baut man die Schichten darüber neu — ohne das Quellsystem noch einmal zu belasten.
In diesem Aufbau übernimmt dlt das Laden. Es ist eine Python-Bibliothek, kein Dienst: Man schreibt eine Funktion, die Datensätze liefert, und dlt kümmert sich um den Rest — Zieltabelle anlegen, Typen ableiten, neue Felder ergänzen, Ladestand merken.
# Die Quelle liefert Datensätze. Mehr muss sie nicht wissen.
@dlt.resource(name="abos", write_disposition="merge", primary_key="id")
def abos(aktualisiert_ab=dlt.sources.incremental("updated_at")):
yield from api.hole_seiten("/subscriptions", seit=aktualisiert_ab.last_value)
pipeline = dlt.pipeline(destination="bigquery", dataset_name="bronze_billing")
pipeline.run(abos)
Inkrementell
Ein Zeitstempel oder eine laufende Nummer merkt sich, wo der letzte Lauf aufgehört hat. Vollladungen sind ein Notfallwerkzeug, kein Tagesgeschäft.
Idempotent
Läuft ein Job doppelt, muss dasselbe herauskommen. Über einen Schlüssel und
merge statt append ist das geschenkt.
Schema-Evolution
Fügt die Quelle eine Spalte hinzu, wird sie angelegt. Ändert sie einen Typ, gibt es eine Warnung statt eines stillen Datenverlusts.
Verlockend ist, schon beim Laden „nur das Nötige“ zu holen und dabei aufzuräumen — Felder weglassen, Werte umbenennen, Zeilen filtern. Sechs Monate später fragt jemand nach genau diesen Feldern.
Die Ladung hat eine einzige Aufgabe: vollständig und unverändert ankommen. Alles Weitere passiert danach, wo man es zurücknehmen kann.
04 — Vier Stationen
Die gängigste Benennung dafür ist die Medaillon-Architektur: Bronze, Silver, Gold — darüber die Datamarts und das Reporting. Die Namen sind Etiketten für drei Vorgänge, die man auseinanderhalten sollte: aufräumen, verstehen, zuschneiden. Jede Schicht macht genau eines davon.
| Schicht | Aufgabe | Darf | Darf nicht |
|---|---|---|---|
| Bronze | Aufbewahren | Genau das speichern, was kam — plus Ladezeitpunkt und Quelle. | Irgendetwas ändern. Von Menschen abgefragt werden. |
| Silver | Aufräumen | Umbenennen, typisieren, entdoppeln, offensichtlichen Müll filtern. Ein Modell pro Quelltabelle. | Joinen. Geschäftslogik enthalten. Aggregieren. |
| Gold | Verstehen | Quellen zusammenführen, Begriffe definieren, Historie führen. Dimensional modelliert. | Auf eine einzelne Abteilung zugeschnitten sein. |
| Datamarts | Zuschneiden | Breit, flach, sprechend benannt. Eine Abteilung, ein Themenbereich. | Neue Begriffe erfinden, die es in Gold nicht gibt. |
| Reporting | Ausliefern | Dashboards, automatische Berichte, Sprachmodellzugriff. Lesend. | Rechnen. Joinen. Begriffe erfinden. |
Interaktiv
Dieselbe Frage, vier Schichten. Klick dich durch und sieh, wie die Abfrage von unten nach oben kürzer und verständlicher wird — und wo die Arbeit dafür hingewandert ist.
Jedes Modell ist eine Datei mit einem SELECT darin. Referenzen laufen
nicht über Tabellennamen, sondern über ref() — daraus leitet dbt
selbst ab, in welcher Reihenfolge gebaut werden muss, und zeichnet nebenbei den
Abhängigkeitsgraphen.
select
k.kunde_id,
k.email_domain,
t.tarif_name,
k.gueltig_ab,
k.gueltig_bis,
k.ist_aktuell
from {{ ref('stg_billing__kunden') }} as k
left join {{ ref('stg_billing__tarife') }} as t
on k.tarif_id = t.tarif_id
Dazu kommen Tests als Teil des Modells, nicht als nachgelagerte Übung. Ein Test ist eine Behauptung über die Daten, die bei jedem Lauf geprüft wird.
models:
- name: dim_kunde
columns:
- name: kunde_id
tests: [not_null]
- name: ist_aktuell
tests:
- accepted_values: { values: [true, false] }
# genau eine aktuelle Zeile pro Kunde
tests:
- unique_combination: { columns: [kunde_id, ist_aktuell], where: "ist_aktuell" }
Faustregel
Ein Test ist billig zu schreiben und teuer zu vermissen. Die zwei, die sich immer lohnen: Der Schlüssel ist eindeutig und der Schlüssel ist nicht leer. Fast jeder stille Datenfehler, der es bis ins Dashboard schafft, verletzt einen von beiden.
05 — Der Kern
Die Form, die Ralph Kimball in den Neunzigern beschrieben hat, hat seitdem niemand ernsthaft verbessert: Fakten in der Mitte, Dimensionen außen herum. Ein Stern.
Sternschema
In der Mitte, was passiert ist. Außen herum, worüber man es aufschlüsseln möchte.
Dieser Satz heißt Grain — die Körnung. Er wird zuerst festgelegt, vor jeder Spalte, und er wird aufgeschrieben. Ist der Grain unklar, addieren sich Kennzahlen falsch, und niemand merkt es, weil das Ergebnis plausibel aussieht.
In unserem Beispiel: Eine Zeile ist ein Ereignis im Lebenslauf eines Abos — Abschluss, Tarifwechsel, Kündigung, Reaktivierung. Nicht „ein Abo“, nicht „ein Kunde pro Monat“. Aus dieser Wahl folgt alles andere.
Ein Kunde wechselt im Mai von Basis auf Premium. Fragt man im Juli „wie viele Premium-Kunden hatten wir im Januar“, lautet die richtige Antwort: er war im Januar keiner. Eine Dimension, die nur den heutigen Stand kennt, kann das nicht beantworten.
Interaktiv
Ein Kunde, drei Tarifwechsel. Schieb den Stichtag und vergleiche, was eine überschreibende Dimension (Typ 1) und eine historisierende (Typ 2) antworten.
Slowly Changing Dimension, Typ 2
Statt die Zeile zu überschreiben, wird die alte geschlossen und eine neue geöffnet.
Jede Zeile trägt gueltig_ab, gueltig_bis und ein Kennzeichen
ist_aktuell. Der Fakt zeigt auf die Version, die zum Zeitpunkt des
Ereignisses galt — nicht auf die heutige.
Typ 2 kostet Komplexität: mehr Zeilen, kniffligere Joins, ein Schlüssel, der nicht mehr der fachliche ist. Für eine Tippfehlerkorrektur im Kundennamen ist das Unsinn — dort überschreibt man (Typ 1) und gut.
Die Frage lautet nicht „historisieren wir?“, sondern: Wird jemand diese Eigenschaft je rückwirkend auswerten wollen? Bei Tarif, Segment und Land lautet die Antwort ja. Bei der Schreibweise des Vornamens nicht.
06 — Die Auflösung
Zurück zu den drei Zahlen vom Anfang. Die Lösung ist nicht, sich auf eine zu einigen — alle drei werden gebraucht. Die Lösung ist, ihnen drei verschiedene Namen zu geben und jeden davon genau einmal zu definieren.
| Name | Definition | Wer fragt danach |
|---|---|---|
| abos_im_zeitraum | Abos, die zu irgendeinem Zeitpunkt im Zeitraum gültig waren. | Marketing, Reichweite |
| zahlende_abos_stichtag | Abos, die am Stichtag zahlend waren. Ohne Testphasen, ohne gekündigte in Restlaufzeit. | Finance, Umsatz |
| genutzte_abos_im_zeitraum | Abos mit mindestens einer Anmeldung im Zeitraum. | Product, Bindung |
Sobald jede Kennzahl einen eindeutigen Namen hat, verschwindet der Streit. Er wird ersetzt durch eine sehr viel bessere Frage: Welche der drei meinst du?
Nicht im Dashboard, und das ist der ganze Punkt. Eine Formel, die in einem BI-Werkzeug eingetippt wurde, ist nicht versioniert, nicht getestet, nicht auffindbar und existiert am Ende so oft, wie es Dashboards gibt.
select
monat,
count(distinct case when gueltig_im_monat then abo_id end) as abos_im_zeitraum,
count(distinct case when zahlend_am_letzten then abo_id end) as zahlende_abos_stichtag,
count(distinct case when hat_anmeldung then abo_id end) as genutzte_abos_im_zeitraum
from {{ ref('int_abo_monat_status') }}
group by monat
Wer weiter gehen will, legt eine semantische Schicht darüber: Metriken, Dimensionen und erlaubte Verknüpfungen als Metadaten, aus denen die Abfrage erzeugt wird. Das lohnt sich ab einer gewissen Größe — und wird im vorletzten Kapitel noch einmal wichtig, wenn ein Sprachmodell die Fragen stellt.
07 — Die Oberfläche
Metabase steht hier stellvertretend für jedes BI-Werkzeug. Die interessante Frage ist nicht, welches man nimmt, sondern wie viel Logik man hineinlässt.
darf
Auf Datamarts zugreifen, nach Zeitraum und Dimension filtern, gruppieren, darstellen, teilen. Genau dafür ist es gebaut.
mit Bedacht
Anteile, Differenzen zum Vormonat, Sortierungen. Solange die Größen darunter definiert sind und die Rechnung eine Zeile ist.
darf nicht
Joins über mehrere Datamarts, Filter, die eine Geschäftsregel kodieren, neue Kennzahlen. Das gehört ins Modell, wo es getestet und gefunden werden kann.
Der Prüfstein
Wenn jemand fragt „woher kommt diese Zahl?“ — kann man ihm eine Datei im Git zeigen? Wenn die Antwort lautet „das hat jemand mal ins Dashboard getippt“, gehört die Logik eine Schicht tiefer.
Datamarts breit und sprechend zu bauen, damit die Oberfläche nichts mehr tun muss.
ist_gekuendigt statt status_id = 7.
monat_beginn als fertige Spalte statt einer Datumsfunktion im Dashboard.
Jede Verrenkung, die ein Nutzer im BI-Werkzeug macht, ist ein Hinweis auf eine
fehlende Spalte im Datamart.
Und: Rechte auf Datensätze, nicht auf Dashboards. Wer nur seine Region sehen darf, soll das über eine Zeilenfilterregel bekommen, nicht dadurch, dass man ihm bestimmte Kacheln vorenthält.
08 — Der Automat
Die meisten Fragen an ein Data Warehouse sind wiederkehrend. Dafür braucht niemand ein Dashboard zu öffnen — es reicht, wenn die Antwort erscheint.
Abonnement
Montags um sieben der Wochenbericht in den Posteingang oder den Team-Kanal. Nützlich, solange jemand widerspricht, wenn eine Zahl komisch aussieht.
Schwelle
Meldung, wenn die Kündigungsquote über einen Wert steigt oder eine Ladung ausbleibt. Deutlich wertvoller als der tägliche Bericht, den keiner liest.
Frische
Ein Test auf das Alter der jüngsten Zeile. Schlägt er an, ist der Bericht falsch, bevor ihn jemand liest — und man erfährt es vorher.
Berichte vermehren sich und sterben nie. Nach zwei Jahren laufen sechzig Stück, von denen ein Drittel niemand mehr öffnet — sie kosten trotzdem Rechenzeit, erzeugen trotzdem Warnungen und müssen trotzdem gepflegt werden.
Die meisten BI-Werkzeuge protokollieren, welche Frage wie oft aufgerufen wurde. Dieses Protokoll ist selbst eine Datenquelle. Einmal im Quartal auswerten und abschalten, was seit sechs Monaten niemand angesehen hat — das ist die undankbarste und lohnendste halbe Stunde im Jahr.
09 — Der Zugang
Die Vorstellung ist verlockend und mittlerweile technisch erreichbar: Jemand fragt in normaler Sprache, ein Sprachmodell schreibt die Abfrage, die Antwort kommt zurück. Kein Ticket, kein Dashboard, keine Wartezeit.
Das funktioniert — aber nur oberhalb einer bestimmten Schicht. Und der Grund dafür ist genau derselbe, aus dem es die Schichten überhaupt gibt.
Interaktiv
Dieselbe Frage, an zwei verschiedene Schichten gerichtet. Wähle, worauf das Modell zugreifen darf.
Nicht, weil das Modell zu dumm wäre. Sondern weil die Information, die es bräuchte, dort schlicht nicht steht.
Namen
sub_st, flg_act, dt_c. Das Modell rät.
Manchmal rät es richtig.
Auswahl
Fünf Tabellen enthalten etwas, das „Kunde“ heißen könnte. Drei davon sind veraltet, eine ist ein Testimport.
Regeln
Dass Testphasen nicht als zahlend gelten, steht in keinem Schema. Es steht in einem Kopf oder in einem dbt-Modell.
Verknüpfungen
Ein Join über den falschen Schlüssel vervielfacht Zeilen. Die Summe ist dann zu hoch — aber nicht so hoch, dass es auffällt.
Die kürzeste Zusammenfassung: Ein Modell soll nicht raten, sondern nachschlagen. Alles Weitere folgt daraus.
1 — Auf einem Datamart ansetzen, nicht auf Bronze
Ein gut gebauter Datamart erledigt den größten Teil der Arbeit von allein: sprechende
Spaltennamen, keine Mehrdeutigkeit, Geschäftsregeln bereits angewendet. Aus
status_id = 7 ist ist_gekuendigt geworden — und
das versteht ein Sprachmodell ohne Anleitung.
2 — Werkzeuge statt freiem SQL
Statt „hier ist die Datenbank, schreib eine Abfrage“ bekommt das Modell einzelne Funktionen: Schema nachschlagen, Kennzahlendefinition abrufen, Spaltenwerte auflisten, eine Abfrage ausführen. Ein MCP-Server ist genau dafür gebaut. Jeder Schritt ist damit prüfbar, statt dass am Ende ein Block SQL herausfällt, den niemand liest.
3 — Die Kennzahl nachschlagen lassen
Fragt jemand nach „aktiven Abos“, soll das Modell nicht selbst entscheiden, was das heißt. Es soll die Definition abrufen — und wenn es drei gibt, zurückfragen. Genau hier zahlt sich die Semantikschicht aus Kapitel 6 aus.
4 — Enge Grenzen ziehen
Ausschließlich lesender Zugriff über ein eigenes Dienstkonto. Nur auf Datamarts, nicht auf Gold. Ein Limit für das gescannte Datenvolumen und die Laufzeit — sonst erzeugt eine unglückliche Frage eine Rechnung. Und: das erzeugte SQL wird immer angezeigt, nicht nur das Ergebnis.
Für Erkundung ist das hervorragend: Nachfragen, Aufschlüsselungen, „zeig mir das mal nach Kanal“. Genau die Fragen, für die früher ein Ticket geschrieben wurde und die eine Woche später beantwortet zurückkamen, obwohl sie zehn Minuten Arbeit waren.
Für Zahlen, die in einen Quartalsbericht wandern, gilt etwas anderes. Dort ist eine Kennzahl das Ergebnis eines geprüften Modells, nicht eines Gesprächs. Der Weg dahin führt über den umgekehrten Ablauf: Das Gespräch findet die Frage, das Modell beantwortet sie dauerhaft.
Wenn ein Sprachmodell auf schlecht modellierten Daten schlechte Antworten gibt, ist die Versuchung groß, das mit einer längeren Anweisung zu reparieren — alle Sonderregeln in den Systemprompt schreiben.
Das ist dieselbe Verlegung von Logik an den falschen Ort wie eine Formel im Dashboard, nur schlechter: nicht versioniert, nicht getestet, und bei jeder Modellversion wieder anders wirksam. Wenn die Anweisung erklären muss, was eine Spalte bedeutet, gehört diese Erklärung ins Datenmodell.
10 — Der Unterbau
Ein Data Warehouse besteht nicht nur aus Tabellen. Es besteht aus Datensätzen, Berechtigungen, Dienstkonten, Zeitplänen, Verbindungen und einer Handvoll Einstellungen, die irgendwann jemand in einer Weboberfläche geklickt hat.
Genau diese geklickten Einstellungen sind das Problem. Sie stehen nirgends, niemand weiß mehr warum, und bei einem Umzug fehlen sie.
resource "google_bigquery_dataset" "mart_finance" {
dataset_id = "mart_finance"
location = "EU"
description = "Datamart Finance. Lesend fuer die Gruppe finance-analysts."
}
# Der Lesezugriff steht hier — nicht in einem Klickpfad.
resource "google_bigquery_dataset_iam_member" "mart_finance_leser" {
dataset_id = google_bigquery_dataset.mart_finance.dataset_id
role = "roles/bigquery.dataViewer"
member = "group:finance-analysts@beispiel.de"
}
Terraform
Datasets, Rollen, Dienstkonten, Zeitpläne. Eine zweite Umgebung zum Testen ist damit eine Kopie mit anderem Präfix statt eines Nachmittags Klickarbeit.
dbt
Transformationen, Tests, Beschreibungen. Der Abhängigkeitsgraph fällt dabei ab, ohne dass ihn jemand pflegen muss.
Git und CI
Jede Änderung als Pull Request, jeder Pull Request baut gegen eine Testumgebung und lässt die Tests laufen. Was rot ist, geht nicht live.
Die Probe
Könnte man das gesamte Warehouse in einem leeren Projekt neu aufbauen, allein aus dem, was im Git steht? Wenn nein: Die Lücke zwischen Repository und Wirklichkeit ist genau das Stück, das beim nächsten Ausfall fehlt.
11 — Die Kehrseite
Bis hierher klang alles nach einer Ordnung, die von selbst hält. Tut sie nicht. Die folgenden fünf Punkte kosten in der Praxis mehr Zeit als der ganze Aufbau.
BigQuery berechnet nicht die Laufzeit, sondern das gescannte Datenvolumen.
Ein Dashboard, das stündlich select * über eine große Faktentabelle
legt, kostet mehr als das gesamte Team an Rechenzeit verbraucht. Partitionierung nach
Datum und Clustering nach dem häufigsten Filter sind keine Feinarbeit, sondern die
Grundeinstellung.
Das wirksamste Gegenmittel ist unspektakulär: Kostenkontrollen einrichten, bevor man sie braucht. Ein Limit pro Abfrage, ein Limit pro Nutzer und Tag, und ein Blick auf die teuersten Abfragen der Woche.
Ein Feld wird umbenannt, ein Statuswert kommt hinzu, ein API-Endpunkt liefert plötzlich Seiten statt einer Liste. Niemand sagt Bescheid, weil niemand weiß, dass jemand mitliest.
Deshalb: Tests auf akzeptierte Werte, nicht nur auf Nichtleere. Ein neuer Statuswert
soll den Lauf rot färben, nicht stillschweigend in einer else-Kategorie
verschwinden.
Irgendwann ist es dringend, und jemand baut einen Datamart direkt aus Bronze. Es funktioniert, es ist schnell, und es ist der Anfang vom Ende — denn ab dann gibt es zwei Wege zur selben Zahl, die auseinanderlaufen werden.
Bei drei Quelltabellen und zwei Auswertungen ist diese Architektur Überbau. Vier Schichten für ein Modell, das man in einer Abfrage schreiben könnte, kosten mehr, als sie bringen.
Die Schwelle liegt ungefähr dort, wo mehr als eine Person dieselben Daten für mehr als einen Zweck nutzt. Vorher ist Disziplin billiger als Struktur. Nachher ist es umgekehrt — und der Übergang tut weh, weil man ihn immer zu spät bemerkt.
Eine kaputte Funktion fällt auf. Ein Datamart, dessen fachliche Annahme seit einem Jahr nicht mehr stimmt, fällt nicht auf — er liefert weiter Zahlen. Nur eben die falschen.
Dagegen hilft kein Werkzeug, sondern ein Termin: einmal im Quartal mit der Fachseite durch die Kennzahlendefinitionen gehen und fragen, ob sie noch das messen, was gemeint war.
Nichts davon spricht gegen den Aufbau. Es spricht dagegen, ihn für fertig zu halten. Ein Data Warehouse ist kein Projekt mit einem Abschlussdatum, sondern ein Ort, an dem laufend verhandelt wird, was ein Wort bedeutet.
Der Erfolg lässt sich an einer einzigen Frage messen — derselben wie am Anfang: Wenn drei Abteilungen nach den aktiven Abos im Juli fragen, bekommen sie dann dieselbe Zahl? Oder wenigstens drei Zahlen, die verschiedene Namen tragen und von denen jeder weiß, welche er meint?
12 — Quellen
Der Klassiker zur dimensionalen Modellierung ist Ralph Kimball und Margy Ross, The Data Warehouse Toolkit. Er ist dreißig Jahre alt, redet von Werkzeugen, die es nicht mehr gibt, und ist trotzdem das Buch, aus dem Grain, Fakten, Dimensionen und die Typ-1/Typ-2-Unterscheidung stammen.
Zur heutigen Umsetzung: die Dokumentation von dbt — insbesondere die Abschnitte zu Modellierung und Tests — und die von dlt zu inkrementellem Laden und Schema-Evolution. Für BigQuery lohnt der Abschnitt zu Partitionierung und Clustering; er spart mehr Geld als jede andere halbe Stunde Lektüre.
Zum Zugriff durch Sprachmodelle: die Spezifikation des Model Context Protocol. Sie beschreibt das Muster, das im neunten Kapitel gemeint ist — Werkzeuge mit klarem Zuschnitt statt freiem Datenbankzugriff.
Weiterlesen