Grenze zum Backend

Von der Schnittstellenbeschreibung bis zum geprüften Objekt

Typen sind eine Behauptung über fremde Daten. Erst die Prüfung an der Systemgrenze und ein Abgleich in der Pipeline machen daraus eine Zusage, auf die der Rest bauen darf.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Seit 1997 am Markt Kleine Gruppen Präsenz und Live-Online Zertifizierte Trainer
Worum es geht

Der Fehler zeigt sich in der Oberfläche, entstanden ist er an der Grenze

Der typische Ablauf: Jemand schreibt ein Interface passend zur Beispielantwort, holt die Daten mit fetch, hängt ein as an die Stelle, an der TypeScript unknown liefert, und ab da behandelt die ganze Anwendung die Daten als gesichert. Das funktioniert genau so lange, bis ein Feld optional wird, ein Datum als Zeichenkette statt als Zahl kommt oder eine Liste bei leerem Ergebnis null statt eines leeren Arrays liefert.

Auffallen wird das an einer ganz anderen Stelle, meistens tief in einer Komponente, die eine Eigenschaft eines Objekts liest, das es nicht gibt. Die Fehlermeldung nennt dann diese Komponente, nicht die Schnittstelle. Bis jemand den Weg zurückverfolgt hat, sind mehrere Personen beteiligt, und die Vermutung steht im Raum, das Frontend habe einen Fehler.

Der zweite, teurere Fall ist die stille Abweichung. Das Backend benennt ein Feld um und liefert das alte für eine Übergangszeit weiter mit, oder ein Wertebereich bekommt einen zusätzlichen Status. Nichts stürzt ab, die Oberfläche zeigt nur an einer Stelle nichts an. Ohne Prüfung an der Grenze merkt das niemand, bis eine Fachabteilung fragt, warum eine Zahl nicht mehr stimmt.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Schritt für Schritt

Der Weg von der Beschreibung bis zur geprüften Antwort

Die Reihenfolge ist wichtiger als die Werkzeugwahl. Wer mit Schritt drei anfängt, prüft am Ende Daten gegen Typen, die jemand von Hand abgeschrieben hat, und hat damit nur das eigene Missverständnis bestätigt.

  1. 1

    Die Beschreibung beschaffen

    Frag nach der OpenAPI-Datei oder dem GraphQL-Schema und danach, wo sie erzeugt wird. Kommt sie aus dem Quellcode des Servers, ist sie aktuell. Liegt sie als gepflegtes Dokument daneben, ist sie eine Absichtserklärung, und du musst mit Abweichungen rechnen.

    Geschafft, wenn: Es gibt eine Datei mit einer festen Adresse, die niemand von Hand nachbessert.

  2. 2

    Typen daraus erzeugen

    Für REST erzeugt openapi-typescript aus der Beschreibung eine Typdatei, die Pfade, Parameter und Antworten je Statuscode abbildet. Für GraphQL übernimmt das der GraphQL Code Generator, der zusätzlich die konkreten Abfragen typisiert. Der Aufruf gehört als Skript ins Repository, damit ihn nicht jeder anders ausführt.

    Geschafft, wenn: Eine erzeugte Datei liegt im Projekt, und niemand bearbeitet sie von Hand.

  3. 3

    Den Aufruf typisieren

    Setz einen dünnen Client auf die erzeugten Typen, bei openapi-typescript zum Beispiel openapi-fetch, der Pfad, Methode und Antwort zusammenführt. Damit meldet der Compiler einen falschen Pfad oder einen fehlenden Parameter beim Schreiben und nicht der Server zur Laufzeit.

    Geschafft, wenn: Ein Tippfehler im Pfad wird zum Fehler im Editor.

  4. 4

    Die Antwort an der Grenze prüfen

    Beschreib die Antwort zusätzlich als Schema, etwa mit Zod, und prüf sie mit safeParse, bevor du sie weitergibst. Der Rückgabewert unterscheidet Erfolg und Fehler, statt eine Ausnahme zu werfen, und der abgeleitete Typ kommt aus demselben Schema, sodass Prüfung und Typ nicht auseinanderlaufen können.

    Geschafft, wenn: Eine unerwartete Antwort erzeugt eine klare Meldung mit Feldnamen statt eines Folgefehlers.

  5. 5

    Die Fehlerfälle sauber trennen

    Gib aus der Anbindung ein Ergebnis zurück, das die Fälle Erfolg, Statusfehler, Netzwerkfehler und Formatabweichung unterscheidet. Eine Vereinigung mit einem gemeinsamen Feld reicht dafür aus, und die Oberfläche kann darauf verzweigen, ohne den Fehlertext zu zerlegen.

    Geschafft, wenn: Die Komponente zeigt bei einem 404 etwas anderes als bei einem Verbindungsabbruch.

  6. 6

    Die Prüfung in die Pipeline hängen

    Lass die Typen im Bauschritt neu erzeugen und vergleich sie mit den eingecheckten. Weicht etwas ab, bricht der Lauf mit einem Hinweis auf das geänderte Feld. Das ist die Stelle, an der eine stille Änderung am Backend laut wird.

    Geschafft, wenn: Eine umbenannte Eigenschaft fällt im Bauschritt auf, nicht in der Fehlermeldung eines Kunden.

  7. 7

    Mocks aus derselben Quelle ziehen

    Leite Testantworten aus der Beschreibung oder aus dem Schema ab, statt sie als Beispiel-JSON zu pflegen. Ein Werkzeug wie Mock Service Worker fängt die Anfragen im Test ab und liefert diese Antworten aus, sodass Tests und Anbindung dieselbe Grundlage teilen.

    Geschafft, wenn: Ein geändertes Feld lässt Tests fehlschlagen, statt sie unverändert grün zu lassen.

