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

Comment un SDK peut ajouter de la valeur aux API REST

Publié le August 4, 2022

Temps de lecture : 14 minutes

L'API Vonage Messages API v1 a récemment été promue au statut de disponibilité générale. À la suite de cette annonce, l'équipe chargée des outils de relations avec les développeurs a travaillé d'arrache-pied pour mettre en œuvre la prise en charge de cette API dans nos SDK de serveur.

Collectivement, nous avons décidé que chaque SDK devrait mettre en œuvre l'API de la manière la plus logique, compte tenu du langage, de la communauté et de l'approche générale utilisée pour prendre en charge d'autres API dans le SDK, dans un souci de cohérence. Notre objectif est de faire en sorte que chaque SDK soit idiomatique, plutôt que d'adopter une approche unique où l'implémentation semble générée automatiquement. L'API Messages v1 est un bon moyen d'illustrer cela, d'où cet article.

Naturellement, en tant que défenseur des développeurs Java au sein de l'équipe, je vais me concentrer sur la mise en œuvre du SDK Java et décrire la logique qui sous-tend sa conception. Mais tout d'abord, examinons la spécification de l'API Messages APIqui est ce que nous devons mettre en œuvre.

L'API n'a qu'un seul point d'arrivée, qui consiste à envoyer un message par le biais d'une requête POST. La complexité vient du schéma des messages. Il existe deux concepts principaux : les canaux et les types de messages. Le premier fait référence au service utilisé pour envoyer le message (SMS, MMS, WhatsApp, Viber, Facebook Messenger). Le second décrit le support du message (texte, image, audio, vidéo, fichier, etc.).

Chaque canal prend en charge un sous-ensemble de types de messages. Par exemple, vous ne pouvez pas envoyer de vidéo par SMS. Certains canaux, comme WhatsApp, prennent en charge les modèles et les types de messages personnalisés, ce qui vous permet d'envoyer des messages plus complexes ou de partager votre position.

