Zum Inhalt springen
MB

Produkte berichten über ihre API

Produktstatistiken bleiben verständlich, wenn jedes System seine Daten selbst verdichtet und über einen kleinen, zeitlich einheitlichen HTTP-Vertrag anbietet.

≈ 9 Min. LesezeitSoftwareArchitekturAPIDelivery

Diesen Beitrag anhören

0:00 / 14:24

Eine Statistik-API ist eine Produktfunktion. Dieser Satz klingt selbstverständlich, wurde in der Arbeit an einem produktübergreifenden Reporting aber erst nach zwei schlechteren Entwürfen klar. Zunächst sollte ein zentraler Sammler direkt auf mehrere PostgreSQL-Datenbanken zugreifen. Danach standen besondere private Reporting-Endpunkte und eigene Schlüssel im Raum. Beides hätte den Report tiefer in die Produkte eingebaut, als es für seinen Zweck nötig ist.

Übrig blieb ein kleinerer Vertrag. Ein Publishing-System, eine Wissensbasis und ein Orchestrator verdichten ihre eigenen Daten selbst und bieten ihre Sicht über normale, authentifizierte HTTP-Endpunkte an. Ein nachgelagerter Ablauf führt die Aggregate für ausdrücklich begrenzte Zeitfenster zusammen – er sieht weder Tabellen noch Markdown, Namen, Schlüssel oder interne IDs.

Diese Aufteilung ist kein spezieller Kniff für Dashboards. Jeder autorisierte Verbraucher kann dieselben Statistikressourcen verwenden: ein Kapazitätsreport, eine Qualitätsprüfung, eine lokale Auswertung oder ein späteres Exportwerkzeug.

Datenbesitz ist mehr als Speicherort#

Wer eine Tabelle besitzt, kennt ihre Bedeutung. Ein Publishing-System weiß, wann eine Revision erstellt und ein Release tatsächlich aktiviert wurde. Eine Wissensbasis unterscheidet einen gestarteten Ingest von einem geprüften Wissensstand. Ein Orchestrator verdichtet seine Ausführungsdaten dort, wo die Läufe tatsächlich stattfinden. Ein zentraler Sammler müsste diese Bedeutungen nachbauen und bei jeder Produktänderung mitziehen.

Direktes SQL macht diesen Nachbau besonders leicht – und damit gefährlich. Eine Abfrage funktioniert, obwohl sie vielleicht einen Zwischenstatus mitzählt, den das Produkt selbst niemals als abgeschlossen anzeigen würde. Eine neue Migration kann den Report brechen, ohne dass sich die öffentliche Produkt-API geändert hat. Der Datenbankzugriff umgeht außerdem die normale Rechteprüfung und zieht Credentials mehrerer Systeme an eine Stelle.

Mit einer Produkt-API liegt die fachliche Verdichtung dort, wo auch die Zustandsänderung implementiert ist:

Fachliche Verdichtung bleibt im Produkt

  1. Produktaktion · erzeugt einen fachlich definierten Zustandsübergang
  2. Eigener Datenspeicher · bewahrt den fachlich maßgeblichen Zustand des Produkts
  3. Aggregation · übersetzt interne Zustände in begrenzte fachliche Kennzahlen
  4. Statistik-API · liefert authentifizierte Werte mit Zeitraum und Bedeutung
  5. Collector · führt kompatible Antworten zusammen, ohne sie neu zu definieren
  6. Report · erklärt den eingefrorenen gemeinsamen Stand

Der Sammler darf die Antworten zusammenführen, aber nicht neu deuten, was ein erfolgreicher Release oder ein verifizierter Claim ist. Diese Begriffe gehören dem Produkt.

Einheitlich ist die Zeit, nicht die Fachlichkeit#

Ein gemeinsamer Vertrag bedeutet nicht, dass alle Produkte dieselben Kennzahlen liefern. Das wäre künstliche Gleichmacherei. Gemeinsam sind nur die Eigenschaften, die ein Collector für zuverlässige Zeitfenster benötigt:

  • bucket bestimmt die Auflösung: Stunde, Tag, Monat oder Jahr.
  • from ist der eingeschlossene Anfang des Fensters.
  • to ist das ausgeschlossene Ende.
  • tz macht die Zeitzone ausdrücklich, hier durchgehend UTC.
  • buckets enthält eine dichte Reihe einschließlich leerer Intervalle.
  • totals fasst genau das angefragte Fenster zusammen.