Fünf Stationen zwischen Server und Komponente

  1. 01 Beschreibung: OpenAPI oder GraphQL-Schema als gemeinsame Grundlage.
  2. 02 Erzeugung: Typen und Client entstehen aus dieser Datei, nicht von Hand.
  3. 03 Grenze: Die Antwort wird einmal geprüft, danach gilt sie als gesichert.
  4. 04 Fehlerpfad: Netzwerk, Status und Formatabweichung bleiben unterscheidbar.
  5. 05 Pipeline: Neu erzeugte Typen werden verglichen, ein Unterschied bricht den Build.
Was du mitnimmst

Was du nach diesem Vorgehen sicher sagen kannst

Ziel ist nicht die perfekte Typdefinition, sondern eine Stelle im Code, an der aus fremden Daten geprüfte Daten werden, und ein Signal, das anschlägt, bevor jemand anderes es merkt. Beides zusammen kostet einen Tag Einrichtung und spart die Suche nach Fehlern, die sich als Anzeigeproblem tarnen.

Die Quelle des Vertrags benennen

Entweder es gibt eine OpenAPI-Beschreibung oder ein GraphQL-Schema, dann ist die Frage geklärt. Gibt es keine, wird das zur ersten Aufgabe, denn ohne eine gemeinsame Beschreibung bleibt jede Typisierung eine private Vermutung über fremden Code.

Typen erzeugen statt abtippen

Aus einer OpenAPI-Datei erzeugt openapi-typescript einen Satz Typen, aus einem GraphQL-Schema erledigt das der GraphQL Code Generator. Der Gewinn ist nicht die gesparte Tipparbeit, sondern dass die Typen bei jeder Änderung neu entstehen und niemand vergisst, sie nachzuziehen.

Eine einzige Prüfstelle einrichten

Die Laufzeitprüfung gehört in die Funktion, die die Antwort entgegennimmt, nicht in die Komponenten. Danach gilt im Rest der Anwendung der geprüfte Typ, und es gibt genau einen Ort, an dem eine unerwartete Antwort sichtbar wird.

