https://a.storyblok.com/f/270183/1368x665/28c6d5be54/sdk_rest-apis_24.png

Wie ein SDK den Wert von REST-APIs erhöhen kann

Zuletzt aktualisiert am August 4, 2022

Lesedauer: 12 Minuten

Die Vonage-Nachrichten v1 API wurde kürzlich in den Status der allgemeinen Verfügbarkeit erhoben. Nach dieser Ankündigung hat das Developer Relations Tooling Team hart daran gearbeitet, die Unterstützung für diese API in unsere Server-SDKs zu implementieren.

Wir haben gemeinsam beschlossen, dass jedes SDK die API so implementieren sollte, wie es angesichts der Sprache, der Community und des allgemeinen Ansatzes, der zur Unterstützung anderer APIs im SDK verwendet wird, am sinnvollsten ist, um Konsistenz zu gewährleisten. Unser Ziel ist es, dass sich jedes SDK idiomatisch anfühlt, und nicht ein Einheitsansatz, bei dem sich die Implementierung automatisch generiert. Die Messages v1 API ist ein gutes Beispiel dafür, daher dieser Artikel.

Als Java Developer Advocate im Team werde ich mich natürlich auf die Implementierung des Java SDK konzentrieren und die Gründe für sein Design beschreiben. Aber zuerst wollen wir uns die Nachrichten-API-Spezifikationuntersuchen, die wir implementieren müssen.

Die API hat einen einzigen Endpunkt, über den eine Nachricht per POST-Anfrage gesendet werden kann. Die Komplexität ergibt sich aus dem Schema der Nachrichten. Hier gibt es zwei Hauptkonzepte: Kanäle und Nachrichtentypen. Ersteres bezieht sich auf den Dienst, der zum Senden der Nachricht verwendet wird (SMS, MMS, WhatsApp, Viber, Facebook Messenger). Letzteres beschreibt das Medium der Nachricht (Text, Bild, Audio, Video, Datei usw.).

Jeder Kanal unterstützt eine Teilmenge von Nachrichtentypen. Sie können zum Beispiel kein Video per SMS senden. Einige Kanäle, wie WhatsApp, unterstützen Vorlagen und benutzerdefinierte Nachrichtentypen, mit denen Sie komplexere Nachrichten senden oder Ihren Standort mitteilen.

Außerdem unterliegt jeder Kanal eigenen Beschränkungen für den Inhalt von Nachrichten. So variiert beispielsweise die Längenbeschränkung für Texte je nach Kanal, ebenso wie die unterstützten Dateitypen für Mediennachrichten - WhatsApp unterstützt beispielsweise eine breite Palette von Audioformaten, während Messenger nur MP3 unterstützt. Für Mediennachrichten erlauben einige Kanäle eine optionale Textbeschreibung (eine so genannte Bildunterschrift in der API-Spezifikation), die der Datei beigefügt werden kann, während dies bei anderen Kanälen nicht der Fall ist.

Dies kann sogar innerhalb von Kanälen variieren - so kann man beispielsweise einer MMS eine Beschriftung beifügen, es sei denn, es handelt sich um eine vCard (die es übrigens nur im MMS-Kanal gibt). Aus der Sicht eines Implementierers besteht die Herausforderung darin, einen angemessen strukturierten und effizienten Weg zu finden, um diese Anforderungen zu erfassen und gleichzeitig die Verwirrung der Nutzer der API zu minimieren.

