Erstelle Django-Frontend-APIs wie ein Ninja

11 Juli 2024

Vor einiger Zeit habe ich einen Artikel über die Integration von Django mit React geschrieben, der einiges an Anklang gefunden hat. Jedes Mal, wenn unser Team ein neues Projekt beginnt, bewerten wir unsere Architektur und die Entscheidungen, die wir in früheren Projekten getroffen haben. Dieses Mal haben wir beschlossen, etwas Neues auszuprobieren, um einige der Ärgernisse mit Frontend-JSON-APIs zu lösen.

Der größte Pain Point war, dass es drei Quellen für die Schnittstellendefinition gab:

  • was das Backend implementierte
  • was die API Dokumentation angab
  • was das Frontend erwartete

Idealerweise sollten diese alle gleich sein, aber in der Praxis gab es Unterschiede. Das lag zum Teil daran, dass die API-Dokumentation manuell und getrennt von der Backend-Implementierung erstellt wurde. Obwohl wir uns sehr bemühten, die Dokumentation und die Backend-Implementierung aufeinander abzustimmen, fielen manchmal Dinge durch das Raster. Zum Beispiel wurde eine Eigenschaft von "erforderlich" zu "nicht erforderlich" geändert.

Unser Ziel bei dieser Implementierung war es, eine bessere API zu schaffen, d. h. eine, die die Gleichheit zwischen dem, was das Backend bereitstellt, und dem, was das Frontend erwartet, garantiert. Außerdem sollte die API automatisch generiert und validiert werden. Als Kirsche auf der Torte haben wir beschlossen, die gesamte Anwendung streng typisiert zu gestalten.

Es gibt ein Repository mit Beispielcode, das hier angesehen werden kann, und damit sind wir auch schon beim Thema.

Unsere Frontend Schnittstelle

Wir haben die Schnittstelle komlett streng typisiert.

Unser Backend und Frontend kommunizieren über eine JSON-API, was gut ist, aber auch einen Nachteil hat. Um diesen Nachteil aufzuzeigen, lasst uns ein einfaches Beispiel nehmen.

Stell dir vor, du hast diese beiden JSON-Objekte

1: {“value”: 1}
2: {“value”: “1”}

Der Werttyp in Beispiel 1 ist ein Zahlentyp, während es sich in Beispiel 2 um eine Zeichenkette handelt. Die Frage ist: Woher weiß das Frontend das? Natürlich können wir immer Typüberprüfungen durchführen, aber das wäre aufwendig, und wenn wir eine übersehen, könnten wir den Kampf mit der JavaScript-Typumwandlung verlieren und unbrauchbare Ergebnisse erhalten. Ein Beispiel: Die API gibt zwei Werte als Zahlen zurück, und im Code wird deren Summe berechnet. Du würdest zu Recht erwarten, dass 1 + 1 = 2 ist. Wenn sich aber einer dieser Werte in der API in eine Zeichenkette verwandelt und Sie keine Typüberprüfung durchgeführt haben, dann ist plötzlich 1 + 1 = 11.

Im Backend haben wir mypy verwendet, um statische Typisierung zu erreichen. Um das Gleiche auf dem Frontend zu erreichen, wechselten wir zu TypeScript.

Wir haben sichergestellt, dass die Versprechen und Erwartungen zwischen Backend und Frontend abgestimmt sind.

Aber wie?

Nun, zunächst brauchten wir eine Möglichkeit, die API-Dokumentation aus einer Hand zu erhalten. Wir wählten dafür die Backend-Ansichten und generierten daraus automatisch die Schnittstellendefinition für das Frontend.

Wir entschieden uns für die automatische Generierung der Open-API-Dokumentation aus der Backend-Implementierung. Die Frontend-Schnittstelle wiederum würde automatisch aus eben dieser API-Dokumentation erstellt werden.

Da wir jetzt strikt typisiert sind, werden alle Änderungen an der Schnittstelle im Backend zur Erstellungszeit im Frontend angezeigt, wodurch böse Überraschungen zur Laufzeit vermieden werden.

Die Backend-Implementierung

Wir haben uns entschieden, Django Ninja für die Entwicklung unserer API zu verwenden. Die Gründe dafür sind, weil es einfach zu bedienen ist und die API-Dokumentation für uns generiert. Wie zuvor, hier ein paar Codebeispiele.

In einer Django-Anwendung hello definieren wir einen neuen Router mit dem add API-Endpunkt, der zwei Ganzzahlen addiert:

django-ninja endpoint implemenation

Im Projekt urls.py setzen wir dann die URLs des hello Routers auf:

django-ninja implemenation urls.py

Einfach, oder?

Django Ninja übernimmt die Typüberprüfung, Serialisierung und Deserialisierung. Aber am wichtigsten für uns hier ist, dass es einen API-Docs-Endpunkt /api/doc erzeugt, der so aussieht:

Screenshot API docs

Und wenn wir unsere API aktualisieren, wird die Dokumentation automatisch aktualisiert.

Mit dem wachsenden Projekt wird auch die API komplexer werden, aber die Grundidee ist dieselbe.

Die Frontend-Implementierung

