TypeScript
Validação de Dados
Zod
Qualidade de Software
Arquitetura

Datenvalidierung mit Zod: Warum TypeScript-Typen nicht ausreichen

TypeScript-Typen sind Kompilierungsgarantien, keine Ausführungsgarantien. Die Schema-First-Validierung mit Zod schafft eine einzige Wahrheitsquelle zwischen Typ und Daten.

Es gibt ein kostspieliges Missverständnis in Teams, die TypeScript mit Begeisterung eingeführt haben. Der Glaube, dass das System durch die Verwendung von Typen vor fehlerhaften Daten geschützt ist. Das ist es nicht. Und diese Verwirrung zwischen dem, was der Compiler tut und was er nicht tut, ist der Ursprung einer ganzen Kategorie von Fehlern, die nur in der Produktion auftreten.

Dieses Missverständnis möchte ich direkt ausräumen, denn es prägt architektonische Entscheidungen. Wer versteht, wo Typen aufhören, beginnt, mit den Grenzen des Systems mit der gebotenen Sorgfalt umzugehen.

Was TypeScript wirklich garantiert

TypeScript ist ein Typsystem, das vollständig zur Kompilierzeit existiert. Sie schreiben die Anmerkungen, der Compiler prüft sie und dann werden sie gelöscht. Das in der Produktion ausgeführte JavaScript weiß nicht, dass es sich bei diesem Objekt um ein Usuario handelt. Für die Ausführungs-Engine ist es einfach ein beliebiges Objekt.

Dies ist beabsichtigt und kein Fehler. TypeScript wurde entwickelt, um zur Laufzeit keine Kosten zu verursachen. Die Konsequenz ist, dass jede Typgarantie eine Zusage für den Code ist, den Sie geschrieben haben, und nicht für die Daten, die er verarbeiten wird.

Solange die Daten in Ihrem Code entstehen, gilt das Versprechen. Eine Funktion, die eine Zahl annimmt und zurückgibt, wird vom Compiler von Anfang bis Ende geschützt. Das Problem beginnt, wenn die Daten von außen kommen.

Die Grenze, an der die Garantie verfliegt

Denken Sie an alles, was in Ihr System gelangt, ohne von ihm erstellt zu werden. Die Antwort einer externen API. Der Hauptteil einer HTTP-Anfrage. Ein Formular, das von einem abgelenkten Menschen ausgefüllt wurde. Eine aus Datenbank gelesene Zeile nach einer verpatzten Migration. Eine Umgebungsvariable. Eine Nachricht in einer Warteschlange.

An all diesen Punkten machen Sie normalerweise etwas in der Art, dass Sie erklären, dass die Daten, die Sie erhalten, von einem bestimmten Typ sind. Eine Behauptung. Und hier liegt der Fehler: Diese Behauptung bestätigt nichts. Es sagt dem Compiler lediglich, dass er Ihnen vertrauen und weitermachen soll. Wenn die API ein Feld von Zahl in Text geändert hat, glaubt TypeScript weiterhin, dass es sich um eine Zahl handelt, weil Sie es angewiesen haben, es zu glauben.

Das Ergebnis ist ein System, das typisiert aussieht, aber Löcher direkt an den Rändern aufweist, wo die reale Welt eintritt. Der Fehler passiert nicht an der Grenze, wo er leicht zu diagnostizieren wäre. Es passiert drei Schichten tief, wenn etwas versucht, dieses Feld zu nutzen, als ob das Versprechen wahr wäre. Die Spur wird kalt, die Stapelverfolgung zeigt an die falsche Stelle und jemand verliert seinen Nachmittag.

Es fehlte die Validierung zur Laufzeit

Die konzeptionelle Lösung ist einfach zu formulieren und leicht aufzuschieben: An Einstiegspunkten müssen Sie zur Laufzeit tatsächlich überprüfen, ob die Daten die erwartete Form haben. Vertraue nicht, überprüfe.

Unter Laufzeitvalidierung versteht man Code, der Daten effektiv untersucht und darauf reagiert, ob sie gültig sind oder nicht. Wenn die API eine Zahl versprochen und einen Text gesendet hat, schreit die Validierung dort, am Rand, mit einer klaren Botschaft, bevor der Wert den Rest des Flusses kontaminiert. Sie tauschen einen stillen, schwerwiegenden Fehler gegen einen lauten, lokalisierten Fehler ein.