Als Java-Entwickler ist es naheliegend, mit dem Datenmodell zu beginnen. Unweigerlich landen wir bei einem abstrakten Typ zur Darstellung einer Nachricht, die von der sendMessage Methode von MessagesClient. Da die Antwort, die wir nach dem Senden einer Nachricht zurückbekommen, unabhängig vom Nachrichtentyp und -kanal dieselbe ist, brauchen wir uns nicht um die Nachricht selbst zu kümmern, wir leiten sie einfach als JSON-Payload weiter, und was wir zurückbekommen, ist ein eindeutiger Bezeichner der gesendeten Nachricht. Mit diesem Gedanken im Hinterkopf wird unser Abstract MessageRequest die Felder aus, die allen Nachrichten gemeinsam sind, nämlich: Typ, Kanal, Absender, Empfänger und eine optionale Client-Referenzzeichenfolge. Da die Kanäle und Nachrichtentypen vordefiniert sind, stellen wir sie als Aufzählungen dar.

Es gibt eine Mischung aus optionalen und obligatorischen Parametern (z. B. ist die Kundenreferenz optional, aber Absender und Empfänger sind obligatorisch). Je nach Kombination von Kanal und Nachrichtentyp gibt es noch weitere Parameter. Da Java keine benannten Argumente kennt, verwenden wir das Builder-Muster, um dies zu emulieren. Sie können mehr darüber lesen hier.

Wir wollen erzwingen, dass nur gültige Kombinationen von Nachrichtentyp und Kanal konstruiert werden können, also definieren wir diese Matrix in der Channel. Wir validieren alle Argumente in Konstruktoren von MessageRequest und seiner Subtypen. Hierin liegt meiner Meinung nach der Hauptvorteil des Designs der Implementierung der Nachrichten-API im Java SDK: Alles ist konstruktionsbedingt korrekt.

Was bedeutet das genau? Es bedeutet, dass es theoretisch schwierig, im Idealfall sogar unmöglich ist, eine ungültige Nachricht zu erstellen. Ich fordere jeden, der dies liest, auf, zu versuchen, die Nachrichten-API mit dem Java SDK zu "missbrauchen". Lassen Sie es uns gemeinsam versuchen.

MessageRequest ist abstrakt, also müssen wir einen seiner konkreten Subtypen verwenden. Alle diese Untertypen (zum Beispiel, MmsImageRequest) setzen bereits die Channel und MessageType. Außerdem, selbst wenn wir unsere eigene Unterklasse von MessageRequesterstellen würden, könnten wir immer noch nicht die Überprüfung von MessageType und Channel umgehen, da diese im Konstruktor von MessageRequest. Außerdem sind Enums implizit final, so dass wir nicht unsere eigenen MessageType oder Channel hinzufügen oder das Validierungsverhalten außer Kraft setzen.

Außerdem sind die Konstruktoren für alle Unterklassen von MessageRequest entweder geschützt (für abstrakte Klassen) oder paketprivat (für konkrete Klassen), und keine der konkreten Klassen kann erweitert werden, da sie final (dasselbe gilt für die Konstruktoren). Strukturell können wir also keine "schlechte" MessageRequest Klasse erstellen.

Okay, versuchen wir etwas anderes. Wenn wir nicht strukturell missbrauchen können MessageRequestnicht missbrauchen können, können wir vielleicht ungültige Werte an die Bauherren übergeben. Könnten wir zum Beispiel einen leeren Text senden? Oder wie wäre es mit der Übergabe einer unsinnigen Zeichenfolge im Feld to (Empfänger) anstelle einer Zahl? Oder was wäre, wenn wir obligatorische Parameter (wie Absender und Empfänger) weglassen?

Ach ja, noch etwas: Alle dateibasierten Nachrichtentypen (die, die ein Bild, Audio, Video usw. akzeptieren) haben ein URL-Feld. Könnten wir nicht eine ungültige URL-Zeichenfolge eingeben? Oder was ist mit dem Feld "Time-to-Live" in Viber-Nachrichten? Wir könnten eine Zahl eingeben, die außerhalb der zulässigen Grenzen liegt, da es sich nur um eine intist, richtig? Oh, hier ist noch eine Nische: In Messenger-Anfragen können sowohl Category und Tag optional. Die API verlangt jedoch, dass wenn der Category einen Wert von message-taghat, dann muss das Tag vorhanden sein. Und was ist mit...