Fehler als Ergebnis behandeln

Netzwerkfehler, Statuscode jenseits von 200 und eine Antwort, die nicht zur Beschreibung passt, sind drei verschiedene Fälle. Wenn die Anbindung sie als unterscheidbares Ergebnis zurückgibt statt als Ausnahme, kann die Oberfläche für jeden Fall etwas Sinnvolles anzeigen.

Abweichungen in der Pipeline sichtbar machen

Erzeugte Typen gehören ins Repository, und ein Schritt in der Pipeline erzeugt sie neu und vergleicht. Bleibt ein Unterschied übrig, hat sich die Schnittstelle geändert, und das merkt ihr beim Bauen statt im Betrieb.

Testdaten aus derselben Beschreibung ziehen

Wenn Mocks von Hand entstehen, veralten sie mit der Schnittstelle und bestätigen danach nur noch sich selbst. Aus der Beschreibung abgeleitete Antworten halten Tests und Wirklichkeit zusammen.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt

Ein Typ ist eine Behauptung, keine Prüfung

TypeScript prüft, ob dein Code in sich stimmig ist. Was zur Laufzeit über das Netz kommt, sieht der Compiler nie. Deshalb liefert eine Antwort in TypeScript zunächst any oder unknown, und jedes as an dieser Stelle ist eine Behauptung, die niemand kontrolliert. Genau dort entsteht der Unterschied zwischen einem Programm, das bei falschen Daten eine verständliche Meldung zeigt, und einem, das an beliebiger Stelle stehen bleibt.

Die Prüfung an der Grenze kostet Rechenzeit, und das ist bei sehr großen Listen ein reales Argument. Der übliche Kompromiss: Antworten mit begrenzter Größe vollständig prüfen, bei sehr großen Sammlungen die Hülle prüfen und die Einträge stichprobenartig. Was du nicht tun solltest, ist die Prüfung wegzulassen und stattdessen in jeder Komponente defensiv auf Existenz zu testen, denn dann steht die Fehlerbehandlung an fünfzig Stellen und die Fehlerursache an keiner.

Erzeugte Typen gegen handgeschriebene

Handgeschriebene Typen sind vertretbar, wenn du zwei Endpunkte anbindest, die sich nicht ändern, und niemand sonst an ihnen arbeitet. Sobald mehrere Teams beteiligt sind, kippt die Rechnung, weil eine Änderung am Backend keine Spur im Frontend hinterlässt. Erzeugte Typen drehen das um: Nach jeder Erzeugung zeigt der Compiler an, wo die neue Beschreibung nicht mehr zum vorhandenen Code passt.

Die Grenzen liegen in der Qualität der Beschreibung. Wenn eine OpenAPI-Datei jedes Feld als optional oder als beliebiges Objekt beschreibt, sind auch die erzeugten Typen nutzlos. Achte besonders auf nullable-Angaben, denn ein Feld, das als Zeichenkette beschrieben ist und in Wirklichkeit null sein kann, produziert die unangenehmsten Fehler. Solche Abweichungen findet man nicht durch Lesen, sondern durch die Laufzeitprüfung, die dann meldet, dass Beschreibung und Wirklichkeit auseinandergehen.

Der Vollständigkeit halber: Es gibt Werkzeuge, die aus der Beschreibung gleich den ganzen Client samt Prüfschemata erzeugen. Das nimmt Arbeit ab und bindet dich an deren Vorstellung von Fehlerbehandlung, Zwischenspeicher und Abbruch. Für kleine Anbindungen lohnt der eigene dünne Client, für große Schnittstellenlandschaften spart die vollständige Erzeugung mehr, als sie an Freiheit kostet.

Wo die Laufzeitprüfung hingehört und wo nicht

