01/06/2024 - 12:08:25
14'46"
Introduction
TRQL Radio est bourré de services : passage au prochain morceau, passage d'un jingle, saut "quantique" dans le catalogue de musique, passage d'une locomotive, passage d'une annonce de sponsoring, passage des tops horaire, liste d'artistes correspondant à des critères spécifiques, météo, recettes de cuisine, géolocalisation et points d'intérêt aux coordonnées courantes, liste des anniversaires de naissance et de décès d'artistes, today in history, nouvelles du jour, devises par rapport à l'euro, cotation des cryptos, faillites par pays, index de corruption, notation attribuée par les agences de notation par pays, PIB par pays, … Nous avons plus de 200 services qui sont définis pour faire tourner la radio. Les services sont utilisés partout !
Pour développer nos services, nous avons décidé, comme beaucoup de gens avant nous et beaucoup de gens après nous, de nous baser sur un système de spécifications standardisées, j'ai nommé OpenAPI, spécifiquement la version 3.0. Dès lors, je vous partage notre expérience en la matière.
Objectifs
Au terme de cet article, (1) vous savez à quoi ressemble une petite API de trois services (nous les puiserons dans nos propres définitions), (2) vous avez compris toutes les données/variables/scalaires/structures utilisés, (3) vous êtes capable de créer votre propre petite API en OpenAPI 3.0 et (4) vous disposez d'une liste de référence miniimale pour vous dirigez dans ce monde de OpenAPI.
OpenAPI 2.0 vs. OpenAPI 3.0
Alors, TRQL Radio est parti sur OpenAPI 3.0 en 2017. Avant cela, OpenAPI en était la version 2.0, une version que nous n'avons pas utilisée. Avec la version 3.0 d'OpenAPI (la version courante est la 3.1.0) une série de changements sont intervenus dans la manière de concevoir les objets de base que permettait le langage YAML et utilisés dans la description d'une API. Nous faisons l'impasse sur la spec 2.0, comme nous faisons aussi l'impasse sur Swagger (ancêtre de OpenAPI) pour directement décrire la version 3.0. Voici ces objets de base qui constituent les éléments fondamentaux pour décrire une API et avec lesquels il faut pouvoir jouer …
Objets de base de OpenAPI 3.0
Un document OpenAPI (OpenAPI object) tel que décrit par le langage YAML est un objet (une structure) qui en contient d'autres. Pour être valide, il doit contenir un ensemble de choses lesquelles sont identifiées par une petite étoile dans la liste ci-dessous.
L'objet OpenAPI est donc l'objet racine du document OpenAPI et il combine toutes les informations de l'API.
openapi
: il s'agit d'une simple chaîne de caractères. Cette chaîne DOIT être le numéro de la version de OpenAPI utilisée.Info Object
: Fournit des métadonnées sur l'API, comme le titre, la version, etc.Paths Object
: Contient les chemins disponibles et les opérations associées à l'API.Servers Object
: Tableau de serveurs dont on spécifie les URL de base que l'API utilise pour les appels.Components Object
: Contient divers composants réutilisables pour l'API, tels que les schémas, les réponses, les paramètres, les exemples, les requêtes et les en- têtes.Security Schemes Object
: Tableau Définit les mécanismes de sécurité utilisés par l'API. Le nom utilisé pour chaque propriété DOIT correspondre à un schéma de sécurité déclaré dans les schémas de sécurité de l'objet Composants.External Docs object
: Documentation externe supplémentaire si nécessaire
Ces objets sont combinés pour créer une spécification complète qui décrit les aspects fonctionnels et techniques d'une API. Pour plus de détails, vous pouvez consulter la spécification OpenAPI v3.1.0, la dernière spec en cours, qui contient des informations complètes sur chaque objet et ses champs.
PAS DE PANIQUE : nous n'avons pas besoin de tous ces objets et propriétés pour démarrer !
Autres objets utiles de OpenAPI 3.0
Contact Object
: Informations de contact.License Object
: Informations sur la licence de l'API exposée.Operation Object
: Décrit une opération API unique sur un path.Server Object
: Informations concernant le ou les serveurs qui seront suscités par les requêtes API.
L'eau à la bouche
Pour vous mettre l'eau à la bouche, et bien que cela ne soit pas notre manière de décrire une API, l'exemple qui suit a l'énorme avantage de vous faire comprendre vers quoi nous allons. Premier exemple de YAML qui décrirait une API (MAIS qui ne correspond pas du tout à OpenAPI 3.0!) :
YAML
microservices: - microservice: name: bankruptcies title: List of bankruptcies per country description: List of bankruptcies for 178 countries\:name, last numbers, previous numbers, last update
XML
Peut-être comprendrez-vous mieux la structure XML :
<microservices> <microservice> <name>bankruptcies</name> <title>List of bankruptcies per country</title> <description>List of bankruptcies for 178 countries\:name, last numbers, previous numbers, last update</description> </microservice> </microservices>
C'est donc vers cela qu'on se dirige et dans notre cas on fera cela pour une API qu'on appellera TRQL Economics regroupant 3 services ! Sans que cela soit l'implémentation réelle, voici à quoi pourrait ressembler unne telle API :
XML
<microservices> <microservice> <name>bankruptcies</name> <title>Bankruptcies per country</title> <description>List of bankruptcies for 178 countries \:name, last numbers, previous numbers, last update</description> </microservice> <microservice> <name>corruption</name> <title>Corruption index per country</title> <description>Corruption index for a list of 178 countries \:name, last index, previous index, last update</description> </microservice> <microservice> <name>currencies</name> <title>EUR to currency list</title> <description>EUR to currency rate for a list of 9 countries \:name, last rate, previous rate, daily delta, daily percentage, weekly percentage, annual percentage</description> </microservice> </microservices>
YAML correspondant
microservices: microservice: - name: bankruptcies title: Bankruptcies per country description: >- List of bankruptcies for 178 countries \:name, last numbers, previous numbers, last update - name: corruption title: Corruption index per country description: >- Corruption index for a list of 178 countries \:name, last index, previous index, last update - name: currencies title: EUR to currency list description: >- EUR to currency rate for a list of 9 countries \:name, last rate, previous rate, daily delta, daily percentage, weekly percentage, annual percentage
Structure minimale de description d'une API
Pour être valide, un document OpenAPI doit au minimum contenir 3 éléments : openapi, info, et paths.
openapi
C'est la fameuse chaîne de caractères qui spécifie quelle est
version de OpenAPI qu'on utilise. Dans notre cas, bien que notre catalogue de
services fait référence à OpenAPI 3.0.0, nous allons utiliser la
dernière version, c'est-à-dire, 3.1.0
ce qui ne poretra pas à
conséquence car tout ce que nous avons défini dans le passé est
strictement compatible avec la dernière version
info
object
Field Name | Type | Description |
---|---|---|
title | string |
REQUIS. Le titre de l'appplication. |
description | string |
Une courte description de l'application. La syntaxe CommonMark peut être utilisée pour des représentations textuelles riches. |
termsOfService | string |
Une URL vers les conditions d'utilisation de l'API. Doit être au format d'une URL. |
contact | Contact Object |
Les informations de contact pour l'API exposée. |
license | License Object |
Les informations de licence pour l'API exposée. |
version | string |
REQUIS. La version du document OpenAPI (qui se distingue de la version de la spécification OpenAPI ou de la version de l'implémentation de l'API). |
Comme vous pouvez le constater, peu d'infos sont absolument nécessaires. Pas de panique non plus !
paths
object
Field Name | Type | Description |
---|---|---|
$ref | string |
Permet de disposer d'une définition
externe (utilisation du "$ ") pour ce
path. La structure référencée doit l'être dans
le format d'un objet Path. S'il y a conflit entre la
définition référencée et ce qui est attendu, le
comportement est indéfini. |
summary | string |
Un résumé facultatif, sous forme de chaîne, destiné à s'appliquer à toutes les opérations de ce path. |
description | string |
Une description facultative, sous forme de chaîne, destinée à s'appliquer à toutes les opérations ce path. La syntaxe CommonMark peut être utilisée pour des représentations textuelles riches. |
get | Operation Object |
Définition d'une opération GET pour ce path. |
put | Operation Object |
Définition d'une opération PUT pour ce path. |
post | Operation Object |
Définition d'une opération POST pour ce path. |
delete | Operation Object |
Définition d'une opération DELETE pour ce path. |
options | Operation Object |
Définition d'une opération OPTIONS pour ce path. |
head | Operation Object |
Définition d'une opération HEAD pour ce path. |
patch | Operation Object |
Définition d'une opération PATCH pour ce path. |
trace | Operation Object |
Définition d'une opération TRACE pour ce path. |
servers | Server Object |
Un tableau server alternatif pour servir toutes les opérations dans ce path |
parameters | Parameter Object |
Reference Object |
Une liste de paramètres applicables
pour toutes les opérations décrites pour ce
path. Ces paramètres peuvent être redéfinis
pour chaque opération, mais ne peuvent être éliminés. La
liste ne PEUT PAS inclure de doublons. Un paramètre
unique parameter est défini par une
combinaison formée d'un nom (name ) et d'une
destination (location ). La liste peut
utiliser un Reference Object pour être
couplée aux paramètres qui sont définis dans les
composants et paramètres de l'objet OpenAPI. |
Je répète le même argument : bien que ce tableau puisse être imposant vous n'utiliserez pas beaucoup de ses propriétés. Rappelez-vous que je vous ai dit que chez TRQL Radio, on utilisait essentiellement les GETs et c'est d'ailleurs 3 services de type REST, méthode GET, que nous allons documenter. Ne soyez pas interdit d'aller plus avant !
Première mise en musique
Alors, maintenant que vous savez tout cela, voyons comment on va créer un premier jet de document OpenAPI en YAML. Ce n'est pas très long et vous voyez donc que vous n'avez aucunement à être impressionné !
openapi: 3.1.0 info: version: 1.0 title: "Economics" description: "Specifications for the TRQL Radio services related to economics data" termsOfService: https://www.trql.fm/tos/ contact: name: Pat Boens email: pb@latosensu.be license: name: Creative Commons Attribution-ShareAlike 4.0 International url: https://creativecommons.org/licenses/by-sa/4.0/ paths: {}
Voici ce que cela donne en XML :
<openapi>3.1.0</openapi> <info> <version>1</version> <title>Economics</title> <description>Specifications for the TRQL Radio services related to economics data</description> <termsOfService>https://www.trql.fm/tos/</termsOfService> <contact> <name>Pat Boens</name> <email>pb@latosensu.be</email> <license> <name>Creative Commons Attribution-ShareAlike 4.0 International</name> <url>https://creativecommons.org/licenses/by-sa/4.0/</url> </license> </contact> </info> <paths/>
Bien entendu, c'est la toute base ! Aucune opération n'est définie. On va s'en occuper par la suite et vous verrez que ce n'est pas bien sorcier non plus !
Ajoutons un (ou des) serveur(s)
Les informations concernant le serveur sur lequel les services sont disponibles ne sont pas obligatoires. Nous allons quand même en faire usage car elles simplifient la vie de la définition de nos services à venir.
Pour rappel, nous nous sommes fixés l'objectif de construire une API qui regroupe 3 services comme je l'avais précisé dans un YAML factice auquel, ici, j'ajoute la question de endpoint (couleur verte) justement pour être capable de faire le pont avec la question de serveur :
microservices: microservice: - name: bankruptcies title: Bankruptcies per country description: >- List of bankruptcies for 178 countries \:name, last numbers, previous numbers, last update endpoint: https://www.trql.fm/vaesoli!/?bankruptcies - name: corruption title: Corruption index per country description: >- Corruption index for a list of 178 countries \:name, last index, previous index, last update endpoint: https://www.trql.fm/vaesoli!/?corruption - name: currencies title: EUR to currency list description: >- EUR to currency rate for a list of 9 countries \:name, last rate, previous rate, daily delta, daily percentage, weekly percentage, annual percentage endpoint: https://www.trql.fm/vaesoli!/?currencies
L'objet server
est relativement simple. En voici
la définition :
Field Name | Type | Description |
---|---|---|
url | string |
REQUIS. URL de l'hôte cible. Cette URL prend
en charge les variables de serveur et PEUT être relative, pour indiquer que
l'emplacement de l'hôte est relatif à l'emplacement où le document OpenAPI
est servi. Des substitutions de variables seront effectuées lorsqu'une
variable est nommée entre {accolades} . |
description | string |
Chaîne facultative décrivant l'hôte désigné par l'URL. La syntaxe CommonMark peut être utilisée pour des représentations textuelles riches. |
variables | Map string , Server Variable Object |
Une correspondance entre le nom d'une variable et sa valeur. La valeur est utilisée pour la substitution dans le modèle d'URL du serveur. |
PAS DE PANIQUE : vous connaissez la
rengaine. C'est pas bien compliqué et il y a très peu de choses à
mentionner pour avoir une définition minimale et puisque mon mantra
c'est Pas de complication !
… on va rester dans les
choses sipmples !
Et donc … qu'est-ce que cela donne ? Et bien un truc très simple. Pour le coup, je vais décrire 3 serveurs : un serveur de production et un serveur de test, juste pour l'exemple car chez TRQL Radio nous n'avons qu'un seul et même serveur [1] .
Cela va être super simple ! Vous êtes prêts ?
servers: - url: https://dev.trql.fm/?vaesoli description: Development Server - url: https://acceptance.trql.fm/?vaesoli description: Acceptance Server - url: https://www.trql.fm/?vaesoli description: Production Server
Et oui … c'est vraiment tout ! Pfff … c'est trop simple !
Alors, à quoi ressemble notre document OpenAPI maintenant si nous lui ajoutons la partie "serveurs" ?
openapi: 3.1.0 info: version: 1.0 title: "Economics" description: "Specifications for the TRQL Radio services related to economics data" termsOfService: https://www.trql.fm/tos/ contact: name: Pat Boens email: pb@latosensu.be license: name: Creative Commons Attribution-ShareAlike 4.0 International url: https://creativecommons.org/licenses/by-sa/4.0/ servers: - url: https://dev.trql.fm/?vaesoli description: Development Server - url: https://acceptance.trql.fm/?vaesoli description: Acceptance Server - url: https://www.trql.fm/?vaesoli description: Production Server paths: {}
Allons au %coeur% : ajout de paths
C'est ma partie préférée car c'est elle qui va me permettre de spécifier les appels et les retours de mes services. Bon, c'est vrai que je dois reconnaître que ma partie favorite reste … l'écriture du code même des services.
Bref rappel : les paths sont REQUIS ! On s'en est tiré jusqu'à
présent en fournissant une donnée factice ({}
—
tableau vide). Cette fois, il s'agit d'être concret !
Vous vous souvenez de ce que nous utilisons que très peu de méthodes HTTP différentes ? Très souvent, il s'agit de méthodes GET. Cela va nous faciliter la tâche !
Voci ce que cela donne en YAML:
paths: ?bankruptcies: get: responses: '200': description: List of bankruptcies per country ?corruption: get: responses: '200': description: List of corruption index per country ?currencies: get: responses: '200': description: List of 8 rates of currencies vs. EURO
Ajoutons cette partie à notre YAML précédent :
openapi: 3.1.0 info: version: 1.0 title: "Economics" description: "Specifications for the TRQL Radio services related to economics data" termsOfService: https://www.trql.fm/tos/ contact: name: Pat Boens email: pb@latosensu.be license: name: Creative Commons Attribution-ShareAlike 4.0 International url: https://creativecommons.org/licenses/by-sa/4.0/ servers: - url: https://dev.trql.fm/?vaesoli description: Development Server - url: https://acceptance.trql.fm/?vaesoli description: Acceptance Server - url: https://www.trql.fm/?vaesoli description: Production Server paths: ?bankruptcies: get: responses: '200': description: List of bankruptcies per country ?corruption: get: responses: '200': description: List of corruption index per country ?currencies: get: responses: '200': description: List of 8 rates of currencies vs. EURO
Vous le croirez ou pas, mais nous venons de définir notre API de trois services. Ça y est !
Liens externes utiles
Notes de bas de page
[1] … Quand nous développons un service, nous le développons avec un nom bidon, souvent généré aléatoirement. Ce service se trouve disponible directement production ! Je n'ose pas vous proposer la même approche, même si elle est particulièrement efficace. C'est la raison pour laquelle, ici, nous mentionnerons 3 serveurs, comme c'est un peu l'usage : un serveur de développement, un serveur d'acceptance (ou staging) et un serveur de production.