Lassen wir es dabei bewenden. Alle diese Fälle wurden in Betracht gezogen, und keiner von ihnen ist möglich. Ja, Ihr Code wird kompiliert, wenn Sie versuchen, eine fehlerhafte URL oder eine ungültige Zahl einzugeben, oder wenn Sie es versäumen, obligatorische Parameter für den Builder zu setzen. Aber zur Laufzeit werden Sie die Nachricht nicht senden können. Sie kommen nicht einmal bis zur Konstruktion des MessageRequest Objekts! Und warum? Weil jeder Parameter im Konstruktor validiert wird. In dem Moment, in dem Sie den Builder build() des Builders aufrufen, wird der Konstruktor aufgerufen, und wenn die zur Konstruktion der Nachricht verwendeten Parameter fehlen oder ungültig sind, erhalten Sie eine IllegalArgumentException geworfen, die das Problem erklärt. Das ist es, was ich meinte, als ich sagte: "Korrekt durch Konstruktion". Wenn Sie eine MessageRequestkonstruieren können, dann ist sie, soweit wir das beurteilen können, nicht offensichtlich falsch, und Sie können mit dem Senden fortfahren.

Das bedeutet natürlich nicht, dass die Nachricht vollständig gültig ist oder dass Sie keine Fehlerantwort vom Server zurückbekommen. Sie könnten z. B. versuchen, eine Nachricht an eine Telefonnummer zu senden, die nicht existiert, aber E164-konform ist. Diese Art der Validierung liegt außerhalb des Anwendungsbereichs des SDK und wird vom Backend-Dienst durchgeführt, mit dem die API kommuniziert. Das SDK schützt im Wesentlichen vor den 422-Fehlern. Worin besteht also der Unterschied zu diesen Fehlern? Was will ich damit sagen? Um das zu beantworten, sollten wir uns das diametrale Gegenteil ansehen: cURL.

Warum brauche ich das SDK, wenn ich die API direkt verwenden kann? Das ist in Ordnung. Hier ist ein Beispiel für das Senden eines Bildes über Viber mit cURL:

curl -X POST https://api.nexmo.com/v1/messages \ -H 'Authorization: Bearer '$JWT\ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d $'{ "message_type": "image", "channel": "viber_service", "image": { "url": "https://example.com/image.jpg" }, "to": "447700900000", "from": "9876543210", "viber_service": { "ttl": 600 } }'

Das ist zwar relativ einfach zu lesen, aber beim manuellen Schreiben einer solchen Anfrage kann man leicht etwas falsch machen. Sie müssen sich nicht nur die URL des Endpunkts, die HTTP-Methode, die Header, den Autorisierungstyp usw. merken, sondern auch das JSON-Format. Sie könnten die Anführungszeichen übersehen (fast alles ist ein String), aber achten Sie darauf ttl - es handelt sich um eine ganze Zahl, nicht um eine int in eine Zeichenkette eingewickelt! Sie müssen sich alle Felder und ihre genauen Bezeichnungen merken - keine IDE wird Ihnen hier mit der Autovervollständigung helfen, weil Sie nur Text eingeben.

Ihr Terminal weiß nichts über die API. Wenn Sie also etwas falsch eingeben, können Sie das erst feststellen, wenn Sie die Anfrage abschicken und eine 422-Antwort zurückbekommen. Und wenn man sich dieses Schema merkt, ist es nicht wirklich intuitiv, oder? Wenn man die Time-to-Live einstellen will, muss man daran denken, sie in ein viber_service Objekt zu verpacken. Und vergessen Sie nicht, dass die Bild-URL ebenfalls in einem Objekt sein muss. image Objekt enthalten sein muss. Außerdem kann man bei Viber-Nachrichten keine Bildunterschrift einfügen, aber cURL wird dich nicht daran hindern, es zu versuchen. Du kannst als Textkörper einfügen, was immer du willst. Es muss nicht einmal gültiges JSON sein, geschweige denn dem API-Schema entsprechen!