Im Frontend haben wir die erstellte API-JSON-Datei verwendet, um die Schnittstellendefinition und einige Hilfsdateien zu generieren, die den Aufruf der API vereinfachen.

Wie bereits erwähnt, haben wir Oazapfts verwendet, aber es gibt auch andere npm-Pakete, die Code aus API-JSON-Dateien generieren können, wie openapi-typescript.

Alles, was wir tun mussten, war, den folgenden Befehl auszuführen:

oazapfts api_docs.json generated-api.ts

Und schon wurde eine Typescript-Datei mit allen Typdefinitionen und Funktionen erstellt, die der Rest der Anwendung zum Aufrufen der API verwenden konnte. Hier ist ein Beispiel dafür, basierend auf der obigen API.

TypeScript call endpoint implemenation

Da wir auf TypeScript umgestellt haben, gab es Fehler, wenn die Felder falsch waren, das Anfrageobjekt nicht den Anforderungen entsprach oder die API geändert wurde.

Die Verwendung war ebenfalls problemlos, da die Hilfsfunktionen klar waren und es bei Fragen eine praktische URL mit der definierten API, dem Schema und hilfreichen Demos gab.

Eine sehr ärgerliche Macke, auf die wir gestoßen sind, war jedoch die Art und Weise, wie Django-ninja standardmäßig API-Operations-IDs erstellt. Wenn Oazapfts die Schnittstelle generiert, verwendet es diese für die Funktionsnamen. Auf diese Weise kann es zu sehr laaaaangen Namen kommen, kein Witz, hier ein Beispiel:

finalsApiMakeUpRegistrationEndpointsGetMakeUpRegistration

Ja, das ist ein Funktionsname.

Aber das war kein großes Problem, denn ozapfts kann mit superlangen Namen umgehen, was nicht weiter verwunderlich ist, da die Autoren Deutsche sind und unsere deutschen Freunde lieben ihre langen Wörter sehr. Im Ernst: Nennt es Eierknacker, nicht Eierschalensollbruchstellenverursacher!

Zum Glück ist das in django-ninja leicht zu lösen.

Was haben wir gelernt?

Ich bin Frontend-Entwickler und der anfängliche Vorstoß in Richtung TypeScript hat mich nicht gerade begeistert. Mein erster Gedanke war, dass TypeScript die Lesbarkeit des Codes beeinträchtigt und schönen JavaScript-Code hässlich aussehen lässt.

Hier ist die tatsächliche Reaktion des Backend-Teams auf den Gedanken an "Beautiful JavaScript".

Die Vorteile von TypeScript wurden jedoch immer deutlicher, als die Codebasis wuchs und die zusätzlichen Informationen über den vorhandenen Code immer hilfreicher wurden.

Durch die Generierung von Schnittstellen für das Frontend aus der API bereiteten Änderungen weniger Kopfschmerzen. Das Nachschlagen in der API ist immer nützlich, und die Zeitersparnis bei der Erstellung von Boiler-Code war wunderbar. Die automatisierte Typprüfung in der CI sorgte dafür, dass jede Änderung, und sei es nur ein einziges Feld, sofort erkannt wurde.

Das Schreiben von Frontend-Tests war ebenfalls einfacher, da die Hilfsfunktionen gut definiert waren und es einfach machten, alle möglichen Ergebnisse zu mocken und zu testen.

Das Frontend-Team ist immer noch gespalten, ich bin ein überzeugter TypeScript-Anhänger, während mein Kollege immer noch skeptisch ist und nicht ganz davon überzeugt ist, dass die Vorteile den Aufwand wert sind.

Am Backend gab es keine allzu große Veränderung gegenüber der normalen Art und Weise, Django-Projekte zu entwickeln. Django-Ninja hat zwar die Implementierung der Views beeinflusst, aber das war's dann auch schon. Also ziemlich viele Vorteile ohne viel Overhead.

Fazit

Wir haben uns vorgenommen, eine bessere API-Schnittstelle zu entwickeln, in der Hoffnung, dass dadurch wunde Punkte mit der API minimiert werden. Am Ende garantierten wir die Gleichheit der API sowohl für das Backend als auch für das Frontend und unsere Anwendung wurde dadurch besser. Wir werden dies auf jeden Fall für unser nächstes API-lastiges Projekt in Betracht ziehen und empfehlen, für zukünftige Projekte einen ähnlichen Ansatz in Erwägung zu ziehen.

Typen und API waren nicht die einzigen Bereiche, die wir geändert haben. Wir haben auch die Art und Weise geändert, wie wir React-Komponenten in Django integrieren. Dies hier zu schreiben, würde den Rahmen sprengen, also werden wir bald einen Folgeartikel veröffentlichen, der sich damit beschäftigt. Spoiler: Es geht darum, React-Komponenten in Web Components umzuwandeln, und das ist ziemlich genial. Wenn das dein interesse geweckt hat oder dieser Artikel nützlich war, kannst du uns gerne auf Linkedin, Twitter, und YouTube folgen oder ein Lesezeichen für unsere Seite setzen, oder noch besser, alles davon.

djangsters GmbH

Vogelsanger Straße 187
50825 Köln