Die Nutzlast bleibt fachlich benannt. Ein Publishing-System kann etwa auf eine Site begrenzte Aggregate für Releases, Inhalte und Leserzugriffe bereitstellen, eine Wissensbasis auf einen Space begrenzte Aggregate für Nutzung, Abdeckung und Reviews, ein Orchestrator auf einen Tenant begrenzte Laufzeitstatistiken für Ausführungen und Connectoren. Kein Produkt muss die Begriffe eines anderen Produkts übernehmen; alle müssen denselben Zeitvertrag erfüllen.

{
  "bucket": "hour",
  "tz": "UTC",
  "from": "2026-07-18T10:00:00.000Z",
  "to": "2026-07-18T12:00:00.000Z",
  "buckets": [
    { "ts": "2026-07-18T10:00:00.000Z", "items_created": 2 },
    { "ts": "2026-07-18T11:00:00.000Z", "items_created": 0 }
  ],
  "totals": { "items_created": 2 }
}

Die halboffene Schreibweise [from,to) verhindert Doppelzählungen. Das Fenster von 10 bis 11 Uhr enthält ein Ereignis um 10:59:59, aber keines um 11:00:00. Das nächste Fenster beginnt genau dort. Ein Retry fragt dasselbe Zeitfenster erneut ab und erhält dieselbe Bedeutung.

Gemessene Null, fehlende Beobachtung, unvollständiges Fenster#

Eine dichte Zeitreihe enthält auch leere Buckets. Das ist praktisch, weil der Collector keine fehlenden Stunden erraten oder auffüllen muss. Eine Null ist dabei ein gemessener Wert: Das Produkt hat das Fenster ausgewertet und kein entsprechendes Ereignis gefunden.

Davon zu unterscheiden sind zwei andere Zustände. Fehlend bedeutet, dass für die Kennzahl keine Beobachtung vorliegt – etwa weil eine Quelle nicht angeschlossen ist oder eine Quote ohne Grundmenge nicht berechnet werden kann. Im hier beschriebenen Reporting-Ablauf bestimmt der nachgelagerte Report außerdem die Unvollständigkeit: Ein erwarteter Snapshot fehlt, oder das Fenster war zum Zeitpunkt der Erfassung noch nicht abgeschlossen. Diese drei Fälle dürfen im Report nicht gleich aussehen, denn sie tragen verschiedene Konsequenzen: Null technische Fehler sind beruhigend, null Seitenaufrufe können stimmen, ein fehlender Analytics-Anschluss ist eine offene Verbindung und keine Messung.

Ein Produkt kann die Unterscheidung zwischen gemessen und fehlend zum Teil der Antwort machen und Messwerte etwa mit value_state und value_kind kennzeichnen. Eine Quote ohne Beobachtungen trägt den Zustand missing und wird nicht als gemessene Null ausgegeben:

{
  "metric": "engagement_rate",
  "value_kind": "ratio",
  "value_state": "missing"
}

Für jemanden, der einen Report liest, ist dieser Zustand wichtiger als eine weitere Nachkommastelle. Zeitfenster, Datenzustand und Messbedeutung gehören deshalb in den Vertrag selbst – nicht in eine Fußnote der Reportseite.

Datenschutz beginnt bei der Antwortform#

Aggregate sollten Informationen weglassen, die der Zweck nicht benötigt. Eine Statistik kann erfolgreiche, fehlgeschlagene und rate-limitierte Leserzugriffe zählen, ohne dafür Benutzernamen, IP-Adresse, Session-ID, Markdown-Inhalte, URLs, Job-IDs oder Credentials in der Statistikstabelle zu speichern. Eine Wissensbasis muss über ihre Statistikressourcen keine Quellentexte ausgeben.

Eine solche Negativliste gehört in die Dokumentation und in Vertragstests. „Wir geben aktuell keine Namen zurück“ ist schwächer als ein Schema, in dem es für Namen gar kein Feld gibt. Der Collector kann dann keinen sensiblen Wert versehentlich in ein JSON-Artefakt oder eine Reportseite übernehmen.

Auch Trace-Daten bleiben auf ihren Zweck begrenzt. W3C Trace Context standardisiert traceparent und tracestate, damit ein Aufruf über mehrere Dienste korreliert werden kann. Die W3C-Empfehlung warnt ausdrücklich davor, personenbezogene oder andere sensible Informationen in diesen Headern zu transportieren. Eine Trace-ID verbindet technische Schritte; sie beschreibt keinen Menschen.

