Funktionen / Erweitern

Fremddaten einbinden

Eine HTTP-JSON-API per Schema als Collection mappen — read-only, gecacht, in derselben REST-Form wie eigener Content. Optional als Zeitreihe mit Retention und Backfill.

Der Strommix von heute, der Pegel am Fluss, das Wetter für die Startseite — die Daten liegen bei jemand anderem, das Frontend soll sie trotzdem zeigen. Statt eines Proxys mit eigenem Cache und Fehlerhandling beschreibst du ein Schema mit type: "external". Ab dann ist sie eine Collection wie jede andere: gleiche URL-Form, gleiche Pagination, gleicher API-Client.

Ein Schema statt eines Microservice

{
  type: "external",
  name: "wetter",
  source: {
    url: "${WEATHER_API_BASE}/current",
    headers: { Authorization: "Bearer ${WEATHER_API_TOKEN}" },
    params: { units: "metric", lang: "de" },
    timeout: "5s",
  },
  cache: { ttl: "30m", staleOnError: true },
  itemsPath: "results[*]",
  slug: { path: "id" },
  fields: {
    city:   { path: "city",        as: "string" },
    temp:   { path: "temp",        as: "f64" },
    region: { path: "meta.region", as: "string", default: "unknown" },
  },
}

Das Feld-Mapping ist ein bewusst kleines JSONPath-Subset: verschachtelte Pfade, Array-Index, ein Filter-Prädikat (production_types[?name="Wind onshore"].data) und Reducer wie last_non_null für Zeitreihen, deren letzte Werte noch fehlen. as erzwingt den Typ; passt er nicht, greift default.

Secrets bleiben in der Umgebung: ${VAR} in URL, Headern und Parametern wird beim Start aufgelöst, eine fehlende Variable bricht den Start ab — nicht erst den Request um drei Uhr nachts. Dazu ${time:now-24h|unix} für Zeitfenster und ${rand:uuid} für einen Nonce.

Gleiche Form wie eigener Content

GET /api/content/wetter                  # Liste, aus dem Cache
GET /api/content/wetter/hamburg          # ein Eintrag
POST /api/content/wetter/_refresh        # Upstream sofort neu holen

Einträge kommen als _status: "published" mit _external: true; schreibende Methoden antworten mit 405. Die Admin-UI zeigt die Collection read-only, mit „Aktualisieren” statt „Neuer Eintrag”.

Cache, der Ausfälle abfängt

TTL pro Schema, In-Memory-LRU, Disk-Snapshot, der nach einem Neustart warm lädt. Fällt der Upstream aus, liefert RustyCMS den letzten guten Stand weiter und markiert die Antwort mit x-rustycms-external-stale: 1; wiederholte Fehler laufen in ein Backoff von zwei Sekunden bis fünf Minuten. Hundert gleichzeitige Besucher lösen einen Upstream-Request aus.

Von der Momentaufnahme zur Zeitreihe

Mit cache.mode: "history" landet jeder erfolgreiche Fetch in einer JSONL-Datei pro Collection und Monat — der rohe Upstream-Response, nicht die Projektion, damit ein späteres Mapping alte Daten nicht verliert. retain: "12mo" räumt täglich auf, granularity: "15min" verhindert doppelte Zeilen bei kurzer TTL. GET /api/content/<coll>/_history?from=&to= liefert das Fenster; ein Backfill-Werkzeug holt die Vergangenheit chunkweise nach.

Schemas laden im Betrieb neu — Datei ändern, Cache leert sich, kein Neustart. Der Validator prüft Pfade und Typen, ohne die Quelle anzufragen.

Bewusst nicht drin

Keine Referenzen auf oder aus externen Collections: Fremd-IDs ändern sich, ein Rename beim Anbieter bräche Verweise. Keine lokalisierten Felder, keine Upstream-Pagination, kein OAuth. Externe Einträge sind Blätter im Content-Graph — genau deshalb cachen sie so sauber.

Willst du das an deinem Projekt sehen?

Sag kurz, was du vorhast. Ich zeige die Admin-UI an einer echten Installation und sage ehrlich, ob es passt.

Früher Zugang anfragen