Um die Nachrichten-API mit cURL zu verwenden, müssen Sie also die API-Referenz zur Hand haben und sich ausgiebig darauf beziehen, um sicherzustellen, dass Ihre Anfragen korrekt sind - nicht nur der Body, sondern auch die Header (z. B. die Autorisierungsmethode). Es ist fehleranfällig, und es gibt nichts, was Sie von Fehlern abhält oder Ihnen hilft, Ihre beabsichtigte Nachrichtenanfrage zu formulieren.

Wenn Sie dagegen die Nachrichten-API über das Java SDK verwenden wollen, brauchen Sie die API-Referenz überhaupt nicht. Alle öffentlichen Methoden, die Sie benötigen, sind dokumentiert und erläutern, was erforderlich und was optional ist, das Verwendungsmuster, Beispiele usw. Auch ohne die Dokumentation zu lesen, können Sie die API mit Hilfe der von Ihrer IDE bereitgestellten Autovervollständigung erkunden. Sie werden z. B. nicht versuchen, eine Beschriftung für ein Viber-Bild festzulegen, da es im Java SDK keine Möglichkeit dazu gibt. Daraus schließen Sie als Benutzer, dass dies nicht unterstützt wird.

Wie sieht es mit der Auffindbarkeit aus? Nun, alle MessageRequest Unterklassen folgen dem [Channel][MessageType]Request Benennungsschema (zum Beispiel, SmsTextRequest). Sie können sogar Ihre IDE verwenden, um alle Unterklassen von MessageRequest. Und es gibt nur einen Weg, Anforderungen zu erstellen: über den Builder, der mit jeder MessageRequest Unterklasse zugeordnet ist. Darüber hinaus sind die Konstruktoren für die Builder nicht öffentlich, so dass die einzige Möglichkeit, einen Builder zu instanziieren, der Aufruf der statischen builder() Methode der Klasse aufzurufen. In Verbindung mit den paketprivaten Konstruktoren verhindert dies, dass sie missbraucht werden.

Eine stark typisierte Sprache wie Java verhindert, dass Sie nicht nur zur Laufzeit, sondern auch zur Kompilierzeit ungültige Werte angeben. Sie können zum Beispiel nicht wissen, welche Werte für Category in dem optionalen viber_service Objekt sind, aber im Java SDK werden sie Ihnen vom Compiler / Ihrer IDE angezeigt, weil es ein Enum ist.

Und die ganze Sache mit dem Einwickeln in ein viber_service Objekt? Darüber brauchen Sie sich keine Gedanken zu machen. Sie legen einfach fest, was Sie für den Builder benötigen, und das SDK kümmert sich für Sie darum. Tatsächlich kümmert sich das SDK um die gesamte Serialisierung und Deserialisierung. Als Benutzer wissen Sie nicht, welches Serialisierungsformat hinter den Kulissen verwendet wird, und müssen sich auch nicht darum kümmern - es wird alles erledigt. Das ist einer der Hauptvorteile der Verwendung eines SDK anstelle einer API: Es ist deklarativ und nicht imperativ. Sie deklarieren welche Sie senden wollen, nicht wie Sie es senden wollen.

Sehen Sie es einmal so: Nehmen wir an, wir haben beschlossen, dass alle unsere APIs nun Avro oder Protobuf (oder, Gott bewahre, einfaches altes XML) anstelle von JSON verwenden werden. Die cURL-Skripte, die Sie herumliegen hatten, müssen umgeschrieben werden. Wenn Sie Glück haben und wir das Schema genau beibehalten haben und Sie zufällig ein RegEx-Guru sind, können Sie die Migration vielleicht zumindest teilweise automatisieren.