GET /v1/sites/example/stats/releases?bucket=hour&from=2026-07-18T10:00:00Z&to=2026-07-18T11:00:00Z
Authorization: Bearer <site-read-key>
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

Zero-config heißt: vorhandene Begriffe verwenden#

Vollständig konfigurationslos ist Telemetrie selten. Ein Dienst braucht zumindest eine dauerhafte Ablage und eine Information darüber, ob er in Entwicklung, Test oder Produktion läuft. „Zero-config“ ist hier enger gemeint: Für den Reporting-Ablauf wird im Produkt kein Sondermodus aktiviert, kein besonderer Schlüssel erzeugt und kein Reportziel eingetragen.

Die Statistik-Endpunkte können vorhandene Produktgrenzen verwenden: ein Publishing-System etwa die Site und einen normalen Lese-Key, eine Wissensbasis den Space und ihren Lese-Scope, ein Orchestrator den Mandanten.

OpenTelemetry liefert dafür ein brauchbares gemeinsames Vokabular. service.name benennt den logischen Dienst, deployment.environment.name unterscheidet development, staging, production und test; Stand August 2026 ist der ältere Schlüssel deployment.environment in den OpenTelemetry-Konventionen als veraltet markiert. Solche standardisierten Felder verhindern, dass ein Dienst prod, ein anderer live und ein dritter production-eu als dasselbe Konzept behandelt. Das Produkt setzt diese Werte einmal für seine Laufzeit; der Reporting-Collector muss keine Etiketten reparieren.

Die gemeinsame Bezeichnung allein genügt allerdings nicht. Liest ein Aggregator andere Feldnamen, als ein älterer Schreibpfad noch liefert, können erfolgreiche Aufrufe als Fehler erscheinen. Ein solcher Übergang lässt sich in beide Richtungen kompatibel machen: Der Leser versteht alte und neue Felder, der Schreiber liefert vorerst beide, und Tests verwenden echte Auditzeilen statt handgebauter Idealobjekte.

Begrenzte Abfragen statt versteckter Last#

Eine allgemeine Statistik-API braucht harte Grenzen, etwa eine maximale Fensterlänge je Auflösung. Ungültige oder zu große Fenster liefern 422, statt die Produktdatenbank mit einer unerwarteten Vollauswertung zu belasten.

Diese Grenzen gehören zum Produktvertrag, weil nur das Produkt seine Kosten kennt. Ein Collector sollte nicht versuchen, sie mit vielen überlappenden Abfragen zu umgehen. Für einen langen Zeitraum verwendet er die gröbere Auflösung oder arbeitet in eindeutigen, nicht überlappenden Seiten.

Dasselbe gilt für Aufbewahrung. Anonyme Rohereignisse können im Produkt eine eigene Aufbewahrungsfrist haben. Ein Reportartefakt kann länger bestehen, wenn sein verdichteter Inhalt dafür vorgesehen ist. Rohereignis, Produktaggregat und veröffentlichter Report haben verschiedene Lebenszyklen; sie müssen nicht in einem Speicher enden.

Bestehende Schlüssel statt einer Hintertür#

Ein eigener „Reporting-Key“ wirkt zunächst ordentlich, schafft aber eine zweite Sicherheitslogik. Was darf dieser Schlüssel lesen? Gilt er für eine Site, einen Space oder das ganze Produkt? Wer rotiert ihn? Sobald jedes Produkt darauf eine andere Antwort gibt, wird aus dem vermeintlich einfachen Collector ein Sonderfall mit weitreichenden Rechten.

Die Statistik-APIs sollten deshalb vorhandene Scopes verwenden. Ein reiner Lese-Key einer Site darf dann genau die Statistiken der Site lesen, für die er ausgestellt wurde. Ein Lese-Key der Wissensbasis bleibt auf seinen Space begrenzt. Ohne passenden Schlüssel bleibt jeder dieser Endpunkte geschlossen.

Für den Collector gilt damit dieselbe Regel wie für andere API-Nutzer: pro Produkt ein eigener, möglichst enger Schlüssel; Secrets ausschließlich in der Credential-Ablage; keine Schlüssel in Workflowdefinition, JSON-Artefakt, Log oder Report. Der Workflow kennt nur eine credential_ref; der tatsächliche Wert wird beim HTTP-Aufruf eingesetzt und erscheint weder im gespeicherten Ablauf noch in dessen Ergebnis. Diese Trennung lässt sich testen: Ein Aufruf ohne Schlüssel muss 401 liefern, ein Schlüssel ohne passenden Scope 403, ein korrekter Schlüssel 200.