Historisch gesehen war dies mühsam und leicht zu vergessen. Wir haben einerseits die TypeScript-Schnittstelle und andererseits eine separate Validierungsfunktion geschrieben, die dieselben Felder manuell überprüft. Zwei Beschreibungen derselben Sache, gehalten von verschiedenen Personen zu unterschiedlichen Zeiten. Sie unterschieden sich. Sie unterscheiden sich immer. Der Typ sagte eine Sache, der Validator überprüfte eine andere und die tatsächlichen Daten folgten einer dritten Regel.

Zod und der Schema-First-Ansatz

Hier verändert Zod die Art und Weise, wie wir über das Problem denken. Die zentrale Idee besteht darin, die Reihenfolge umzukehren: Anstatt den Typ zu schreiben und dann einen Validator zu verwenden, der versucht, damit Schritt zu halten, schreiben Sie ein einzelnes Schema, und der Typ wird automatisch daraus abgeleitet.

Sie beschreiben einmal die Form der Daten mit ihren Regeln, Pflichtfeldern, Formaten und Grenzen. Aus diesem Schema extrahiert Zod gleichzeitig zwei Dinge. Einer davon ist der Validator, der in der Produktion läuft und die Daten tatsächlich untersucht. Der andere ist der statische Typ TypeScript], der aus derselben Definition generiert wird, ohne dass Sie ihn von Hand schreiben müssen.

Das ist der Gewinn, auf den es ankommt: eine einzige Quelle der Wahrheit. Typ und Validierung können nicht mehr voneinander abweichen, da sie vom selben Ort stammen. Wenn Sie das Schema ändern, ändert sich auch der Typ, und jeglicher Code, der vom alten Format abhing, kann nicht kompiliert werden. Divergenz ist nicht mehr durch Konstruktion möglich, sondern kann durch Disziplin vermieden werden.

Die externen Daten kommen herein, durchlaufen das Schema und kommen auf der anderen Seite als Wert wieder heraus, den der Compiler nun mit legitimem Vertrauen behandeln kann, da das Vertrauen zur Laufzeit erworben und nicht nur deklariert wurde. Die blinde Behauptung wird zu einer echten Prüfung, und von da an ist der Rest des Systems wieder durch Typen geschützt.

Was verändert dies in der Meinung derjenigen, die entscheiden?

Die Einführung der Schema-First-Validierung ist keine Wahl des Tools, sondern eine Wahl, wo die Vertrauensgrenze platziert werden soll. Die Regel, die ich dem Team vorschlage, ist einfach: Nichts, was von außen kommt, dringt ein, ohne ein Schema zu durchlaufen. API, Formular, Bank, Warteschlange, Konfiguration. Alles wird am Rand validiert.

Innerhalb dieser Grenze vertrauen Sie den Typen völlig, weil sie wieder der Realität entsprechen. Abgesehen davon vertrauen Sie nichts, was nicht überprüft wurde. Diese klare Trennung zwischen dem validierten Territorium und der Wildnis macht das System vorhersehbar. Jeder, der mit TypeScript in fortgeschrittenen Projekten arbeitet, weiß, dass Typ ein Argumentationswerkzeug ist und nur bei Daten gute Argumente liefert, die tatsächlich die versprochene Form haben.

Es lohnt sich, die Kosten zu erfassen, die ehrlich und gering sind. Es gibt einen anfänglichen Aufwand zur Modellierung der Schemata und einen Ausführungsaufwand für die Validierung von Daten an den Rändern. Im Gegenzug eliminieren Sie eine Klasse von Fehlern, die gerade deshalb teuer sind, weil sie sich spät und fernab der Ursache manifestieren. Dies ist einer der besten Return on Investments, die ich in der Softwarequalität kenne, und spricht direkt für umfassendere Webanwendungssicherheitspraktiken, da die Validierung von Eingaben auch die erste Verteidigungslinie ist.

Wenn Sie ein Team leiten, das TypeScript übernommen hat, externe Daten aber immer noch mit blinden Behauptungen behandelt, lohnt es sich, diese Woche die Systemgrenzen zu überprüfen. Die Kosten für das Hinzufügen einer Validierung dort, wo die Daten eingehen, sind gering, und die Kosten für das Fehlen einer Validierung sind genau die Art von Fehler, die freitags niemand beheben möchte.

Lesen Sie auch