Was aber, wenn wir das Schema ändern? Bleiben wir beim obigen Beispiel, was wäre, wenn wir entscheiden, dass der url Parameter nicht mehr in ein Objekt verpackt werden muss? Oder wir entfernen den viber_service Container, der für ttl und category Parameter verwendet wird? Oder was wäre, wenn wir den viber_service Nachrichtentyp in viber umbenennen würden, um konsistent zu sein? Wenn Sie Ihre Anfragen mit cURL von Hand abwickeln, müssen Sie sich all dieser Änderungen bewusst sein. Wenn Sie das Java SDK verwenden, müssen Sie nur auf die nächste Version upgraden - eine einzige Zeichenänderung in Ihrem pom.xml oder build.gradle Datei. Sie müssen nichts umgestalten - das wird alles im Hintergrund erledigt.

Obwohl ich mich auf das Java SDK konzentriert habe, ist es erwähnenswert, dass die starke Typisierung keine Voraussetzung für die Validierung ist. Sie macht es nur einfacher, sie mit Hilfe des Compilers durchzusetzen, als mit handkodierter Logik.

So ist beispielsweise die Implementierung der Nachrichten-API im Python-SDK im Vergleich zum Java-SDK winzig klein (es ist eine einzige Datei mit weniger als 100 Zeilen!), und die des Ruby SDK ist ebenfalls vergleichsweise klein. Beide SDKs führen einige grundlegende Validierungen von Parametern durch.

Der gleiche Ansatz hätte auch im Java SDK verwendet werden können: Wir könnten ein Map-Konstrukt mit den Parametern akzeptieren und sie dynamisch validieren, aber das ist fehleranfälliger, da der Compiler uns nicht helfen kann. Die Größe der Java SDK-Implementierung ist hauptsächlich darauf zurückzuführen, dass Java eine ausführliche Sprache ist; - zum Beispiel ist sie ähnlich wie die C#-Implementierung im Prinzip ähnlich, hat aber wesentlich mehr Codezeilen.

Es mag für den Leser eine interessante Übung sein, die anfänglichen Pull Requests für die Messages v1-Implementierung in den Java, Python, Ruby und PHP SDKs, um dies zu beurteilen.

Dieser strenge Ansatz bei der Entwicklung des SDK hat jedoch auch Nachteile. Nehmen wir an, dass die API irgendwann in der Zukunft das Senden von Videos über Viber unterstützt. Sie können dies sofort mit cURL nutzen, aber ein SDK, das die Kombinationen aus Nachrichtentyp und Kanal validiert, erfordert ein Update.

Es ist auch ein größerer Wartungsaufwand für die SDK-Betreuer, wenn sich die API häufig ändert, weil die Validierungslogik, Datentypen usw. alle aktualisiert werden müssen, um diese Änderungen widerzuspiegeln. Sobald sich die Dinge jedoch eingespielt haben und die API stabil ist (was in der Regel der Fall ist, wenn unsere APIs von der Beta-Phase in die GA-Phase übergehen), wird dies weniger zum Problem. Letztendlich wollen wir die bestmögliche Benutzererfahrung bieten, und ich hoffe, dieser Artikel hat Sie von dem Wert überzeugt, den maßgeschneiderte SDKs für REST-APIs haben.

Wir freuen uns immer über die Beteiligung der Gemeinschaft. Sie können sich uns gerne auf dem Vonage Community Slack oder senden Sie uns eine Nachricht auf Twitter. Wenn Sie Vorschläge für Verbesserungen oder Erweiterungen haben oder einen Fehler entdecken, zögern Sie nicht, einen Fehler auf GitHub.

Share:

https://a.storyblok.com/f/270183/400x400/46a3751f47/sina-madani.png
Sina MadaniJava Developer Advocate

Sina ist Java Developer Advocate bei Vonage. Er hat einen akademischen Hintergrund und ist generell neugierig auf alles, was mit Autos, Computern, Programmierung, Technologie und der menschlichen Natur zu tun hat. In seiner Freizeit geht er gerne spazieren oder spielt wettbewerbsfähige Videospiele.