Die Prüfung gehört an die Systemgrenze, also in die Funktion, die die Antwort entgegennimmt. Danach ist der Typ garantiert und der Rest der Anwendung arbeitet ohne zusätzliche Absicherung. Mit Zod schreibst du das Schema einmal und leitest den Typ daraus ab, sodass es keine zweite Quelle geben kann, die sich unbemerkt davon entfernt. safeParse ist dabei die richtige Variante, weil das Ergebnis den Fehlerfall mitführt, statt eine Ausnahme zu werfen, die irgendwo weiter oben gefangen wird.

Zwei Dinge lohnen sich an dieser Stelle zusätzlich. Erstens die Umwandlung: Ein ISO-Datum kommt als Zeichenkette an, und wenn es die Prüfung ohnehin anfasst, kann sie daraus direkt ein Date machen. Zweitens die Meldung: Der Fehlerpfad aus der Prüfung nennt das betroffene Feld, und dieser Pfad gehört ins Protokoll. Damit steht in der Fehlermeldung, welches Feld nicht gepasst hat, und nicht nur, dass die Antwort unerwartet war.

Nicht sinnvoll ist die Prüfung in Komponenten, in jedem Reducer oder rund um Daten, die deine eigene Anwendung erzeugt hat. Innerhalb des Systems gilt der Typ. Wer überall prüft, verlangsamt die Anwendung und verwässert die Aussage, denn dann sagt eine fehlgeschlagene Prüfung nicht mehr, dass eine fremde Quelle etwas anderes geliefert hat.

Was passiert, wenn sich die Schnittstelle ändert

Änderungen sind der Normalfall, und die Frage ist nur, wer sie zuerst bemerkt. Additive Änderungen, also neue Felder und neue optionale Parameter, sind ungefährlich. Gefährlich sind Umbenennungen, verschärfte Pflichtfelder, geänderte Typen und neue Werte in einer Aufzählung, denn genau diese Fälle sehen in der Antwort harmlos aus.

Der Abgleich in der Pipeline ist die billigste Absicherung: Typen neu erzeugen, mit den eingecheckten vergleichen, bei Unterschied abbrechen. Das kostet einen Schritt und liefert im Fehlerfall eine Zeile, die genau sagt, welches Feld sich geändert hat. Für neue Werte in einer Aufzählung hilft zusätzlich, im Code über alle Fälle zu verzweigen und den nicht behandelten Fall vom Compiler bemängeln zu lassen, statt einen Standardzweig zu bauen, der alles Unbekannte still schluckt.

Wenn Backend und Frontend getrennt ausgerollt werden, gehört noch eine Regel dazu: Die Beschreibung ändert sich zuerst, dann das Frontend, dann fällt das alte Feld weg. Wer diese Reihenfolge einhält, braucht keine Absprache über Zeitpunkte, und wer sie nicht einhält, braucht sie bei jeder Änderung neu.

GraphQL verschiebt die Frage, es beantwortet sie nicht

Bei GraphQL ist das Schema Teil des Servers, es kann also nicht veralten. Die erzeugten Typen beziehen sich nicht nur auf Objekte, sondern auf deine konkreten Abfragen, sodass jedes Feld, das du nicht abgefragt hast, im Typ auch nicht auftaucht. Das ist der eigentliche Vorteil gegenüber REST: Der Typ beschreibt genau das, was du angefordert hast.

Bleiben zwei Punkte, die Teams regelmäßig überraschen. Erstens die Nullbarkeit: In GraphQL ist alles voreingestellt nullbar, und ein Fehler in einem Teilbaum liefert Daten und Fehler gleichzeitig. Deine Anbindung muss also mit einer Antwort umgehen, die teilweise gefüllt ist. Zweitens die Frage, wer Abfragen kontrolliert: Ein Client kann sehr teure Abfragen stellen, deshalb arbeiten viele Teams mit fest hinterlegten Abfragen, was den Vorteil der freien Zusammenstellung wieder einschränkt.

