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.