En outre, chaque canal impose des restrictions uniques sur le contenu des messages. Par exemple, la longueur maximale des textes varie d'un canal à l'autre, tout comme les types de fichiers pris en charge pour les messages multimédias - par exemple, WhatsApp prend en charge un large éventail de formats audio, tandis que Messenger ne prend en charge que le format MP3. Pour les messages multimédias, certains canaux autorisent une description textuelle facultative (appelée légende dans la spécification de l'API) pour accompagner le fichier, tandis que d'autres ne le permettent pas.

Cela peut même varier au sein d'un même canal - par exemple, il est possible d'inclure une légende dans un MMS, sauf s'il s'agit d'une vCard (qui, soit dit en passant, est propre au canal MMS). Du point de vue de l'auteur de la mise en œuvre, le défi consiste donc à trouver un moyen structuré et efficace de saisir ces exigences tout en réduisant au minimum la confusion pour les consommateurs de l'API.

En tant que développeur Java, il semble naturel de commencer par le modèle de données. Inévitablement, nous nous retrouvons avec un type abstrait pour représenter un message, qui est accepté par la méthode sendMessage de MessagesClient. Étant donné que la réponse que nous recevons en retour de l'envoi d'un message est la même quels que soient le type de message et le canal, nous n'avons pas besoin de nous préoccuper du message lui-même, nous le transmettons simplement en tant que charge utile JSON et ce que nous recevons en retour est un identifiant unique du message qui a été envoyé. En gardant cela à l'esprit, notre résumé MessageRequest détermine les champs communs à tous les messages, à savoir : le type, le canal, l'expéditeur, le destinataire et une chaîne de référence client facultative. Les canaux et les types de messages étant prédéfinis, nous les représentons sous forme d'énumérations.

Il y a un mélange de paramètres facultatifs et obligatoires (par exemple, la référence du client est facultative, mais l'expéditeur et le destinataire sont obligatoires). Il existe également d'autres paramètres, en fonction des combinaisons de canaux et de types de messages. Comme Java n'a pas d'arguments nommés, nous utilisons le modèle du constructeur pour émuler cela. Vous pouvez en savoir plus à ce sujet ici.

Nous voulons nous assurer que seules des combinaisons valides de type de message et de canal peuvent être construites, c'est pourquoi nous définissons cette matrice dans la section Channel. Nous validons tous les arguments des constructeurs de MessageRequest et de ses sous-types. C'est là que réside, à mon avis, le principal avantage de la conception de l'implémentation de l'API Messages par le SDK Java : tout est correct par construction.

Qu'est-ce que cela signifie exactement ? Cela signifie qu'en théorie, il est difficile, idéalement impossible, de construire un message non valide. Je mets au défi quiconque lisant ceci d'essayer de "mal utiliser" l'API Messages à l'aide du SDK Java. Essayons ensemble.

MessageRequest est abstrait, nous devons donc utiliser l'un de ses sous-types concrets. Tous ces sous-types (par exemple, MmsImageRequest) définissent déjà les valeurs Channel et MessageType. En outre, même si nous créons notre propre sous-classe de MessageRequestnous ne pourrions pas contourner la vérification de la validation du type de message et du canal, puisqu'elle est effectuée dans le constructeur de MessageRequest. De plus, les enums sont implicitement finaux, donc nous ne pouvons pas ajouter nos propres MessageType ou Channel ou remplacer le comportement de validation.

En outre, les constructeurs de toutes les sous-classes de MessageRequest sont soit protégés (pour les classes abstraites), soit privés (pour les classes concrètes), et aucune des classes concrètes ne peut être étendue puisqu'elles sont des final (il en va de même pour les constructeurs). Ainsi, structurellement, nous ne pouvons pas créer une "mauvaise" classe. MessageRequest mauvaise" classe.

D'accord, essayons autre chose. Si nous ne pouvons pas faire une mauvaise utilisation structurelle MessageRequestnous pouvons peut-être transmettre des valeurs invalides aux constructeurs. Par exemple, pourrions-nous envoyer un texte vide ? Ou pourquoi ne pas transmettre une chaîne de caractères absurde dans le champ to (destinataire) au lieu d'un nombre ? Ou si nous omettons des paramètres obligatoires (tels que l'expéditeur et le destinataire) ?

Oh, en voici une autre : tous les types de messages basés sur des fichiers (ceux qui acceptent une image, un fichier audio, une vidéo, etc.) ont un champ URL. Ne pourrions-nous pas transmettre une chaîne d'URL non valide ? Ou qu'en est-il du champ "Time-to-Live" dans les messages Viber ? Nous pourrions définir un nombre qui est en dehors des limites acceptables puisqu'il s'agit simplement d'un champ intn'est-ce pas ? Oh, en voici une autre : dans les requêtes Messenger, tant Category et Tag sont facultatifs. Cependant, l'API exige que si l'élément Category a une valeur de message-tagla balise doit être présente. Et qu'en est-il de...

Arrêtons là. Tous ces cas ont été envisagés et aucun n'est possible. Oui, votre code se compilera si vous essayez de passer une URL malformée, un numéro invalide ou si vous négligez de définir des paramètres obligatoires sur le constructeur. Mais au moment de l'exécution, vous ne pourrez pas envoyer le message. Vous n'arriverez même pas à construire l'objet MessageRequest objet ! Comment cela se fait-il ? Parce que chaque paramètre est validé dans le constructeur. Dès que vous appelez build() sur le Builder, le constructeur est invoqué, et si les paramètres utilisés pour construire le message sont manquants ou invalides, alors vous recevrez un IllegalArgumentException vous sera envoyé pour vous expliquer le problème. C'est ce que je voulais dire lorsque j'ai parlé de "correct par construction". Si vous pouvez construire un MessageRequestalors, pour autant que nous puissions en juger, il n'est pas manifestement erroné et vous pouvez l'envoyer.

Bien entendu, cela ne signifie pas que le message est totalement valide ou que vous ne recevrez pas de réponse d'erreur de la part du serveur. Vous pourriez, par exemple, essayer d'envoyer un texte à un numéro de téléphone qui n'existe pas, mais qui est conforme à la norme E164. Ce type de validation dépasse la portée du SDK et est effectué par le service dorsal avec lequel l'API communique. Le SDK se prémunit essentiellement contre les erreurs 422. Alors, à quoi cela s'oppose-t-il ? Où est-ce que je veux en venir ? Pour répondre à cette question, examinons l'opposé diamétral : cURL.

Pourquoi ai-je besoin du SDK alors que je peux utiliser l'API directement ? Voici un exemple d'envoi d'une image sur Viber à l'aide de 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 } }'

Bien que cela soit relativement facile à lire, il est facile de se tromper lorsque l'on écrit une telle requête manuellement. Vous devez non seulement vous souvenir de l'URL du point de terminaison, de la méthode HTTP, des en-têtes, du type d'autorisation, etc. mais aussi du format JSON. Vous pourriez oublier les guillemets (presque tout est une chaîne de caractères), mais faites attention à ceci ttl - il s'agit en fait d'un entier, et non d'un int enveloppé dans une chaîne de caractères ! Vous devez vous souvenir de tous les champs et de leur dénomination exacte - aucun IDE ne vous aidera à utiliser l'auto-complétion parce que vous ne faites que taper du texte.

Votre terminal ne sait rien de l'API, donc si vous faites une erreur de saisie, il n'y a aucun moyen de le savoir tant que vous n'avez pas envoyé la requête et reçu une réponse 422 en retour. Et se souvenir de ce schéma n'est pas vraiment intuitif, n'est-ce pas ? Si vous voulez définir le time-to-live, vous devez vous rappeler de l'envelopper dans un objet viber_service objet. Et n'oubliez pas que l'URL de l'image doit également être dans un objet image et vous ne pouvez pas inclure de légende avec votre image pour les messages Viber, mais cURL ne vous empêchera pas d'essayer. Vous pouvez passer ce que vous voulez comme corps. Il n'est même pas nécessaire que ce soit du JSON valide, et encore moins que ce soit conforme au schéma de l'API !

Il est donc juste de dire que pour utiliser l'API Messages avec cURL, vous allez avoir besoin de la référence de l'API à portée de main et vous y référer fréquemment pour vous assurer que vos requêtes sont correctes - non seulement le corps mais aussi les en-têtes (méthode d'autorisation, par exemple). C'est une source d'erreurs, et il n'y a rien pour vous empêcher de faire des erreurs ou pour vous aider à formuler la demande de message que vous souhaitez.

En revanche, si vous souhaitez utiliser l'API Messages via le SDK Java, vous n'avez pas du tout besoin de la référence API. Toutes les méthodes publiques dont vous avez besoin sont documentées, expliquant ce qui est nécessaire et ce qui est optionnel, le modèle d'utilisation, des exemples, etc. Même sans lire la documentation, vous pouvez explorer l'API en vous contentant de l'auto-complétion fournie par votre IDE. Par exemple, vous n'essaierez pas de mettre une légende sur une image Viber parce qu'il n'y a aucun moyen de le faire dans le SDK Java. En tant qu'utilisateur, vous en concluez donc que l'API n'est pas prise en charge.

Qu'en est-il de la découvrabilité ? Eh bien, toutes les MessageRequest suivent le schéma de dénomination [Channel][MessageType]Request (par exemple, SmsTextRequest). Vous pouvez même utiliser votre IDE pour lister toutes les sous-classes de MessageRequest. Et il n'y a qu'une seule façon de construire des requêtes : par l'intermédiaire du constructeur associé à chaque sous-classe de MessageRequest sous-classe. De plus, les constructeurs ne sont pas publics, de sorte que la seule façon d'instancier un constructeur est d'appeler la méthode statique builder() de la classe. Ceci, combiné avec les constructeurs privés du paquet, vous empêche de les utiliser à mauvais escient.

Un langage fortement typé comme Java vous empêche de fournir des valeurs non valides, non seulement au moment de l'exécution, mais aussi au moment de la compilation. Par exemple, vous pouvez ne pas savoir quelles sont les valeurs valides pour Category dans l'objet optionnel viber_service mais dans le SDK Java, elles vous sont indiquées par le compilateur ou votre IDE parce qu'il s'agit d'une énumération. il s'agit d'un enum.

Et l'idée de l'envelopper dans un viber_service ? Vous n'avez pas à vous en préoccuper, il vous suffit de définir ce dont vous avez besoin dans le constructeur, et le SDK s'en chargera pour vous. En fait, le SDK s'occupe de toute la sérialisation et la désérialisation. En tant qu'utilisateur, vous ne savez pas ou n'avez pas besoin de vous soucier du format de sérialisation utilisé en coulisses - tout est pris en charge. C'est l'un des principaux avantages de l'utilisation d'un SDK au lieu d'une API : il est déclaratif plutôt que impératif. Vous déclarez ce que que vous voulez envoyer, et non comment l'envoyer.

Voyons les choses sous cet angle : supposons que nous décidions que toutes nos API utiliseront désormais Avro ou Protobuf (ou, Dieu nous en préserve, le bon vieux XML) au lieu de JSON. Les scripts cURL que vous aviez en réserve vont devoir être réécrits. Peut-être que si vous avez de la chance, que nous avons gardé le même schéma et que vous êtes un gourou du RegEx, vous pourrez au moins automatiser partiellement la migration.

Mais que se passe-t-il si nous modifions le schéma ? Pour rester dans l'exemple précédent, que se passerait-il si nous décidions que le paramètre url n'a plus besoin d'être enveloppé dans un objet ? Ou si nous supprimions le viber_service qui est utilisé pour ttl et category ? Et si nous renommions le type viber_service en viber par souci de cohérence ? Si vous faites vos requêtes à la main avec cURL, vous devez être au courant de tous ces changements. Si vous utilisez le SDK Java, il vous suffit de passer à la version suivante - un changement d'un seul caractère dans votre fichier pom.xml ou build.gradle dans votre fichier or. Il n'est pas nécessaire de remanier quoi que ce soit, tout est pris en charge dans les coulisses.

Bien que je me sois concentré sur le SDK Java, il convient de noter que le typage fort n'est pas une condition préalable à la validation. Il permet simplement de l'appliquer plus facilement en utilisant le compilateur plutôt qu'une logique codée à la main.

Par exemple, la mise en œuvre de l'API Messages par le SDK Python est minuscule par rapport au SDK Java (il s'agit d'un fichier unique de moins de 100 lignes !), et du SDK Ruby du SDK Ruby est également relativement petite. Ces deux SDK effectuent une validation de base des paramètres.

La même approche aurait pu être utilisée dans le SDK Java : nous aurions pu accepter une construction Map avec les paramètres et les valider dynamiquement, mais cette approche est plus sujette aux erreurs puisque le compilateur ne peut pas nous aider. La taille de l'implémentation du SDK Java est principalement due au fait que Java est un langage verbeux ; - par exemple, elle est similaire à l'implémentation en C# en principe, mais avec beaucoup plus de lignes de code.

Il peut être intéressant pour le lecteur de comparer les demandes initiales de retrait de l'implémentation de Messages v1 dans l'ensemble de la base de données de Java, Python, Ruby et PHP SDK pour l'évaluer.

Cette approche stricte de la conception du SDK présente toutefois des inconvénients. Supposons qu'à l'avenir, l'API prenne en charge l'envoi de vidéos sur Video. Vous pouvez immédiatement en tirer parti avec cURL, mais un SDK qui valide les combinaisons de type de message et de canal nécessitera une mise à jour.

La charge de maintenance est également plus lourde pour les responsables du SDK lorsque l'API change fréquemment, car la logique de validation, les types de données, etc. doivent tous être mis à jour pour refléter ces changements. Mais une fois que les choses sont réglées et que l'API est stable (ce qui est généralement le cas lorsque nos API passent de la version bêta à la version GA), ce problème devient moins important. En fin de compte, notre objectif est de fournir la meilleure expérience utilisateur possible, et j'espère que cet article vous a convaincu de la valeur que les SDK sur mesure peuvent ajouter aux API REST.

La participation de la communauté est toujours la bienvenue. N'hésitez pas à nous rejoindre sur le Communauté Vonage Slack ou envoyez-nous un message sur sur Twitter. Si vous avez des suggestions d'amélioration, des améliorations ou si vous repérez un bogue, n'hésitez pas à soulever un problème sur GitHub.

Partager:

https://a.storyblok.com/f/270183/400x400/46a3751f47/sina-madani.png
Sina MadaniVonage Ancien membre de l'équipe

Sina est développeur Java chez Vonage. Il est issu d'une formation universitaire et est généralement curieux de tout ce qui touche aux voitures, aux ordinateurs, à la programmation, à la technologie et à la nature humaine. Pendant son temps libre, on peut le trouver en train de marcher ou de jouer à des jeux vidéo compétitifs.