Für die Typisierung heißt das: Der Weg ist derselbe, nur die Quelle ist eine andere. Schema holen, Typen für die eigenen Abfragen erzeugen, Antwort an der Grenze auf Vollständigkeit prüfen, Fehlerfeld auswerten. Wer das bei REST eingeübt hat, überträgt es in einem Nachmittag.

Dazu passende Kurse

Wer das an eigenen Endpunkten durchspielen möchte, findet bei cmt die passenden Termine, um Serverantworten zuverlässig typisieren .

Weil diese Frage nicht am Editor hängt, sondern am Zuschnitt der Anwendung, lohnt es sich, die Grenze zwischen Frontend und Backend sauber schneiden .

Wissen prüfen

Wie sicher bist du beim Thema wirklich?

Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.

Ein sehr gutes, praxisorientiertes und nachhaltiges Seminar. So sollte es immer sein. Vielen Dank.
LimeSurvey - Anwendertraining Teil 1 (Grundlagen)
Die Schulung ist gerade für Einsteiger oder Entwickler*innen mit eingestaubtem Basiswissen sehr hilfreich.
Vue.js 3 Grundkurs
Ich bin sehr glücklich Franz als Trainer gehabt zu haben. Er hat offensichtlich unglaublich viel Wissen zu den Themen, und schafft es, dieses auch interaktiv und verständlich weiterzugeben.
React Komplettausbildung

Häufige Fragen

Reicht es nicht, die Antwort einfach mit as auf den erwarteten Typ zu zwingen?
as sagt dem Compiler nur, er solle nicht weiter nachfragen. Zur Laufzeit passiert dabei nichts, die Daten bleiben, wie sie sind. Wenn der Server etwas anderes liefert, verschiebt as den Fehler nur an die Stelle, an der zum ersten Mal auf ein fehlendes Feld zugegriffen wird, und die liegt meist weit weg von der Ursache.
Kostet eine Laufzeitprüfung nicht spürbar Leistung?
Bei einer Antwort mit ein paar Dutzend Feldern ist der Aufwand nicht messbar. Spürbar wird es bei Listen mit mehreren zehntausend Einträgen, die im Sekundentakt kommen. Dann prüf die Hülle vollständig und von den Einträgen eine Stichprobe, oder prüf je Eintrag nur die Felder, die die Anwendung wirklich liest. Was du nicht tun solltest, ist die Prüfung im Betrieb abzuschalten, denn dort kommen die Antworten an, die vorher niemand gesehen hat, und ohne Prüfung verlierst du auch die Meldung, die das abweichende Feld benennt.
Wir haben keine OpenAPI-Beschreibung. Womit fangen wir an?
Mit der Frage, wer die Schnittstelle betreibt. Erzeugt der Server sie aus seinem Code, ist die Datei meist vorhanden und nur nicht veröffentlicht. Gibt es sie wirklich nicht, beschreib zuerst die Endpunkte, die du tatsächlich nutzt, und leg diese Beschreibung in das Repository des Servers, nicht in deins. Sonst pflegst du auf Dauer eine Vermutung über fremden Code.
Wohin gehören Daten aus der Schnittstelle im Frontend?
In einen Zwischenspeicher für Serverdaten, nicht in den allgemeinen Zustand der Anwendung. Diese Daten gehören dir nicht, sie sind eine Kopie mit Verfallsdatum, und Bibliotheken für genau diesen Zweck bringen Nachladen, Wiederholversuche und Ungültigkeitserklärungen bereits mit.
Persönlich für dich da

Deine Ansprechpartner

Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.

Yves Hoppe

Yves Hoppe

Weiterbildung & Beratung

Hilft dir, aus dem Webentwicklung-Programm den passenden Kurs für deinen Stand zu finden.

Norbert Jansen

Norbert Jansen

Beratung & Inhouse

Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.

Schnittstellen anbinden, ohne auf gutes Wetter zu hoffen

Bei cmt baust du eine solche Anbindung einmal vollständig auf, von der Beschreibung über die erzeugten Typen bis zur Prüfung, die im Fehlerfall etwas Verständliches sagt.