PostgreSQL ist intern, nicht verboten#

Die Ablehnung direkter Datenbankabfragen bedeutet nicht, dass Aggregate außerhalb der Produktdatenbank liegen müssen. Für ein Publishing-System oder eine Wissensbasis kann PostgreSQL gerade der passende Ort sein. Ereignis und fachlicher Zustand lassen sich dort transaktional erfassen, nach Site oder Space begrenzen und mit denselben Migrationen weiterentwickeln wie der Rest des Produkts.

Wichtig ist die Richtung der Abhängigkeit. Das Produkt darf seine eigene Datenbank kennen. Ein fremder Collector darf sie nicht kennen müssen. Ob eine Antwort aus einer vorberechneten Tabelle, einer begrenzten SQL-Aggregation oder einem Cache entsteht, bleibt eine interne Entscheidung. Entscheidend ist, dass jedes Produkt seine interne Ablage nach dem eigenen Betriebsmodell gestalten kann und nach außen denselben verlässlichen Zeitvertrag erfüllt.

Dokumentation gehört zum Vertrag#

Ein neuer Endpunkt ist erst fertig, wenn OpenAPI, llms.txt, llms-full.txt, README und die ausführliche Produktdokumentation dasselbe erzählen. Sonst findet ein Mensch einen Scope in der README, ein Client generiert aus einer älteren OpenAPI-Datei und ein Agent liest eine dritte Variante.

Darum prüfen die Repositories ihre generierten Dokumente auf Drift. Vertragstests schicken gültige, ungültige, zu große und nicht autorisierte Fenster an die Handler. Integrationstests verwenden die echte Produktablage. Ein Binärtest stellt sicher, dass die ausgelieferte Datei dieselben Routen und dieselbe Versionsnummer enthält wie der Quellstand. Diese Prüfungen verhindern, dass der Collector von einer veralteten oder nur intern bekannten Antwortform abhängt.

APIs machen Austausch möglich#

Der unmittelbare Effekt dieser Trennung zeigt sich beim Testen. Jede Produkt-API lässt sich mit einem festen Fenster gegen ihr eigenes Schema prüfen. Der Collector erhält Fixtures ohne Zugriff auf die Datenbank. Ein Produktrelease lässt sich auf API-Ebene unabhängig zurückrollen, solange der veröffentlichte Vertrag erhalten bleibt.

Ein neuer Verbraucher braucht keine neue Tabelle im Produkt. Ein anderes Reporting-Werkzeug kann dieselbe API ebenso aufrufen. Ein Produkt darf seine interne Ablage ändern, ohne dass die Statistikressource neu erfunden wird.

Das hält auch die Produkte verständlich. Jedes Produkt behält seine Rolle – als Publishing-System, Wissensbasis oder Orchestrator. Ihre Statistikressourcen beschreiben die eigene Arbeit als begrenzte, fachlich benannte Aggregate. Alles Weitere geschieht außerhalb ihrer Grenzen. Solange der veröffentlichte Vertrag erhalten bleibt, kann ein Teil ersetzt werden, ohne die anderen anzupassen.

Was sich seit damals geändert hat#

Der Beitrag HTTP APIs: klein, stabil, lesbar formulierte 2014 einen Endpunkt als langfristiges Versprechen an unbekannte Clients. Kleine Ressourcen, eindeutige Zeitstempel, stabile Bedeutungen und lesbare Fehler sollten verhindern, dass jeder Client eine eigene Abwehrschicht baut. Diese Anforderungen gelten weiterhin.

Neu ist der Blick auf interne Produktstatistiken. Ein Statistikpfad wird nicht für ein bestimmtes Dashboard gebaut, sondern als begrenzte Projektion der eigenen Produktdaten. Er verwendet bestehende Schlüssel und Scopes, liefert Zeitgrenzen und Datenzustand mit und veröffentlicht nur Aggregate. Damit bleibt die API klein, unabhängig davon, ob ein Mensch, ein Workflow oder ein Agent sie aufruft.

Weiterführende Quellen#

Mit Unterstützung von KI überarbeitet. Hörfassung mit synthetischer Stimme.