Newsletter Developpez.com

Inscrivez-vous gratuitement au Club pour recevoir
la newsletter hebdomadaire des développeurs et IT pro

Tutoriel pour réaliser les spécifications d'un service web REST de manière simple et compréhensible en utilisant RAML 1.0

Dans nos précédents tutoriels (« Tutoriel pour développer et déployer un service web RESTFUL de base sous Eclipse et Karaf », « Tutoriel pour développer et déployer un service Web RESTFUL OSGI multibundle sous Eclipse et Karaf » et « Tutoriel pour mettre en place et exécuter les tests unitaires sur un projet multibundle déployé sous Karaf et développé sous Eclipse »), et comme leur nom l'indique, nous avons essentiellement considéré le développement et les tests de services web RESTRepresentational State Transfer.

Néanmoins, comme pour tout projet informatique, il est essentiel d'écrire des spécifications avant de commencer le développement, ceci afin de bien délimiter le périmètre du projet, inventorier les contraintes et anticiper les éventuels problèmes.

En ce qui concerne les services web RESTRepresentational State Transfer, les spécifications peuvent s'exprimer par l'intermédiaire de différents langages (RAMLRESTful API Modeling Language, YAML, markdown) et projets qui utilisent ces langages. Parmi eux, les plus connus sont : « Swagger », « API Blueprint », « WADL », « Slate » ou encore « RAMLRESTful API Modeling Language ».

En termes de comparaison, RAMLRESTful API Modeling Language, en plus d'être fortement soutenu dans le milieu des « APIApplication Programming Interface » (notamment par MuleSoft), est très facile à appréhender grâce aux nombreux exemples fournis par le site web de l'organisation qui est à l'origine de ce langage. En outre, les spécifications RAMLRESTful API Modeling Language sont manipulables aussi bien par les développeurs que par les non-développeurs.

Pour ces raisons, nous avons choisi de consacrer un tutoriel à RAMLRESTful API Modeling Language. Dans ce tutoriel, non seulement nous apprendrons à utiliser ce langage, mais nous verrons également sa mise en œuvre à travers différents outils. L'outil d'édition est constitué d'une console fournissant un aperçu du produit final en fonction du code saisi. RAMLRESTful API Modeling Language fournit également une console permettant aux développeurs d'interagir avec l'APIApplication Programming Interface.

Bien que ces outils soient relativement efficaces et fassent gagner beaucoup de temps, nous ne les étudierons qu'en seconde partie de ce tutoriel, car le but avant de se simplifier la vie pour la conception de spécifications d'APIApplication Programming Interface RESTful est d'apprendre et de comprendre les rudiments de RAMLRESTful API Modeling Language.

Enfin, nous terminerons par une comparaison entre RAMLRESTful API Modeling Language et ses alternatives.

Si vous souhaitez donner votre avis sur le contenu de cet article, exprimez votre opinion, profitez de cette discussion : 1 commentaire Donner une note à l'article (5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Liens utiles

Les spécifications RAMLRESTful API Modeling Language : https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md

Un guide de bonnes pratiques pour la mise en œuvre d'APIApplication Programming Interface RESTRepresentational State Transfer : http://blog.octo.com/designer-une-api-rest/

II. Configuration logicielle

  • Windows 10
  • ATOM 1.10.2
  • API Workbench 0.8.38
  • API Console 3.0.8
  • Tomcat 8.5.5

III. Qu'est-ce que RAML ?

RAMLRESTful API Modeling Language est un langage structuré basé sur YAML (un format de représentation de données) permettant de décrire les données en entrée et en sortie des services web RESTRepresentational State Transfer.

Avec RAMLRESTful API Modeling Language, il est possible de décrire des ressources, des méthodes et d'autres aspects de conception telle que la sécurité.

RAMLRESTful API Modeling Language permet de décrire les APIApplication Programming Interface RESTRepresentational State Transfer et de faciliter la communication entre développeurs. Il permet d'avoir un aperçu de la conception à un stade précoce, ce qui permet d'anticiper les changements à effectuer dans le programme et de communiquer des idées aux membres de l'équipe avant de démarrer le développement, ce qui favorise leur implication dans le processus de développement.

En outre, RAMLRESTful API Modeling Language intègre deux des meilleures pratiques des développeurs : les « design patterns » et la réutilisabilité du code.

RAMLRESTful API Modeling Language vous permet de profiter des avantages des bibliothèques, du polymorphisme, de la réutilisation des traitements (via les « traits » - Voir le paragraphe IV.B.10 Le nœud « traits »), de la réutilisation des ressources (via les « resourceType » - Voir le paragraphe IV.B.11 Le nœud « resourceTypes ») pour vous assurer que votre APIApplication Programming Interface reste cohérente, tout en vous permettant d'économiser beaucoup de temps.

IV. Les bases de RAML

IV-A. La racine des spécifications RAML

On peut assimiler les spécifications d'une APIApplication Programming Interface RESTRepresentational State Transfer en RAMLRESTful API Modeling Language à un fichier texte d'extension « .raml ». Le contenu de ce fichier est composé d'un ensemble de nœuds qui définissent la racine des spécifications.

Pour définir une APIApplication Programming Interface RESTRepresentational State Transfer basique en RAMLRESTful API Modeling Language, nous pouvons partir d'un squelette de fichier tel que nous le présentons ci-dessous. Dans ce squelette, chacune des lignes commence par ce qui est appelé un « nœud » et qui peut être assimilé à une balise en XML :

Racine spécification RAML
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
#%RAML 1.0
title: 
description: 
version: 
baseUri: 
baseUriParameters: 
protocols: 
mediaType:
documentation: 
annotationTypes:
traits:
resourceTypes:
types:
/vehicules:  #Exemple de nom de ressource 
/conducteurs: #Exemple de nom de ressource

Afin de bien assimiler les rouages de RAMLRESTful API Modeling Language, nous renseignerons, dans ce tutoriel, les nœuds ci-dessus les uns après les autres tout en expliquant leur utilité.

Dans le code ci-dessus, #%RAML 1.0 indique que le document est un fichier RAMLRESTful API Modeling Language dont la version de ce langage est 1.0. Cette ligne constituera toujours la première ligne du fichier.

La racine du document commence véritablement à la seconde ligne. Dans le paragraphe suivant, nous expliquerons la raison d'être de chacun de ces nœuds en l'illustrant par un exemple.

Puis, plus loin, nous verrons, la manière de compléter ces nœuds par d'autres nœuds dans le but de manipuler des « design pattern » ou encore de faire appel à des bibliothèques externes et, à fortiori, de réutiliser le code.

IV-B. Description des nœuds

À partir du squelette de fichier présenté ci-dessus, nous allons créer, petit à petit, une APIApplication Programming Interface RAMLRESTful API Modeling Language en présentant l'utilité de chacun des nœuds et en les complétant avec les valeurs qu'il est possible de leur affecter.

IV-B-1. Le nœud « title »

Il s'agit d'un nœud obligatoire qui permet de nommer l'APIApplication Programming Interface.

Exemple :

Exemple title
Sélectionnez
1.
title: API de gestion d′un parc automobile

IV-B-2. Le nœud « description »

Il s'agit d'un nœud facultatif qui permet de décrire l'APIApplication Programming Interface. La description peut être rédigée au format markdown.

Exemple :

Exemple description
Sélectionnez
1.
description: API permettant de gérer les opérations CRUD ( create, read, update, delete) relatives la gestion d′un parc de véhicules

IV-B-3. Le nœud « version »

Il s'agit d'un nœud facultatif qui permet de préciser la version de l'APIApplication Programming Interface (v1, v1.2…).

Exemple :

Exemple version
Sélectionnez
1.
version: v1

Afin de respecter les conventions, ce numéro de version devra figurer au plus haut niveau du chemin de l'URIUniform Resource Identifier.

Exemple : https://dijon.garage.com/v1/vehicules

IV-B-4. Le nœud « baseUri »

Il s'agit d'un nœud facultatif qui précise le endpoint du service web RESTRepresentational State Transfer.

Il renseigne sur l'URL de base qui permet l'accès aux différentes ressources. Cette URL de base peut être une URL classique (de la forme « /XXXX… » ou « http://XXXX… » ou « https://XXXX… ») ou un « template » (du type « /{yyyy}… » ou « http://XXXX/{yyyy}… » ou « https://XXXX/{yyyy}… ») dans lequel le paramètre qui figure entre les accolades « {} » est défini obligatoirement dans le nœud « baseUriParameters » présenté dans le paragraphe suivant.

Dans le cas où un ou plusieurs caractères « / » sont placés à la fin de l'URL de base, ces caractères seront ignorés.

Par exemple :

Dans l'exemple ci-dessous, les chemins absolus aux ressources « vehicules » et « voitures » seront respectivement et  :

Exemple baseUri
Sélectionnez
1.
2.
3.
baseUri: https://{ville}.garage.com/{release}/
/vehicules:
     /voitures:

Ce qui revient donc à écrire :

Exemple baseUri 2
Sélectionnez
1.
2.
3.
baseUri: https://{ville}.garage.com/{release}
/vehicules:
     /voitures:

Remarquez l'absence du « / » à la fin de l'URIUniform Resource Identifier dans le second exemple.

IV-B-5. Le nœud « baseUriParameters »

Il s'agit d'un nœud facultatif qui définit les paramètres nommés utilisés dans le nœud « baseUri » si ce dernier définit un « template » (comme nous l'avons vu ci-dessus).

Par exemple :

Exemple baseUriParameters
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
baseUri: https://{ville}.garage.com/{version}
baseUriParameters: 
    ville:
        description: Sous-domaine auquel l′API est accessible.
        enum: [ paris, dijon]
    release:
        description: Version de l′API

Nous voyons, dans l'exemple ci-dessus, qu'il est fait référence à deux « templates » dans le nœud « baseUri » qui sont « ville » et « version ».

Ces deux « templates » sont définis dans le nœud « baseUriParameters ».

IV-B-6. Le nœud « protocols »

Il s'agit d'un nœud facultatif qui définit le protocole supporté par l'APIApplication Programming Interface.

Si ce nœud n'est pas renseigné, seul(s) le/les protocole(s) inclus dans le nœud « baseUri » est/sont utilisé(s).

Si ce nœud est spécifié, ce sont les protocoles énumérés dans ce nœud qui sont pris en compte. Le nœud « protocols » prend comme valeur un tableau non vide de chaînes de caractères dans lequel les chaînes sont « HTTP » et/ou « HTTPS ». Ces valeurs ne sont pas sensibles à la casse.

Exemple :

Exemple protocols
Sélectionnez
1.
2.
baseUri: https://{apiDomain}.garage.com/{version}
protocols: [ HTTP, HTTPS ]

IV-B-7. Le nœud « mediaType »

 Il s'agit d'un nœud facultatif qui spécifie le type de média par défaut utilisé dans le « payload », c'est-à-dire dans le « body » de la requête envoyée au service web et dans le « body » de la réponse retournée par le service web.

Ce nœud évite de définir le « mediaType » dans toutes les définitions des ressources, ce qui doit être fait obligatoirement, si vous n'utilisez pas ce nœud, de la manière suivante :

Exemple mediaType
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
/conducteurs:
   get:
    responses:
        200:
            body:
                application/json:
                    type: Conducteur[]

Les valeurs les plus couramment utilisées sont « application/json » et « application/xml ». Une ou plusieurs valeurs peuvent être spécifiées dans ce nœud. Dans le cas où plusieurs valeurs sont spécifiées, elles doivent apparaître dans un tableau.

Exemple :

Exemple mediaType
Sélectionnez
1.
mediaType: [ application/json, application/xml ]

Dans l'exemple ci-dessus, on spécifie que les données peuvent être transmises au format « JSON » ou au format « XML ».

Il est néanmoins possible de définir un « mediaType » explicitement dans le « body » d'une requête ou d'une réponse liée à une ressource. Cette définition sera alors prioritaire sur la valeur définie dans le nœud « mediaType ».

Exemple :

Exemple mediaType
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
mediaType: [ application/json, application/xml ]
types:
  Vehicule: ...
  Conducteur: ...
/conducteurs:
  get:
    responses:
      200:
        body: Conducteur[]
/vehicules:
  post:
    body:
      application/json:
        type: Vehicule

Dans l'exemple ci-dessus, la ressource retourne un tableau d'instances de type « Conducteur » () représenté soit en « JSON », soit en « XML » car, étant donné que le « mediaType » n'est pas défini dans la ressource, le « mediaType » par défaut qui s'appliquera sera celui défini dans le nœud « mediaType ».

En revanche, la ressource utilisera la valeur de « mediaType » qui lui est explicitement définie. Par conséquent, la ressource retournera uniquement des données de type « Voiture » au format « JSON ».

IV-B-8. Le nœud « documentation »

Il s'agit d'un nœud facultatif permettant d'écrire ou de faire référence à de la documentation (notice utilisateur ou documentation de référence de l'APIApplication Programming Interface). Le but de ce nœud est de pouvoir se documenter sur le fonctionnement de l'APIApplication Programming Interface et/ou de fournir les contextes techniques et métiers dans lesquels le service web est intégré.

Ce nœud se compose d'une séquence d'un ou plusieurs documents représentés sous forme de « map » contenant deux colonnes (clé, valeur). La clé n'est autre que le titre de la documentation et la valeur est son contenu. Ce contenu peut être un texte brut ou un lien vers une documentation. Dans ce dernier cas, le lien est spécifié par l'utilisation de « !include ». Le contenu peut être formaté en utilisant le langage de balisage « markdown ».

Exemple :

Exemple documentation
Sélectionnez
1.
2.
3.
4.
5.
6.
documentation:
   - title: Accueil
     content: Bienvenue sur la documentation de l′API de gestion de parc automobile

   - title: Legal
     content: !include docs/legal.markdown

IV-B-9. Le nœud « types »

 Il s'agit d'un nœud facultatif qui définit les types des données utilisés dans l'APIApplication Programming Interface.

Les types de données peuvent être déclarés :

  • Dans le nœud optionnel « types » ;
    Il s'agit, dans ce cas, de prévoir la réutilisabilité du type dans différentes ressources. Ainsi, il n'y a pas besoin de définir le même type dans toutes les ressources qui l'utilisent. C'est la solution à adopter afin de factoriser le code et ainsi d'alléger la définition des ressources.
    Exemple :
Exemple typestypes:
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
types:
    Vehicule:
        type: object
          properties:
            marque:
                required: true
                type: string
            modele:
                required: true
                type: string
            categorie:
                required: true
                type: string
            places: number  #Par défaut required: false. Cette syntaxe est équivalente à "places?: number"
            prix:
                required: true
                type: number
            couleur:
                required: true
                enum:
                  - Blanc
                  - Noir
                  - Gris
                  - Bordeaux
                  - Jaune
                  - Bleu
        example:
            marque: RENAULT
            modele: Scenic
            categorie: Monospace
            prix: 31500
            couleur: Gris
            
    Conducteur:
        type: object
          properties:
            nom:
                required: true
                type: string
            prenom:
                required: true
                type: string
            date_naissance:
                required: true
                type: date-only
                example: 1981-05-04
        example:
            nom: Célère
            prenom: Jacques
            date_naissance: 1981-05-04t#y#p#e#s#:#

Dans l'exemple ci-dessus, nous définissons deux types (« Vehicule » et « Conducteur ») que nous pouvons, dès lors, utiliser dans les ressources « /vehicules » et « /conducteurs » de la manière suivante :

Reutilisabilite type
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
/vehicules:
    displayName: vehicules
    get:
        responses:
            200:
                body:
                    application/json:
                        type: Vehicule[]
        
    post:
        body: Vehicule
/conducteurs:
    get:
        responses:
            200:
                body:
                    application/json:
                        type: Conducteur[]
  • au sein de la définition des ressources. Il s'agit de la méthode la moins claire en termes de relecture ;
    Exemple :

    Exemple types2
    Sélectionnez
    1.
    2.
    3.
    4.
    5.
    6.
    7.
    8.
    9.
    10.
    11.
    12.
    13.
    14.
    15.
    16.
    17.
    18.
    19.
    20.
    21.
    22.
    23.
    24.
    25.
    26.
    27.
    28.
    /vehicules:
        get:
          responses:
            200:
              body:
                application/json:
        post:
          body:
            application/json:
              type: object
              properties:
                marque: string
                modele: string
                categorie : string
                prix: number
                couleur:
                      enum:
                        - Blanc
                        - Noir
                        - Coloré
              example: |
                {
                  "marque": "RENAULT",
                  "modele": "SCENIC",
                  "categorie": "Monospace",
                  "prix": 25000,
                  "couleur": "Noir"
                }
    

    Avec cet exemple, nous voyons dès lors qu'il est impossible de réutiliser le type à moins de le définir à nouveau dans une autre ressource. Par conséquent, la spécification deviendra vite illisible sans compter les oublis si on change le type dans une ressource et pas dans une autre…

  • au sein d'une bibliothèque qui sera incluse comme le présente le paragraphe « VIII Création d'une bibliothèque RAML ». L'avantage de l'utilisation d'une bibliothèque est de pouvoir réutiliser nos propres types, « ressourceType » (voir le paragraphe IV.B.11 Le nœud « resourceTypes ») et « traits » (voir le paragraphe IV.B.10 Le nœud « traits ») dans d'autres fichiers RAMLRESTful API Modeling Language (voir le paragraphe VIII Création d'une bibliothèque RAML) ;
    Exemple d'inclusion de bibliothèque :
Exemple types 3
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
title: Garage
uses:
  NewLibrary: NewLibrary.raml
version: v1
baseUri: /garage
types:
 Vehicule:
 type: NewLibrary.Vehicule

Nous verrons plus en détail la déclaration de type dans le paragraphe « VI Les types ».

IV-B-10. Le nœud « traits »

 Il s'agit d'un nœud facultatif permettant de réutiliser des fonctionnalités dans différentes méthodes (get, post, delete, put).

Ces fonctionnalités peuvent être, par exemple, une description ou des paramètres à fournir à une méthode. Les méthodes qui utilisent un ou plusieurs de ces « traits » héritent de toutes les caractéristiques de ces « traits », c'est-à-dire de l'ensemble des nœuds contenus dans ces « traits ».

P ar exemple, supposons que nous ayons une ressource décrite de la manière suivante :

Exemple traits
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
/vehicules:
      get:
        queryParameters:
            page:
                description: Numéro de page à récupérer
                type: number
                required: true
                example: 1
            par_page:
                description: Nombre d′instances récupérées par page
                type: integer
                minimum: 20
                maximum: 50
                default: 30
                example: 50
            prixPlusPetitQue?:
                description: "Prix plus petit que" #Le "?" signifie que le paramètre est optionnel
                type: number
            prixPlusGrandQue?:   #Le "?" signifie que le paramètre est optionnel
                description: Prix plus grand que
                type: number
            secured:
                usage: S′applique aux méthodes qui ont besoin d′être sécurisées
                description: Certaines requêtes requièrent une authentification
                headers:
                      access_token:
                          description: Access Token
                          example: 5757gh76
                          required: true
     responses:
          200:
         body:
            applications/json:
                 type: Vehicule[]

La méthode get sera donc une requête qui pourra exiger jusqu'à cinq paramètres nommés : « page », « par_page », « prixPlusPetitQue », « prixPlusGrandQue » et « access_token ».

Afin de simplifier la lecture du code et de permettre la réutilisabilité de ces paramètres dans d'autres méthodes (par exemple, on peut envisager que toutes les méthodes « get » de chaque ressource exigent les paramètres de pagination), nous pouvons créer un nœud « traits ».

Les ressources et les types de ressource (voir le paragraphe IV.B.11 Le nœud « resourceTypes » ci-dessous), peuvent aussi utiliser, et donc hériter, de « traits » qui s'appliquent alors à toutes les méthodes de la ressource et de la « resourceType ». Nous verrons également un exemple de cette utilisation de « traits » par les ressources et les « resourceTypes » dans la section IV.B.11 Le nœud « resourceTypes ».

Exemple de « traits » à partir de l'exemple précédent permettant « d'alléger » la méthode « get »  :

Exemple traits
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
traits:
    pagination:
       usage: S′applique uniquement aux méthodes get
       description: Permet de paginer les résultats
       queryParameters:
           page:
               description: Numéro de page à récupérer
               type: number
               required: true
               example: 1
           par_page:
               description: Nombre d′instances récupérées par page
               type: integer
               minimum: 20
               maximum: 50
               default: 30
               example: 50
    filtre_par_prix:
       usage: S′applique uniquement aux méthodes get
       description: Permet de filtrer les réponses en fonction du prix
       queryParameters:
            prixPlusPetitQue?:
              description: "Prix plus petit que" #Le "?" signifie que le paramètre est optionnel
              type: number
            prixPlusGrandQue?:   #Le "?" signifie que le paramètre est optionnel
              description: Prix plus grand que
              type: number
    secured:
       usage: S′applique aux méthodes qui ont besoin d′être sécurisées
       description: Certaines requêtes requièrent une authentification
       headers:
            access_token:
               description: Access Token
               example: 5757gh76
               required: true
  • Dans l'exemple ci-dessus, nous avons déplacé les paramètres définis dans la méthode « get » dans un nœud « traits ».
    Nous créons trois « traits » :

    • Le « trait » nommé « pagination » spécifie des paramètres de requête (mot clé « queryParameters ») permettant de préciser, lors de l'appel d'une méthode, le numéro de la page à récupérer ainsi que le nombre d'instances par page. Il accepte deux paramètres en entrée nommés respectivement « page » et « par_page » ;
    • Le « trait » nommé « Filtre_par_prix » spécifie des paramètres de requête permettant de filtrer les collections d'instances en fonction du prix. Il accepte deux paramètres en entrée nommés respectivement « prixPlusPetitQue » et « prixPlusGrandQue » ;
    • Le « trait » nommé « secured » spécifie un paramètre de sécurité dans les requêtes.
      L'utilisation de ces « traits » par une méthode est spécifiée par le mot clé « is » de la manière suivante (par exemple sur la ressource « /vehicules ») :
Exemple traits 2
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
/vehicules:
  is: [ secured ] #Les méthodes utilisées par la ressource « /vehicules » seront toutes sécurisées
  get:
     is: [ filtre_par_prix, pagination ]
     responses:
          200:
         body:
            applications/json :
                 type: Vehicule[]
   post:
           body:
             application/json:
               type: Vehicule

Dans le but de passer des valeurs de paramètre aux « traits » et aux « resourceTypes » (que nous verrons dans le paragraphe suivant) et ainsi permettre d'utiliser des « pattern », il est possible d'utiliser une map dans la déclaration du « trait » ou du « resourceType » comme illustré ci-dessous :

Exemple Parametres Traits
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
traits:
    pagination:
        usage: S′applique uniquement aux méthodes get
        description: Permet de paginer les résultats
        queryParameters:
            page:
                description: Numéro de page à récupérer
                type: number 
                required: true
                example:1
            par_page:
                description: Nombre d′instances récupérées par page
                type: integer
                minimum: 20
                maximum: 50
                default: 30
                example: 50
    filtre_par_prix:
        usage: S′applique uniquement aux méthodes get
        description: Permet de filtrer les réponses en fonction du prix
        queryParameters:
            prixPlusPetitQue?:   #Le "?" signifie que le paramètre est optionnel
                description: Prix plus petit que
                type: number
            prixPlusGrandQue?:   #Le "?" signifie que le paramètre est optionnel
                description: Prix plus grand que
                type: number
 secured:
 usage: S′applique aux méthodes qui ont besoin d′être sécurisées
 description: Certaines requêtes requièrent une authentification
        headers:
          <<tokenName>>:
            description: Exige un <<tokenName>> valide

/vehicules:
      is: [ secured: {tokenName: access_token} ] #Les méthodes utilisées par la ressource « /vehicules » seront toutes sécurisées
      displayName: vehicules
      get:
        responses:
           200:
               body:
                   application/json:
                        type: Vehicule[]
        is: [ filtre_par_prix, pagination ]
      post:
           body:
             application/json:
               type: Vehicule

En plus de pouvoir être définis dans des bibliothèques (voir le paragraphe VIII Création d'une bibliothèque RAML), les « traits » peuvent également être définis dans un fichier d'extension « .raml » qui est ensuite importé de la manière suivante dans notre APIApplication Programming Interface :

Exemple import traits
Sélectionnez
1.
2.
3.
4.
traits:
  secured: !include traits/secured.raml
  pagination: !include traits/pagination.raml
  filtre_par_prix: !include traits/filtre_par_prix.raml

IV-B-11. Le nœud « resourceTypes »

 Il s'agit d'un nœud facultatif permettant de réutiliser les méthodes (get, post, delete, put), et leurs caractéristiques (response, body, type…), dans plusieurs ressources.

Exemple :

Considérons la ressource « /vehicules » de l'exemple précédent :

Exemple ResourceType
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
/vehicules:
      is: [ secured: {tokenName: access_token} ] #Les méthodes utilisées par la ressource « /vehicules » seront toutes sécurisées
      displayName: vehicules
      get:
        responses:
           200:
               body:
                   application/json:
                        type: Vehicule[]
        is: [ filtre_par_prix, pagination ]
      post:
           body:
             application/json:
               type: Vehicule

Il apparaît que les méthodes « get » et « post » pourraient être réutilisées dans d'autres ressources pour peu que ces autres ressources retournent des objets de type « Vehicule ». Dès lors, nous pouvons déclarer ces méthodes sous forme de « resourceType » qui pourraient être appelés par d'autres ressources.

Ceci simplifiera la lecture de la spécification et surtout la réutilisabilité du code :

Exemple ResourceTypes
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
resourceTypes:
  Collection:
    get:
      responses:
        200:
          body:
            application/json:
              type: Vehicule[]
    post:
      body:
        application/json:
          type: Vehicule




/vehicules:
      is: [ secured ] #Les méthodes utilisées par la ressource « /vehicules » seront toutes sécurisées
      displayName: vehicules
      type: Collection
      get:
          is: [ filtre_par_prix, pagination ]

Le code ci-dessus permet de définir un type de ressource nommé « Collection » qui possède une méthode « get » ainsi qu'une méthode « post » retournant toutes les deux des objets de type « Vehicule » au format « JSON ».

Les méthodes « get » et « post » de la ressource « /vehicules » sont héritées de la « resourceType » nommée « Collection ».

Les méthodes « put » et « delete » peuvent être définies directement au sein de la ressource ou dans le nœud « resourceTypes » comme nous l'avons fait pour les méthodes « get » et « post ».

Il est possible de factoriser encore un peu plus le code afin qu'il soit réutilisable dans la majorité des ressources. Pour cela, comme pour les « traits », nous pouvons paramétrer les « resourceTypes » comme le présente l'exemple ci-dessous :

Exemple parametre resourceType
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
resourceTypes:
    Collection:
       get:
         responses:
            200:
                body:
                    application/json:
                         type: <<item>>[]
       post:
            body:
              application/json:
                type: <<item>>



/vehicules:
      is: [ secured ] #Les méthodes utilisées par la ressource « /vehicules » seront toutes sécurisées
      displayName: vehicules
      type:  { Collection: {item : Vehicule} }
      get:
          is: [ filtre_par_prix, pagination ]


/conducteurs:
       displayName: conducteurs
       type: { Collection: {item : Conducteur} }

       /{id}:
            put:
                description: Met à jour un conducteur dont l′identifiant est fourni dans l′uri
                body:
                      application/json:
                            type: Conducteur
            delete:
                responses:
                       204:

Dans l'exemple ci-dessus, la « resourceType » nommée « Collection » est utilisée à la fois dans la ressource « /vehicules » et dans la ressource « /conducteurs ». Pour cela, nous avons défini un paramètre nommé « item » dont on doit préciser le type dans la définition de la méthode.

Nous pouvons créer une seconde « resourceTypes » afin de considérer le cas des méthodes « put » et « delete » :

Exemple resourceTypes 3
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
resourceTypes:
    Collection:
       get:
         responses:
            200:
                body:
                    application/json:
                         type: <<item>>[]
       post:
            body:
              application/json:
                type: <<item>>
    Membre:
      put:
          body:
             application/json:
                   type: <<item>>
      delete:
          responses:
               204:


/conducteurs:
       displayName: conducteurs
       type: { Collection: {item : Conducteur} }

       get:
           description: Permet de récupérer un ensemble d′objets de type "Conducteur" 
       post:
           description: Permet d′enregistrer un nouveau conducteur

       /{id}: #On a affaire à une sous-ressource
            type:  { Membre: {item : Conducteur} }
            put:
                description: Met à jour un conducteur dont l′identifiant est fourni dans l′uri
            delete:
                description: Supprime un conducteur

Les « resourceTypes » peuvent être définies dans un fichier d'extension « .raml » qui est ensuite importé de la manière suivante :

Exemple import traits
Sélectionnez
1.
2.
resourceTypes:
  collection:  !include resourceTypes/collection.raml

IV-B-12. Le nœud « annotationTypes »

Il s'agit d'un nœud facultatif permettant de compléter les spécifications à l'aide de métadonnées. Ces annotations ne sont pas comprises par le processeur RAMLRESTful API Modeling Language. Néanmoins, elles peuvent être utilisées pour donner plus de détails dans les spécifications. Ces métadonnées pourront ensuite être utilisées pour améliorer la documentation, les tests ou l'exploitation.

Nous verrons, dans ce paragraphe, que les annotations peuvent être de deux types (standard ou personnalisé) et que chaque annotation peut concerner seulement certains types d'éléments de la spécification ou plusieurs.

IV-B-12-a. Les annotations « standards »

Il existe des types d'annotations « standards » comme « displayName » et « description » qui peuvent être utilisés dans n'importe quel nœud.

Ces types d'annotations sont utilisés de la manière suivante pour détailler l'utilité d'une ressource, d'une méthode… :

Exemple annotationType sur ressource
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
/vehicules:
  displayName: Gestion des vehicules
  type: Collection
  /{id}:
    put:
      description: Met à jour un véhicule dont l′identifiant est fourni en paramètre
      body:
        application/json:
          type: Vehicule
    delete:
      responses:
        204:

Dans l'exemple ci-dessus, l'annotation « displayName » permet de décrire l'utilité de la ressource alors que l'annotation « description », qui peut être écrite en utilisant le langage Markdown permet de décrire l'utilité de la méthode.

IV-B-12-b. Les annotations personnalisées

Il est également possible de créer d'autres annotations qui se composent de couples (clé, valeur) comme le présente l'exemple ci-dessous et pour lesquelles il est possible de préciser pour quel type d'entité elles peuvent être utilisées (ressource, méthode…) via l'intermédiaire de l'annotation « allowedTargets ». Cette dernière peut prendre une ou plusieurs valeurs parmi celles référencées sur la page https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md#annotation-target-location.

Ces annotations utilisent le nœud « annotationTypes » comme nous pouvons le voir ci-dessous :

Exemple :

Exemple annotationType
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
annotationTypes:
    deprecie: nil
    experimental: string?  #équivalent à « nil | string »
    auteur: string  #string est le type par défaut

    #Deux autres syntaxes possible sont :
    #auteur:
    #    type: string
    #ou
    #auteur:
    niveau_autorisation:
        allowedTargets: [ Resource ]
        properties:
             niveau:
                enum: [ faible, moyen, élevé ]
                required: true
             signature:
                pattern: "\\d{3}-\\w{12}"
                required: true
    meta-resource-method:
       allowedTargets: [ Resource, Method ]
    meta-data:
       allowedTargets: TypeDeclaration
types:
    Vehicule:
        (meta-data): Sur la déclaration du type "Vehicule"
        properties:
               marque:
                   type: string
                   required: true
                   (meta-data): Sur la déclaration de la "properties"
               modele:
                   type: string
                   required: true
               categorie:
                   type: string
                   required: true
                   enum:
                     - Citadine
                     - berline
                     - Monospace
                     - SUV
               places: integer  #Par défaut required: true.
               prix:
                   required: false
                   type: number
               couleur:
                   required: true
                   enum:
                        - Blanc
                        - Noir
                        - Gris
                        - Bordeaux
                        - Jaune
                        - Bleu
        example:
          marque: RENAULT
          modele: Scenic
          categorie: Monospace
          places: 7
          prix: 31500
          couleur: Gris

    Conducteur:
        properties:
              nom:
                   type: string
                   required: true
              prenom:
                   type: string
                   required: true
              date_naissance:
                   type: date-only
                   required: true
                   example: 1981-05-04
        example:
          nom: Célère
          prenom: Jacques
          date_naissance: 1981-05-04

/vehicules:
      is: [ secured ] #Les méthodes utilisées par la ressource « /vehicules » seront toutes sécurisées
      displayName: Gestion des vehicules
      (meta-resource-method): Sur une ressource
      type:  { Collection: {item : Vehicule} }
      get:
          description: Permet de récupérer un ensemble d′objets de type "Vehicule"
          is: [ filtre_par_prix, pagination ]
          (deprecie):
          (experimental):
      post:
          description: Permet d′enregistrer un nouveau véhicule


/conducteurs:
       displayName: Gestion des conducteurs
       (niveau_autorisation):
         niveau: élevé
         signature: 230-ghtwvfrs1itr
       type: { Collection: {item : Conducteur} }

       get:
           description: Permet de récupérer un ensemble d′objets de type "Conducteur" 
           (deprecie):
           (experimental):
       post:
           description: Permet d′enregistrer un nouveau conducteur

       /{id}: #On a affaire à une sous-ressource
            type:  { Membre: {item : Conducteur} }
            put:
                description: Met à jour un conducteur dont l′identifiant est fourni dans l′uri
            delete:
                description: Supprime un conducteur

Dans l'exemple ci-dessus, nous avons créé six annotations (« deprecie », « experimental », « auteur », « niveau_autorisation », « meta-resource-method », « meta-data ») afin de montrer la syntaxe des définitions des annotations et comment spécifier qu'une annotation peut être appliquée sur un nœud ou un autre.

Nous voyons par exemple que :

  • l'annotation « deprecie », de par son type « nil », n'autorise pas la saisie d'une valeur ;
  • l'annotation « experimental », de par son type « string ? » équivalent à « nil | string », peut ne pas être renseignée ou peut contenir une chaîne de caractères ;
  • l'annotation « auteur » est forcément de type « string » si elle apparaît ;
  • l'annotation « niveau_autorisation » est composée de deux propriétés que sont « niveau » et « signature ». À cause de la présence de l'attribut « allowedTargets » ayant pour valeur un tableau contenant la valeur « Resource », ces deux propriétés ne pourront être utilisées que dans les nœuds ressource.
    La propriété « niveau » ne peut prendre que les valeurs « faible », « moyen » ou « élevé » alors que la propriété « signature » ne peut prendre comme valeur qu'une chaîne de caractères dont la composition est définie par l'expression régulière « \\d{3}-\\w{12} ». Cette expression régulière spécifie une chaîne de caractères commençant par trois chiffres suivis du caractère « - » suivis de douze caractères alphanumériques. Les expressions régulières sont basées sur les expressions régulières de Perl dont un tutoriel est disponible ici : https://www.cs.tut.fi/~jkorpela/perl/regexp.html
  • l'annotation « meta-resource-method » montre comment rendre possible l'utilisation d'annotation uniquement dans les ressources et les méthodes ;
  • L'annotation « meta-data », en utilisant la valeur « TypeDeclaration » dans le nœud « allowedTarget », permet de déclarer qu'une annotation ne peut être utilisée que dans :

    • les déclarations de type de données ;
    • les déclarations de header ;
    • les nœuds « queryParameters » ;
    • les nœuds « baseUriParameters » ;
    • une propriété placée dans n'importe lequel des nœuds cités ci-dessus et dans lequel la propriété « type » peut être utilisée.

La liste des valeurs pouvant être prises par le nœud « allowedTarget » est décrite ici : https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#annotation-target-location

IV-B-12-c. Annotation de nœud scalaire

Il est également possible d'annoter les nœuds dits « scalaires ». Il s'agit des nœuds qui n'exigent qu'une valeur et n'ont pas de propriété supplémentaire (par exemple le nœud « baseUri »). La liste des nœuds scalaires est la suivante :

  • displayName
  • description
  • type
  • schema
  • default
  • example
  • usage
  • required
  • content
  • strict
  • minLength
  • maxLength
  • uniqueItems
  • minItems
  • maxItems
  • discriminator
  • minProperties
  • maxProperties
  • discriminatorValue
  • pattern
  • format
  • minimum
  • maximum
  • multipleOf
  • requestTokenUri
  • authorizationUri
  • tokenCredentialsUri
  • accessTokenUri
  • title
  • version
  • baseUri
  • mediaType
  • extends

Afin d'annoter un nœud scalaire, il faut déjà savoir que cette notation est équivalente à celle-ci :

 
Sélectionnez
1.
2.
baseUri:
  value: http://www.example.com/api

Dès lors, nous pouvons annoter un nœud scalaire de la manière suivante :

 
Sélectionnez
1.
2.
3.
baseUri:
  value: http://www.example.com/api
  (redirigeable): true

IV-B-13. Le nœud « uses »

Il s'agit d'un nœud facultatif qui permet d'importer des bibliothèques externes. La création de telles bibliothèques sera expliquée dans le paragraphe VIII Création d'une bibliothèque RAML.

Exemple :

Exemple nœud « uses »
Sélectionnez
1.
2.
uses:
  NewLibrary: NewLibrary.raml

Dans l'exemple ci-dessus, « NewLibrary.raml » est une bibliothèque externe qui contient des « traits » (voir le paragraphe IV.B.10 Le nœud « traits »), des « resourceTypes » (voir le paragraphe IV.B.11 Le nœud « resourceTypes ») et des « types » (voir le paragraphe IV.B.9 Le nœud « types »).

Le nœud « uses » permet donc à RAMLRESTful API Modeling Language de réutiliser des spécifications existantes.

V. Création de ressource et de ressource imbriquée

Nous avons étudié, dans la section précédente, la liste des nœuds, obligatoires et facultatifs, qui constituent la racine d'une spécification RAMLRESTful API Modeling Language. Nous avons volontairement omis de décrire les nœuds « ressources » même s'ils ont été évoqués dans les exemples qui précèdent (il s'agissait des nœuds « /véhicules », « /conducteurs » et « /conducteurs/{id} »).

En effet, les nœuds « ressources » méritent un paragraphe complet et nous nous appliquons à les étudier ci-dessous.

V-A. Formation de l'URI

Comme nous l'avons vu, les nœuds « ressources » sont identifiés par une URIUniform Resource Identifier relative qui commence par un « / ». Il s'agit soit d'un nœud qui se situe à la racine de la définition de l'APIApplication Programming Interface (par exemple : « /vehicules »), soit d'un enfant d'un nœud ressource (par exemple : « /conducteurs/{id} »). Dans ce dernier cas, on parle de « ressource imbriquée ».

Les ressources imbriquées peuvent elles-mêmes avoir d'autres ressources dites, elles aussi, imbriquées.

  1. Il est préférable que le nom des ressources soit au pluriel afin de permettre au développeur de deviner au premier coup d'œil qu'en manipulant ces ressources, il manipule des collections d'objets.
  2. Une collection de ressources sera donc adressée de cette manière :
    « /v1/vehicules » qui permet, par exemple dans le cas de l'appel à la méthode « get », de déduire qu'on récupérera un ensemble de ressources de type « vehicule », ou, dans le cas de l'appel à la méthode « post », de déduire qu'on crée une instance dans la collection de véhicules.
    L'instance d'une ressource sera, quant à elle, adressée de la manière suivante : « /v1/vehicules/2 » qui permet de déduire qu'on recherche le véhicule d'identifiant « 2 » dans la collection de véhicules.
  3. Dans le cas où le nom d'une ressource est composé de plusieurs mots, il est préférable de séparer ces mots par un tiret (« - ») ou un underscore (« _ »).
    Par exemple : « /v1/vehicules-legers » ou « /v1/vehicules_legers ».
  4. En ce qui concerne le nommage du « body » de la requête, il semble que l'underscore (« _ ») ou la notation consistant à mettre la première lettre de l'attribut en minuscule et la première lettre de chaque autre mot en majuscule soient devenues conventionnelles.
    Par exemple : « /vehicules?id_vehicule=2 » ou « /vehicules?idVehicule=2 »
    Il en est de même pour les balises JSON et XML qui constituent le « body ».

Exemple :

Exemple ressource
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
#%RAML 1.0
title: API de gestion d′un parc automobile
description: API permettant de gérer les opérations CRUD ( create, read, update, delete) relatives la gestion d′un parc de véhicules
version: v1
baseUri: https://{ville}.garage.com/{release}
baseUriParameters:
    ville:
         description: Sous-domaine auquel l′API est accessible.
         enum: [ paris, dijon]
    release:
         description: Version de l′API
/vehicules:
  
/conducteurs:
      /{conducteurId}: #On a affaire à une ressource imbriquée
         uriParameters: 
           conducteurId:
             type: integer
         /permis:
           /{idPermis}:
             uriParameters: 
               type: integer

Les URIUniform Resource Identifier absolues pour les ressources ci-dessus sont les suivantes (selon l'ordre de leur déclaration) :

  • https://{ville}.garage.com/{release}/vehicules
  • https://{ville}.garage.com/{release}/conducteurs
  • https://{ville}.garage.com/{release}/conducteurs/{conducteurId}
  • https://{ville}.garage.com/{release}/conducteurs/{conducteurId}/permis
  • https://{ville}.garage.com/{release}/conducteurs/{conducteurId}/permis/{idPermis}

Les URIUniform Resource Identifier ne doivent pas être identiques à une autre. Les paramètres ne sont toutefois pas évalués par le moteur RAMLRESTful API Modeling Language. En conséquence, les URIUniform Resource Identifier ci-dessous sont permises :

Exemple URI
Sélectionnez
1.
2.
3.
/conducteurs/{conducteurId}:
/conducteurs/{conducteurName}:
/conducteurs/moi:

Lors de la spécification de services web RESTRepresentational State Transfer, il est nécessaire de penser aux performances du serveur lors d'un très grand nombre d'appels.

Ainsi, même si on peut naturellement penser créer une ressource par URL, il est important de garder à l'esprit que ceci peut surcharger inutilement le serveur.

Par exemple, si on a trois ressources (« véhicules », « conducteurs », « permis » et qu'on souhaite récupérer le permis de conduire d'un conducteur qui conduit un véhicule donné, il est nécessaire d'effectuer trois appels :

  • https://dijon.garage.com/v1/vehicules/2

puis

  • https://dijon.garage.com/v1/conducteurs/3

puis

  • https://dijon.garage.com/v1/permis/5

Toutes ces informations sont pourtant souvent utilisées ensemble. Nous générons donc trois appels alors qu'un seul peut suffire.

En revanche, il ne faut pas non plus que les réponses contiennent trop de données inutiles, car les échanges seraient surchargés inutilement.

Il est donc nécessaire de regrouper, dans une même URL, les ressources ayant des dépendances entre elles et auxquelles l'utilisation de l'une entraînera l'utilisation des autres quasi systématiquement.

Pour reprendre l'exemple que nous venons d'évoquer, une URL de la forme « /v1/vehicules/2/conducteurs/permis » contenant deux niveaux d'imbrication au maximum permet de limiter le volume de données retourné tout en récupérant les données susceptibles de nous intéresser le plus.

Au-delà de deux niveaux d'imbrication, le flux d'informations retournées risquerait de contenir des informations obsolètes qui ne subiront aucun traitement et qui risqueraient de surcharger le flux inutilement.

V-B. Les propriétés des ressources

Nous avons entrevu, dans la section précédente, la propriété « baseUriParameters » qui permet de fournir des détails concernant le paramètre de l'URIUniform Resource Identifier comme son type par exemple.

Il existe d'autres propriétés que nous avons déjà utilisées précédemment pour définir une ressource telles :

  • displayName (optionnelle) qui permet d'afficher le nom de la ressource ;
  • description (optionnelle) qui permet de décrire précisément l'utilité de la ressource ;
  • is (optionnelle) qui permet de faire appel à un/des « traits » ;
  • type (optionnelle) qui permet de déclarer le type de la ressource ou de la ressource imbriquée ;
  • les méthodes HTTP de RESTRepresentational State Transfer (« get », « post », « delete » et « put » qui permettent respectivement de récupérer, insérer, supprimer et mettre à jour).
    Il existe également d'autres méthodes HTTP pour RESTRepresentational State Transfer qui sont les suivantes :

    • patch
      Le corps d'une requête « PATCH » contient uniquement les instructions permettant de mettre à jour une instance de classe (contrairement au corps d'une requête « PUT » qui contient une représentation complète de la ressource à modifier). Le corps de la requête « PATCH » est donc d'un type différent de la représentation de la ressource à modifier. Par conséquent, contrairement à la requête « PUT », la requête « PATCH » ne peut pas créer de nouvelle ressource ;
      Exemple : la modification de l'adresse mail de l'utilisateur « 123 » en appliquant la méthode « PATCH » sur sa représentation JSON ressemble à ceci :

      Exemple PATCH
      Sélectionnez
      PATCH /users/123
      
      [
          { "op": "replace", "path": "/email", "value": "new.email@example.org" }
      ]
  • Options
    Cette méthode permet de récupérer les opérations qui peuvent être appelées sur une ressource particulière.
    Par exemple, si une ressource est en lecture seule, alors envoyer une requête « OPTIONS » sur cette ressource retourne un header de la réponse nommé « Allow » avec les valeurs « GET » et « HEAD ». « GET » et « HEAD » sont donc les deux seules opérations qu'on peut appliquer sur la ressource ;
    Exemple :

    Exemple OPTION
    Sélectionnez
    1.
    2.
    3.
    4.
    5.
    OPTIONS www.garage.com/aboutme
    
    Response
    Headers
    Allow : HEAD, GET
    
  • Head
    Cette méthode permet de récupérer des informations contenues dans le « header » de la réponse. C'est une méthode similaire à la méthode « get » sauf qu'elle retourne des informations concernant les métadonnées d'une ressource. La méthode « head » peut donc être utilisée pour savoir si une ressource existe sur le serveur ou pour récupérer des informations sur cette ressource.

V-C. Les propriétés des méthodes

Comme nous l'avons vu dans la section précédente, les méthodes pouvant être utilisées dans les ressources sont les méthodes « get », « post », « put », « delete », « patch », « options » et « head ».

Ces méthodes possèdent des propriétés que nous avons déjà énumérées dans les exemples ci-dessus telles :

  • displayName (optionnelle) qui permet d'afficher le nom de la méthode ;
  • description qui permet de décrire précisément l'utilité de la méthode ;
  • queryParameters (optionnelle) qui permet de spécifier les filtres à fournir à une méthode pour effectuer une requête (Voir le paragraphe IV.B.10 Le nœud « traits ») ;
  • responses (optionnelle) qui permet de décrire les réponses HTTP attendues lors de l'appel à la méthode.
    En fonction de la valeur de retour, on peut spécifier qu'on attend une structure spécifique dans le « body » et dans le « header » (voir l'explication du nœud « headers » ci-dessous) ;
    Par exemple :

    Exemple response
    Sélectionnez
    1.
    2.
    3.
    4.
    5.
    6.
    7.
    8.
    9.
    10.
    11.
    12.
    13.
    14.
    15.
    16.
    17.
    18.
    19.
    20.
    21.
    22.
    23.
    24.
    25.
    /vehicules:
      get:
        responses:
          200:
            body:
              type: Vehicule[]
      post:
        body:
          type: Vehicule
        responses:
          201:
            headers:
              Location:
                example: /vehicules/45612
            body:
              application/json:
                type: !include schemas/vehicule.json
              text/xml:
                type: !include schemas/vehicule.xsd
          422:
            body:
              properties:
                error:
              example:
                error: Impossible de traiter les instructions : données erronées
    
  • body (optionnelle) qui permet de décrire le contenu du body de la requête. Il peut s'agir, dans le cas d'une méthode « put » ou d'une méthode « post » de préciser, dans le « body », la structure de la ressource à créer ou à modifier tout en précisant le format dans lequel les données seront transmises (par exemple : « application/json ») ;

  • protocols (optionnelle) qui permet de spécifier le protocole utilisé par la méthode (« HTTP » ou « HTTPS »). La valeur de cette propriété n'est pas sensible à la casse ;

  • is (optionnelle) qui permet, à la méthode, de faire appel à un/des « traits » ;

  • (<annotationName>) (optionnelle) qui permet d'utiliser une annotation (dont le nom est toujours entre parenthèses).

À cette liste, nous pouvons ajouter les propriétés :

  • headers qui permet de spécifier la structure du « header » de la requête envoyée par la méthode. La valeur du nœud « headers » est composée d'un ensemble de déclarations de propriétés. Chaque nom de propriété définit un nom de « header » autorisé. Par défaut, la valeur de la propriété est de type « string ».
    Si une déclaration de « header » spécifie un type « array » pour la valeur du « header », alors plusieurs instances de ce « header » devront être autorisées par le processeur dans la requête ou la réponse. En revanche, si un autre type que « array » est spécifié alors le processeur ne doit pas autoriser plus d'une instance de ce « header » dans la requête ou la réponse.
    Exemple (repris des spécifications RAMLRESTful API Modeling Language) :

    Exemple header
    Sélectionnez
    1.
    2.
    3.
    4.
    5.
    6.
    7.
    8.
    9.
    10.
    11.
    12.
    13.
    14.
    15.
    16.
    17.
    18.
    19.
    20.
    21.
    22.
    23.
    24.
    25.
    26.
    27.
    28.
    29.
    30.
    31.
    32.
    33.
    34.
    #%RAML 1.0
    title: Example with headers
    traits:
      chargeable:
        headers:
          X-Dept:
            type: array
            description: |
              Un département peut être chargé.
              Plusieurs de ces headers sont autorisés.
            items:
              pattern: ^\d+\-\w+$
              example: 230-OCTO
      traceable:
        headers:
          X-Tracker:
            description: Code pour tracer les appels à l′API de bout en bout
            pattern: ^\w{16}$
            example: abcdefghijklmnop
    /users:
      get:
        is: [ chargeable, traceable ]
        description: |
          Les interactions HTTP ressembleront à ceci :
    
          GET /users HTTP/1.1
          X-Dept: 18-FINANCE
          X-Dept: 200-MISC
          X-Tracker: gfr456d03ygh38s2
        headers:
          X-Dept:
            example: [ 18-FINANCE, 200-MISC ]
          X-Tracker:
            example: gfr456d03ygh38s2
    
  • securedBy qui permet de spécifier les schémas de sécurité. Nous traiterons de la sécurité dans le paragraphe VII. La gestion de la sécurité.
 

VI. Les types

VI-A. Les types par défaut

VI-A-1. Le type « object »

Il est possible de déclarer un type de donnée de la manière suivante :

Exemple type "string"
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
types:
    Conducteur:
        properties:
              nom:
                   type: string
                   minLength: 2
                   maxLength: 50
                   required: true
              prenom:
                   type: string
                   minLength: 2
                   maxLength: 50
                   required: true

Dans cet exemple, on ne sait pas de quel type est le type « Conducteur ». Néanmoins, du fait qu'il contient des propriétés (introduites par le mot clé « properties »), sachez qu'il est de type « object » par défaut et que la déclaration ci-dessus est équivalente à celle-ci :

Exemple type "string"
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
types:
    Conducteur:
        type: object
        properties:
              nom:
                   type: string
                   minLength: 2
                   maxLength: 50
                   required: true
              prenom:
                   type: string
                   minLength: 2
                   maxLength: 50
                   required: true

Dans l'exemple ci-dessus, on a ajouté la ligne : type: object.

VI-A-2. Le type « string »

Si la déclaration d'un type ne contient pas de « properties » ou de « type », alors le type par défaut est le type « string ».

Par exemple, la propriété « name » ci-dessous ne contient pas de nœud « properties », ni même de nœud « type » donc « name » est de type « string » :

Exemple type string par défaut
Sélectionnez
1.
2.
3.
4.
types:
  Person:
    properties:
      name: #Possibilité de ne pas renseigner le type

VI-A-3. Le type « any »

Le type « any » est appliqué aux nœuds « body » qui ne contiennent pas de nœud « properties » ou « type ».

Exemple :

Exemple de type "any"
Sélectionnez
1.
2.
3.
body:
  application/json:
    # Le type par défaut est `any`

Si un type de média par défaut (voir le paragraphe IV.B.7 Le nœud « mediaType ») n'a pas été défini, alors la définition du body peut se résumer à ceci :

Exemple de type "any" 2
Sélectionnez
1.
2.
body:
      # Le type par défaut est `any`

Dans ce dernier cas, le body peut contenir n'importe quelle structure.

VI-B. Les types scalaires

Il s'agit des types natifs de RAMLRESTful API Modeling Language. Nous les décrirons dans les sections ci-dessous.

VI-B-1. Le type « string »

Il s'agit du type « chaîne de caractères ». Les propriétés qui lui sont attachées sont les suivantes :

  • pattern
    Il s'agit d'un nœud facultatif qui reçoit, pour valeur, une expression régulière. Les expressions régulières sont basées sur les expressions régulières de Perl dont un tutoriel est disponible ici : https://www.cs.tut.fi/~jkorpela/perl/regexp.html ;
  • minLength
    Il s'agit d'un nœud facultatif, dont la valeur par défaut est 0, et qui a pour valeur la longueur minimum de la chaîne de caractères ;
  • MaxLength
    Il s'agit d'un nœud facultatif, dont la valeur par défaut est 2147483647, et qui a pour valeur la longueur maximum de la chaîne de caractères.

Exemple :

Exemple type "string"
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
types:
    Conducteur:
        properties:
              nom:
                   type: string
                   minLength: 2
                   maxLength: 50
                   required: true
              prenom:
                   type: string
                   minLength: 2
                   maxLength: 50
                   required: true

VI-B-2. Le type « number »

Il s'agit d'un type qui regroupe tous les types numériques de JSON y compris le type « integer ».

Les propriétés du type « number » sont les suivantes :

  • minimum
    Il s'agit d'un nœud facultatif qui précise la valeur minimum que peut prendre la variable ;
  • maximum
    Il s'agit d'un nœud facultatif qui précise la valeur maximum que peut prendre la variable ;
  • format
    Il s'agit d'un nœud facultatif qui fournit le format de la valeur. Ce nœud prend l'une des valeurs suivantes :

    • int32 ;
    • int64 ;
    • int ;
    • long ;
    • float ;
    • double.
  • multipleOf
    Ce nœud facultatif permet de préciser que la valeur qui sera prise par l'instance devra obligatoirement être un multiple de la valeur renseignée pour la propriété multipleOf. Le résultat de la division de la valeur de l'instance par la valeur de la propriété multipleOf doit être un entier.

Exemple :

Exemple type "number"
Sélectionnez
1.
2.
3.
4.
5.
6.
types:
  ChevalFiscal:
    type: number
    minimum: 2
    maximum: 9
    format: int

VI-B-3. Le type « integer »

Il s'agit d'un sous-ensemble du type « number » qui peut être positif ou négatif. Il hérite des propriétés du type « number ».

Exemple :

Exemple type "integer"
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
types:
  Annee:
    type: integer
    minimum: 1950
    maximum: 2016
    format: int8
    multipleOf: 1

VI-B-4. Le type « boolean »

Il s'agit du type booléen comme nous le trouvons dans de nombreux langages qui prend, bien sûr, pour seules valeurs « true » ou « false ».

Exemple :

Exemple type "boolean"
Sélectionnez
1.
2.
3.
types:
  ToitOuvrant:
    type: boolean

VI-B-5. Le type « date »

Le type « date » se décline en plusieurs types selon les informations plus ou moins détaillées qu'on souhaite obtenir :

  • date-only
    Permet de stocker une date au format aaaa-mm-jj ;

    Exemple type "date-only"
    Sélectionnez
    1.
    2.
    3.
    4.
    date_naissance:
          required: true
          type: date-only
          example: 1981-05-04
    
  • time-only
    Permet de stocker une heure au format hh:mm:ss ;

    Exemple type "time-only"
    Sélectionnez
    1.
    2.
    3.
    heure_depart :
         type: time-only
         example: 14:00:26
    
  • datetime-only
    Il s'agit d'un mélange entre le format « date-only » et le format « time-only ». Ce format est stocké sous la forme aaaa-mm-jjThh:mm:ss. Le « T » sépare la date de l'heure ;

    Example type "datetime-only"
    Sélectionnez
    1.
    2.
    3.
    heure_depart:
         type: datetime-only
         example: 2016-05-15T14:00:26
    
  • datetime
    Concernant ce type, le format de la date dépendra de la valeur du nœud « format ». Si le nœud « format » est omis ou si sa valeur est « rfc3339 », le format RFC3339 est appliqué.
    Si le nœud « format » est spécifié alors le format défini par la norme RFC2616 est utilisé.
Exemple type datetime
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
heure_depart:
    type: datetime
    example: 2016-02-28T16:41:41.090Z
    format: rfc3339 # Par défaut donc pas besoin de spécifier
heure_arrivee:
    type: datetime
    example: Sun, 28 Feb 2016 16:41:41 GMT
    format: rfc2616 # Requis sinon, l'exemple n'est pas valide

VI-B-6. Le type « file »

Ce type permet de contraindre le contenu des informations envoyées depuis un formulaire.

Lorsque ce type est utilisé dans le contexte d'un formulaire web, il devrait être représenté par un fichier « uploadé » valide au format JSON. Le contenu de ce fichier devrait être une chaîne de caractères encodée en base 64.

Le type « file » contient les propriétés suivantes :

  • fileTypes
    Il s'agit d'une propriété facultative qui prend pour valeur une liste de « content type » (type de contenu) valides pour le fichier. Le type de fichier */* doit être une valeur valide. Une liste de « content type » est fournie à cette adresse : http://www.freeformatter.com/mime-types-list.html ;
  • minLength
    Il s'agit d'un nœud facultatif qui, par défaut, prend la valeur 0, et qui permet de spécifier la taille minimum en octets du fichier transmis. Cette valeur doit être égale ou supérieure à 0.
  • maxLength
    Il s'agit d'un nœud facultatif qui, par défaut, prend la valeur 2147483647, et qui permet de spécifier la taille maximum en octets du fichier transmis. Cette valeur doit être égale ou supérieure à 0.

VI-B-7. Le type « array »

Il s'agit du type « tableau ». Il existe deux manières équivalentes de définir un tableau :

  • par utilisation des crochets « [] » à la fin du type ;
    Exemple :

    Déclaration d'un type tableau avec crochet
    Sélectionnez
    1.
    2.
    3.
    4.
    5.
    6.
    7.
    8.
    9.
    10.
    11.
    12.
    13.
    14.
    15.
    16.
    17.
    18.
    19.
    20.
    21.
    22.
    23.
    24.
    25.
    26.
    27.
    28.
    29.
    30.
    31.
    32.
    33.
    34.
    35.
    36.
    37.
    38.
    39.
    40.
    41.
    42.
    43.
    44.
    45.
    46.
    47.
    48.
    49.
    50.
    51.
    types:
      Vehicule:
        properties:
          marque:
             type: string
             required: true
             (meta-data): Sur la déclaration de la "properties"
    
          modele:
             type: string
             required: true
          categorie:
             type: string
             required: true
             enum:
                  - Citadine
                  - Berline
                  - Monospace
                  - SUV
          places: integer  #Par défaut required: true.
          prix:
             required: false
             type: number
          couleur:
             required: true
             enum:
                  - Blanc
                  - Noir
                  - Gris
                  - Rouge
                  - Jaune
                  - Bleu
      Vehicules:
         type: Vehicule[]
         minItems: 1
         uniqueItems: false
         example: # example that contains array
                - # item 1
                 marque: RENAULT
                 modele: Scenic
                 categorie: Monospace
                 places: 7
                 prix: 31500
                 couleur: Gris
                - # item 2
                 marque: RENAULT
                 modele: Megane
                 categorie: Berline
                 places: 5
                 prix: 19500
                 couleur: Rouge
    

    Dans l'exemple ci-dessus, on voit que le type « Vehicules » est un tableau d'instances du type « vehicule ».

  • par utilisation du mot array en tant que valeur du nœud « type ».
    Exemple :
Déclaration d'un type tableau avec "array"
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
types:
  Vehicule:
    properties:
      marque:
         type: string
         required: true
         (meta-data): Sur la déclaration de la "properties"

      modele:
         type: string
         required: true
      categorie:
         type: string
         required: true
         enum:
              - Citadine
              - Berline
              - Monospace
              - SUV
      places: integer  #Par défaut required: true.
      prix:
         required: false
         type: number
      couleur:
         required: true
         enum:
              - Blanc
              - Noir
              - Gris
              - Rouge
              - Jaune
              - Bleu
  Vehicules:
     type: array
     items: Vehicule
     minItems: 1
     uniqueItems: false
     example: # exemple qui contient un tableau
            - # item 1
             marque: RENAULT
             modele: Scenic
             categorie: Monospace
             places: 7
             prix: 31500
             couleur: Gris
            - # item 2
             marque: RENAULT
             modele: Megane
             categorie: Berline
             places: 5
             prix: 19500
             couleur: Rouge

Comme on peut le voir dans l'exemple ci-dessus, les propriétés des tableaux sont les suivantes :

  • uniqueItems
    Il s'agit d'un nœud facultatif qui indique, si sa valeur est à « true », que l'objet dans le tableau doit être unique ;
  • items
    Il s'agit d'un nœud facultatif qui indique de quel type sont tous les objets qui sont stockés dans le tableau ;
  • minItems
    Il s'agit d'un nœud facultatif qui indique le nombre minimum d'objets qui sont dans le tableau ;
  • maxItems
    Il s'agit d'un nœud facultatif qui indique le nombre maximum d'objets qui sont dans le tableau.

VI-B-8. Le type « Nil »

C'est un type qui permet de dire qu'aucune valeur n'est autorisée pour le champ déclaré de ce type.

Le type « nil » utilisé dans des headers, des paramètres d'URIUniform Resource Identifier, ou des paramètres de requête permet uniquement d'utiliser la chaîne de caractères « nil » (sensible à la casse). Inversement, une instance ayant pour valeur la chaîne de caractères « nil » (sensible à la casse), lorsqu'elle est de type « nil », voit sa valeur mise à néant.

Exemples (repris des spécifications RAMLRESTful API Modeling Language ) :

Exemple type "nil"
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
types:
  NilValue:
    type: object
    properties:
      name:
      comment:
    example:
      name: Fred
      comment: #Ne pas fournir de valeur ici n'est pas permis
Exemple type "nil" 2
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
types:
  NilValue:
    type: object
    properties:
      name:
      comment: nil
    example:
      name: Fred
      comment: #Fournir une valeur ici n'est pas permis
Exemple type "nil" 3
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
types:
  NilValue:
    type: object
    properties:
      name:
      comment: nil | string # equivalent à « comment: string? »
    example:
      name: Fred
      comment: # Fournir, ou pas, une valeur ici est permise

VI-B-9. Le type « union »

Dans le dernier exemple de la section précédente, le caractère « | » est un caractère d'union en RAMLRESTful API Modeling Language. Il est utilisé pour permettre à une instance de donnée de permettre l'affectation de valeurs de type différent. Dans l'exemple précédent, la propriété « comment » peut ne pas avoir de valeur ou peut prendre une valeur sous forme de chaîne de caractères.

L'union de type peut être utilisée de manière plus complexe comme le présente l'exemple ci-dessous (repris des spécifications RAMLRESTful API Modeling Language) :

Exemple type "union"
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
types:
   HasHome:
     type: object
     properties:
       homeAddress: string
   Cat:
     type: object
     properties:
       name: string
       color: string
   Dog:
     type: object
     properties:
       name: string
       fangs: string       
   HomeAnimal: [ HasHome ,  Dog | Cat ]

Dans l'exemple ci-dessus, une instance de type « HomeAnimal » hérite des caractéristiques du type « HasHome » et des caractéristiques d'un des types « Dog » ou « Cat ».

De manière encore plus complexe, il est possible d'écrire une déclaration de ce type :

Exemple type "union" 2
Sélectionnez
1.
2.
types:
   HomeAnimal: [ HasHome | IsOnFarm ,  Dog | Cat | Parrot ]

Cette déclaration spécifie que les instances du type « HomeAnimal » peuvent hériter des types :

  • [HasHome, Dog] ;
  • [HasHome, Cat] ;
  • [HasHome, Parrot] ;
  • [IsOnFarm, Dog] ;
  • [IsOnFarm, Cat] ;
  • [IsOnFarm, Parrot].

À travers le type « union », c'est l'héritage multiple qui apparaît.

VI-B-10. Utilisation des schémas XML et des schémas JSON

Il est possible, avec RAMLRESTful API Modeling Language, d'utiliser des schémas XML et des schémas JSON pour décrire un type ou le corps d'une requête ou d'une réponse en intégrant les schémas via la propriété type. L'appel de ces schémas, situés dans des fichiers externes, s'effectue par utilisation du mot « !include »

Exemples :

Exemple import schéma JSON
Sélectionnez
1.
2.
types:
  Person: !include voiture.json
Exemple import schéma JSON 2
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
/voitures:
  get:
    responses:
      200:
        body:
          application/json:
            type: !include voiture.json

Sur les types définis par un schéma JSON ou XML, il n'est pas possible d'ajouter de propriétés supplémentaires. Par exemple, cette définition est invalide :

Exemple invalide d'import schéma JSON
Sélectionnez
1.
2.
3.
4.
5.
types:
  Voiture:
    type: !include voiture.json
    properties: # invalide
      electrique: boolean

En revanche, on peut ajouter des annotations (y compris « displayName » et « description ») et des exemples :

Exemple ajout description avec import de schéma JSON
Sélectionnez
1.
2.
3.
4.
types:
  Voiture:
    type: !include voiture.json
    description: Il s′agit d′un schéma décrivant une voiture

Il n'est pas non plus possible d'utiliser les types définis à partir de schéma JSON ou XML dans les propriétés des types.

Contre-exemple :

Exemple invalide d'utilisation de type utilisant un schéma JSON
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
types:
  Voiture:
    type: !include voiture.json
    description: Il s′agit d′un schéma décrivant une voiture
  Parking:
    properties:
      parc: Voiture[] # Utilisation invalide à cause des crochets « [] » et de l'utilisation du type Voiture dans les propriétés d'un type.

VI-C. La gestion du polymorphisme

Lorsque le corps d'un message contient des instances dont le type est ambigu à cause du type « union » ou de l'héritage, il est compliqué de différencier le type des instances au cours de l'exécution et ainsi d'affecter l'instance à une variable de type approprié.

Le processeur RAMLRESTful API Modeling Language peut sélectionner automatiquement un type spécifique parmi un ensemble de types possibles. Pour cela, il est nécessaire d'utiliser un attribut, nommé « discriminator » qui permet, par la valeur qu'il stocke, d'identifier le type de l'objet auquel cet attribut appartient.

L'attribut « discriminator », qui appartient au type parent, permet de renseigner le nom de la propriété, héritée du type parent, qui permet de faire la différence entre les différents types enfants.

C'est l'attribut « discriminatorValue » qui permet de stocker la valeur que prendra la propriété sur laquelle s'applique l'attribut « discriminator ». Par défaut, la valeur de « discriminatorValue » est le nom du type enfant.

Exemple :

Exemple discriminator
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
types:
  Vehicule:
    type: object
    discriminator: categorie # se réfère à la propriété "categorie" de l'objet "Vehicule"
    properties:
      categorie: string # contient le nom du genre de l'instance `Vehicule`
      nom: string
  Poids_Lourd: # categorie peut être égale à "Poids_Lourd". Il s'agit de la valeur par défaut pour l'attribut "discriminatorValue"
    type: Vehicule
    properties:
      plId: integer
  Vehicule_Leger: # categorie peut être égale à "Vehicule_Leger". Il s'agit de la valeur par défaut pour l'attribut "discriminatorValue"
    type: Vehicule
    properties:
      vlId: integer

Les données liées à l'exemple ci-dessus peuvent être similaires à celles-ci :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
data:
  - nom: Scenic
    vlId: 1
    categorie: Vehicule_Leger
  - nom: Actros
    plId: 2
    categorie: Poids_Lourd

Dans l'exemple ci-dessus, on peut voir que, sans préciser la valeur de l'attribut « discriminatorValue », c'est le nom du type enfant qui est utilisé pour renseigner la propriété « categorie ».

Vous pouvez également décider de modifier la valeur par défaut de l'attribut « discriminatorValue ». On précise alors cette valeur dans chacun des types enfant comme le présente l'exemple ci-dessous :

Exemple discriminator 2
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
types:
  Vehicule:
    type: object
    discriminator: categorie # se réfère à la propriété "categorie" de l'objet "Vehicule"
    properties:
      categorie: string 
      nom: string
  Poids_Lourd: # categorie peut être égale à "Poids_lourds". Il s'agit de la valeur par défaut pour l'attribut "discriminatorValue"
    type: Vehicule
    discriminatorValue: PL
    properties:
      plId: integer
  Vehicule_Leger: # categorie peut être égale à "Poids_lourds". Il s'agit de la valeur par défaut pour l'attribut "discriminatorValue"
    type: Vehicule
    discriminatorValue: VL
    properties:
      vlId: integer

Les données liées à l'exemple ci-dessus peuvent être similaires à celles-ci :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
data:
  - nom: Scenic
    vlId: 1
    categorie: VL
  - nom: Actros
    plId: 2
    categorie: PL

Les attributs « discriminator » et « discriminatorValue » sont valables uniquement dans le nœud « types ». Il est interdit de les utiliser dans les déclarations de type en ligne ou dans les types « union ».

Exemple :

Discriminator invalide
Sélectionnez
1.
2.
3.
4.
5.
# invalide dans une déclaration de type en ligne
application/json:
   discriminator: categorie
   properties:
     categorie: string
Discrimantor invalide 2
Sélectionnez
1.
2.
3.
4.
# Invalide pour le type « union »
VehiculeLegerOuLourd:
   type: Vehicule_Leger | Poids_Lourd
   discriminator: aUneBenne

VII. La gestion de la sécurité

 En plus des nœuds de base que nous avons étudiés plus haut, RAMLRESTful API Modeling Language permet d'inclure des mécanismes de sécurisation d'accès aux données, d'identification de requêtes, de détermination de niveau d'accès et de visibilité de données.

VII-A. Les schémas de sécurité supportés par RAML

Pour gérer les mécanismes de sécurisation, RAMLRESTful API Modeling Language supporte les types de schémas de sécurité ci-dessous :

  • OAuth 1.0 et OAuth 2.0
    La raison d'être du protocole OAuth est de s'identifier à partir du profil d'une autre application comme le proposent certains sites en nous permettant de nous identifier à partir de notre compte Twitter ou Facebook.
    L'intérêt majeur d'OAuth vient du fait que l'utilisateur n'a plus besoin de fournir ses informations d'identification à une application tierce. En contrepartie, le site sur lequel on se connecte peut tout faire avec le compte facebook ou twitter. Pour pallier cela, Oauth met en place un mécanisme de délégation d'autorisation entre les différentes entités.
    Cette méthode d'authentification, basée, pour plus de sécurité, sur l'utilisation de HTTPS, utilise un accès par jeton.
    Il s'agit d'un identifiant utilisé par un client pour accéder à une ressource. Tant que le jeton est valide, le client peut accéder à la ressource. Le jeton peut être révoqué par le serveur d'autorisation à partir du moment où il est expiré ou à partir du moment où le client a choisi de le révoquer.
    Le scénario général est le suivant :

    • Le client demande au détenteur de la ressource (par exemple facebook ou twitter), l'autorisation de demander un jeton.
    • En cas d'autorisation, le détenteur de la ressource envoie son consentement au serveur d'autorisation qui finit par envoyer le jeton au client. Le serveur d'autorisation aura préalablement déterminé la durée de vie de ce jeton.
    • Le jeton d'accès sera ensuite envoyé par le client, dans le header ou en paramètres, à chacune de ses requêtes vers le serveur de ressources.

      Pour plus d'informations sur le protocole OAuth, voir les sites : http://blog.netapsys.fr/oauth-comment-ca-marche/ et http://www.bubblecode.net/fr/2016/01/22/comprendre-oauth2/.

      Ces deux sites présentent plusieurs types d'autorisations possibles au sein du protocole OAuth.

  • Les protocoles OAuth 1.0 et OAuth 2.0 sont généralement utilisés pour sécuriser les APIApplication Programming Interface RESTRepresentational State Transfer.
    Les caractéristiques majeures qu'apporte OAuth 2.0 par rapport à OAuth 1.0 sont les suivantes :

    • Gestion de l'authentification et de l'habilitation des ressources par tout type d'application (native mobile, native tablette, application JavaScript, application web de type serveur, application batch/back-office) avec ou sans consentement du propriétaire des ressources.
    • OAuth 2.0 est devenu le standard de sécurisation des APIApplication Programming Interface RESTRepresentational State Transfer.

  • Basic Authentication
    Lors de l'envoi d'une requête au serveur, le client s'identifie à l'aide d'un nom d'utilisateur et d'un mot de passe. Le serveur répond ensuite en envoyant les données requises ou en envoyant un message d'erreur. Les données ne sont pas cryptées et sont seulement encodées en base 64.
    Pour être sécurisée, l'authentification basique doit être utilisée sur une couche « transport » implémentant le protocole SSL (Secure Sockets Layer).

  • Digest Authentication
    Cette méthode d'authentification est plus sécurisée que l'authentification basique, car elle ne requiert pas le protocole SSL, ce qui rend les échanges plus rapides. Les identifiants de connexion sont envoyés encryptés au serveur.

    • Le client envoie une requête au serveur.
    • Le serveur y répond en envoyant :

      • un code appelé « nonce » généré par le serveur à chaque réponse 401 (demande d'authentification) ;
      • une chaîne de caractères appelée « realm » qui contient au moins le nom de la machine et le nom du groupe d'utilisateur requis afin que l'utilisateur sache avec quel identifiant et quel mot de passe il doit s'identifier.
    • Le client applique alors une fonction de hashage sur ce « nonce », mais également sur l'identifiant et le mot de passe encryptés ainsi que sur le « realm » et renvoie l'ensemble au serveur.
    • Le serveur répond avec l'information demandée par le client si la valeur de hashage retournée par le client correspond à la valeur calculée par le serveur.
      Avec ce schéma de sécurité, plusieurs options de sécurité sont optionnelles. Si le paramètre « QOP » (Quality of Protection) n'est pas renseigné par le serveur, le client échangera dans un mode de sécurité réduit.
      De plus, cette authentification est vulnérable si un pirate informatique arrive à se placer au milieu des conversations entre le client et le serveur et demande au client d'appliquer le mode de sécurité réduit. L'authentification « digest » ne fournit aux clients aucun mécanisme permettant la vérification de l'identité du serveur.

  • Pass through
    L'authentification « Pass through » se déroule de la manière suivante :

    • Le client envoie une requête « HTTP », « HTTPS », « FTP » ou « Telnet » au serveur.
    • Le serveur intercepte la demande et voit que la demande envoyée nécessite une authentification depuis une base de données locale ou depuis un autre serveur d'authentification.
    • Le serveur fait afficher une demande d'identifiant et de mot de passe sur l'écran du client.
    • L'utilisateur répond en envoyant son nom d'utilisateur et son mot de passe.
    • Soit le serveur vérifie la cohérence des informations retournées dans sa base de données, soit il envoie ces informations de connexion au serveur d'authentification externe.
    • Si les informations retournées par l'utilisateur sont cohérentes, le serveur informe le client que son identification est correcte.

  • x-{other}
    L'authentification de l'APIApplication Programming Interface est reliée à une autre méthode d'authentification.

VII-B. Le nœud « securitySchemes »

Les mécanismes de sécurité énumérés dans la section précédente sont spécifiés par l'intermédiaire du nœud securitySchemes.

Voyons d'abord un exemple de définition de schéma de sécurité extrait des spécifications de RAMLRESTful API Modeling Language (https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#security-schemes ) :

Exemple schéma de sécurité OAuth
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
#%RAML 1.0
title: Dropbox API
version: 1
baseUri: https://api.dropbox.com/{version}
securitySchemes:
  oauth_2_0:
    description: |
      Dropbox supporte OAuth 2.0 pour authentifier toutes les requêtes de l′API
    type: OAuth 2.0
    describedBy:
      headers:
        Authorization:
          description: |
             Utilisé pour envoyer un jeton d′accès OAuth 2.0 valide. À ne pas utiliser avec le paramètre "access_token"
          type: string
      queryParameters:
        access_token:
          description: |
             Utilisé pour envoyer un jeton d′accès OAuth 2.0 valide. À ne pas utiliser avec le header "Authorization".
          type: string
      responses:
        401:
          description: |
              Jeton corrompu ou expiré. Ceci survient si l′utilisateur ou dropbox révoque ou expire un jeton d′accès. Pour solutionner ce problème, ré-authentifier l′utilisateur. 
        403:
          description: |
              Mauvaise requête OAuth (mauvaise clé, mauvais nonce ou temps écoulé). La réauthentification de l′utilisateur n′aidera en rien.
    settings:
      authorizationUri: https://www.dropbox.com/1/oauth2/authorize
      accessTokenUri: https://api.dropbox.com/1/oauth2/token
      authorizationGrants: [ authorization_code, implicit ]
  oauth_1_0:
    description: |
      OAuth 1.0 continue d′être supporté par toutes les requêtes de l′API mais OAuth 2.0 est préféré.
    type: OAuth 1.0
    settings:
      requestTokenUri: https://api.dropbox.com/1/oauth/request_token
      authorizationUri: https://www.dropbox.com/1/oauth/authorize
      tokenCredentialsUri: https://api.dropbox.com/1/oauth/access_token

Nous voyons que le nœud « securitySchemes » comporte plusieurs nœuds fils qui sont les suivants :

  • Le nœud « type » ;
  • Le nœud « displayName » ;
  • Le nœud « description » ;
  • Le nœud « describedBy » ;
  • Le nœud settings.

Nous expliquerons, dans les sections ci-dessous, le rôle de chacun de ces nœuds.

VII-B-1. Le nœud « type »

Le nœud « type » est obligatoire et permet de spécifier quel(s) type(s) de schéma de sécurité peut/peuvent être utilisé(s) par l'APIApplication Programming Interface.

Les valeurs que peut prendre ce nœud sont :

  • OAuth 1.0 ;
  • Oauth 2.0 ;
  • Basic Authentication ;
  • Digest Authentication ;
  • Pass Through ;
  • x-<other>.

VII-B-2. Le nœud « displayName »

Le nœud « displayName » est facultatif. Il n'est d'ailleurs pas utilisé dans l'exemple ci-dessus. Il permet de nommer le schéma de sécurité en langage humain.

VII-B-3. Le nœud « description »

Le nœud « description » est facultatif et permet de décrire le schéma de sécurité choisi. La valeur prise par ce nœud peut être formatée en langage markdown.

VII-B-4. Le nœud « describedBy »

Le nœud « describedBy » est facultatif. Il permet de décrire les composants des requêtes (headers, paramètres, réponses) déterminés par le schéma de sécurité. Il est de bon aloi, même pour les schémas de sécurité standards, de renseigner chacune des propriétés de ce nœud. Les propriétés du nœud « describedBy » sont les suivantes :

VII-B-4-a. Le nœud « headers »

Le nœud « headers » est optionnel.

Il permet de spécifier la structure du « header » de la requête envoyée par la méthode. La valeur du nœud « headers » est composée d'un ensemble de déclarations de propriétés. Chaque nom de propriété définit un nom de « header » autorisé. Par défaut, la valeur de la propriété est de type « string ».

Si une déclaration de « header » spécifie un type « array » pour la valeur du header, alors plusieurs instances de ce « header » devront être autorisées par le processeur dans la requête ou la réponse. En revanche, si un autre type que « array » est spécifié, alors le processeur ne doit pas autoriser plus d'une instance de ce « header » dans la requête ou la réponse.

VII-B-4-b. Le nœud « queryParameters »

Le nœud « queryParameters » est optionnel. Il permet de spécifier les paramètres utilisés par le schéma pour autoriser la requête.

VII-B-4-c. Le nœud « responses »

Le nœud « responses » est optionnel. Il permet de décrire les réponses HTTP attendues lors de l'appel à la méthode. Ce nœud est composé de nœuds facultatifs « description », « headers » et « body » que nous avons déjà décrits dans les paragraphes précédents.

VII-B-5. Le nœud « settings »

Le nœud « settings » est un nœud facultatif qui fournit des informations spécifiques au type de schéma de sécurité.

VII-B-5-a. Exemple pour le schéma OAuth 1.0

Le contenu du nœud « settings » est le suivant pour le schéma OAuth 1.0 :

Exemple de noeud settings pour OAuth 1.0
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
#%RAML 1.0
title: My Sample API
securitySchemes:
  oauth_1_0:
    description: |
      OAuth 1.0 continue d′être supporté pour toutes les requêtes d′API mais OAuth 2.0 est désormais préféré.
    type: OAuth 1.0
    settings:
      requestTokenUri: https://api.mysampleapi.com/1/oauth/request_token
      authorizationUri: https://api.mysampleapi.com/1/oauth/authorize
      tokenCredentialsUri: https://api.mysampleapi.com/1/oauth/access_token
      signatures: [ 'HMAC-SHA1', 'PLAINTEXT' ]

Le nœud requestTokenUri contient l'URIUniform Resource Identifier du serveur auquel on demande le jeton.

Le nœud authorizationUri contient l'URIUniform Resource Identifier du serveur qui délivre l'autorisation de jeton.

Le nœud tokenCredentialsUri contient l'URIUniform Resource Identifier du serveur de ressource.

Le nœud signaturescontient une liste de signatures de méthodes utilisée par le serveur d'autorisation. Cette liste peut contenir les valeurs « HMAC-SHA1 », « RSA-SHA1 » ou « PLAINTEXT ». Si ce nœud est absent alors le serveur d'autorisation permettra l'utilisation des signatures de méthodes définies dans la spécification RFC5849 Section3.4.

VII-B-5-b. Exemple pour le schéma OAuth 2.0

Le contenu du nœud « settings » est le suivant pour le schéma OAuth 2.0 :

Exemple de noeud settings pour OAuth 2.0
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
#%RAML 1.0
title: Dropbox API
version: 1
baseUri: https://api.dropbox.com/{version}
securitySchemes:
  oauth_2_0:
    description: |
      Dropbox supporte OAuth 2.0 pour authentifier toutes les requêtes de l′API
    type: OAuth 2.0
    describedBy:
      headers:
        Authorization:
          description: |
             Utilisé pour envoyer un jeton d′accès OAuth 2.0 valide. À ne pas utiliser avec le paramètre "access_token"
          type: string
      queryParameters:
        access_token:
          description: |
             Utilisé pour envoyer un jeton d′accès OAuth 2.0 valide. À ne pas utiliser avec le header "Authorization".
          type: string
      responses:
        401:
          description: |
              Jeton corrompu ou expiré. Ceci survient si l′utilisateur ou dropbox révoque ou expire un jeton d′accès. Pour solutionner ce problème, réauthentifier l′utilisateur. 
        403:
          description: |
              Mauvaise requête OAuth (mauvaise clé, mauvais nonce ou temps écoulé). La réauthentification de l′utilisateur n′aidera en rien.
    settings:
      authorizationUri: https://www.dropbox.com/1/oauth2/authorize
      accessTokenUri: https://api.dropbox.com/1/oauth2/token
      authorizationGrants: [ authorization_code, implicit ]

Le nœud contient l'URIUniform Resource Identifier du serveur qui délivre l'autorisation de jeton. Ce nœud est obligatoire seulement si on utilise l'autorisation via un code ou l'autorisation implicite (Voir le site : http://blog.netapsys.fr/oauth-comment-ca-marche/).

Le nœud contient l'URIUniform Resource Identifier du serveur qui fournit le jeton sur présentation de l'autorisation. Cette URIUniform Resource Identifier n'est pas utilisée pour les autorisations implicites puisqu'un jeton d'accès est délivré directement.

Le nœud contient une liste des types d'autorisations supportées par l'APIApplication Programming Interface. Il peut contenir les valeurs suivantes « authorization_code », « password », « client_credentials » ou encore « implicit ».

Un autre nœud est susceptible de figurer pour définir le schéma OAuth 2.0. Il s'agit du nœud scopes. Il spécifie précisément à quelles ressources l'utilisateur a le droit d'accéder.

VII-B-5-c. Exemple pour le schéma Basic Authentication

L'authentification basique ne requiert aucune information supplémentaire dans la définition de l'APIApplication Programming Interface :

Exemple de noeud settings pour Basic Authentification
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
#%RAML 1.0
title: Dropbox API
version: 1
baseUri: https://api.dropbox.com/{version}
securitySchemes:
  basic:
    description: |
      Cette API supporte l′authentification basique
    type: Basic Authentication

VII-B-5-d. Exemple pour le schéma Digest Authentication

L'authentification « digest » ne requiert aucune information supplémentaire dans la définition de l'APIApplication Programming Interface :

Exemple de noeud settings pour Digest Authentication
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
#%RAML 1.0
title: Dropbox API
version: 1
baseUri: https://api.dropbox.com/{version}
securitySchemes:
  digest:
    description: |
      Cette API supporte l′authentification « digest »
    type: Digest Authentication

VII-B-5-e. Exemple pour le schéma Pass Through

L'authentification « Pass Through » ne comporte pas de paramètre spécifique et son implémentation est connue de RAMLRESTful API Modeling Language. Vous devez fournir une valeur pour chaque « header » ou « queryParameter » défini dans le nœud « describedBy » et passé dans la requête :

Exemple de noeud settings pour Pass Through
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
#%RAML 1.0
title: Dropbox API
version: 1
baseUri: https://api.dropbox.com/{version}
securitySchemes:
  passthrough:
    description: |
      Cette API supporte l′authentification « Pass Through » 
    type: Pass Through
    describedBy:
      queryParameters:
        query:
          type: string
      headers:
        api_key:
          type: string

VII-B-5-f. Exemple pour le schéma x-<other>

Les méthodes d'authentification « x-<other> » ne comportent pas de paramètre spécifique, car leur implémentation est méconnue en tant qu'implémentation standard de RAMLRESTful API Modeling Language.

Ces schémas de sécurité peuvent inclure uniquement les nœuds « description » et « describedBy » pour permettre de documenter l'utilisation attendue du schéma de sécurité :

Exemple de need settings pour x-<other>
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
#%RAML 1.0
title: Custom API
version: 1
baseUri: https://api.custom.com/{version}
securitySchemes:
  custom_scheme:
    description: |
      Schéma de sécurité personnalisé pour authentifier des requêtes.
    type: x-custom
    describedBy:
      headers:
        SpecialToken:
          description: |
            Utilisé pour envoyer un jeton personnalisé.
          type: string
      responses:
        401:
          description: |
            Mauvais jeton.
        403:

VII-C. Le nœud « securedBy »

Il s'agit d'un nœud permettant d'appliquer les schémas de sécurité définis dans le nœud « securitySchemes » à toutes les méthodes de l'APIApplication Programming Interface sauf à celles qui ont leur propre nœud « securedBy ».

Le schéma de sécurité spécifié dans une méthode est prioritaire par rapport au schéma de sécurité défini pour toute l'APIApplication Programming Interface ou pour une ressource dont dépend la méthode. Pour indiquer qu'une méthode est protégée en utilisant un schéma de sécurité spécifique, cette méthode doit obligatoirement être définie en utilisant le nœud « securedBy » ;

Exemple :

exemple SecuredBy
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
#%RAML 1.0
title: Dropbox API
version: 1
baseUri: https://api.dropbox.com/{version}
securedBy: [oauth_2_0]
securitySchemes:
  oauth_2_0: !include securitySchemes/oauth_2_0.raml
  oauth_1_0: !include securitySchemes/oauth_1_0.raml
/users:
  get:
    securedBy: [oauth_2_0, oauth_1_0]

La valeur assignée au nœud « securedBy » doit être une liste de schémas de sécurité précédemment définis dans le nœud « securitySchemes » que nous avons expliqué plus haut.

Un nœud « securedBy » contenant « null » dans sa liste de schéma de sécurité (par exemple ) indique que la méthode peut être appelée sans appliquer de schéma de sécurité.

Comme nous l'avons évoqué plus haut, le nœud « securedBy » peut aussi appliquer une liste de schémas de sécurité à une ressource. Dans ce cas, toutes les méthodes à l'exception de celles utilisant leur propre nœud « securedBy » peuvent être authentifiées par l'un des schémas de sécurité spécifiés. La valeur du nœud « securedBy » dans la ressource passe outre la valeur du nœud « securedBy » au niveau de la racine. Les schémas de sécurité ne s'appliquent pas aux ressources imbriquées.

VIII. Création d'une bibliothèque RAML

 Comme nous l'avons vu dans de précédents exemples, il est possible de faire appel à des bibliothèques RAMLRESTful API Modeling Language par l'utilisation du nœud « uses ».

Les bibliothèques sont utilisées pour combiner des ensembles de déclarations :

  • de types de données ;
  • de type de ressources ;
  • des déclarations de « traits » ;
  • des déclarations de schémas de sécurité.

dans un fichier qui sera réutilisable par différentes APIApplication Programming Interface RAMLRESTful API Modeling Language.

Exemple :

Exemple de bibliothèque RAML
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
#%RAML 1.0 Library
resourceTypes:
  Collection:
    get:
      responses:
        200:
          body:
            application/json:
              type: Vehicule[]
    post:
      body:
        application/json:
          type: Vehicule
traits:
  pagination:
    usage: S′applique uniquement aux méthodes get
    description: Permet de paginer les résultats
    queryParameters:
      page:
        description: Numéro de page à récupérer
        type: number
        required: true
        example: 1
      par_page:
        description: Nombre d′instances récupérées par page
        type: integer
        minimum: 20
        maximum: 50
        default: 30
        example: 50
  filtre_par_prix:
    usage: S′applique uniquement aux méthodes get
    description: Permet de filtrer les réponses en fonction du prix
    queryParameters:
      prixPlusPetitQue?:
        description: Prix plus petit que
        type: number
      prixPlusGrandQue?:
        description: Prix plus grand que
        type: number
  secured:
    usage: S′applique aux méthodes qui ont besoin d′être sécurisées
    description: Certaines requêtes requièrent une authentification
    headers:
      access_token:
        description: Access Token
        example: 5757gh76
        required: true
types:
  Vehicule:
    (meta-data): Sur la déclaration du type "Vehicule"
    properties:
      marque:
        type: string
        required: true
        (meta-data): Sur la déclaration de la "properties"
      modele:
        type: string
        required: true
      categorie:
        type: string
        required: true
        enum:
          - Citadine
          - berline
          - Monospace
          - SUV
      places: integer
      prix:
        required: false
        type: number
      couleur:
        required: true
        enum:
          - Blanc
          - Noir
          - Gris
          - Bordeaux
          - Jaune
          - Bleu
    example:
      marque: RENAULT
      modele: Scenic
      categorie: Monospace
      places: 7
      prix: 31500
      couleur: Gris
  Conducteur:
    properties:
      nom:
        type: string
        required: true
      prenom:
        type: string
        required: true
      date_naissance:
        type: date-only
        required: true
        example: 1981-05-04
    example:
      nom: Célère
      prenom: Jacques
      date_naissance: 1981-05-04

L'utilisation de cette bibliothèque se résume à l'utilisation du nœud « uses » :

Exemple utilisation bibliothèque
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
#%RAML 1.0
title: API de gestion d′un parc automobile
uses:
  NewLibrary: NewLibrary.raml
description: API permettant de gérer les opérations CRUD (create, read, update, delete) relatives la gestion d′un parc de véhicules
version: v1
baseUri: https://{ville}.garage.com/{release}
baseUriParameters:
    ville:
         description: Sous-domaine auquel l′API est accessible.
         enum: [ paris, dijon]
    release:
         description: Version de l′API
protocols: [ HTTP, HTTPS ]
mediaType: [ application/json, application/xml ]
documentation:
   - title: Accueil
     content: Bienvenue sur la documentation de l′API de gestion de parc automobile

   - title: Legal
     content: !include docs/legal.markdown

annotationTypes:
    deprecie: nil
    experimental: string?  #équivalent à « nil | string »
    auteur: string  #string est le type par défaut

    #Deux autres syntaxes possibles sont :
    #auteur:
    #    type: string
    #ou
    #auteur:
    niveau_autorisation:
        allowedTargets: [ Resource ]
        properties:
             niveau:
                enum: [ faible, moyen, élevé ]
                required: true
             signature:
                pattern: "\\d{3}-\\w{12}"
                required: true
    meta-resource-method:
       allowedTargets: [ Resource, Method ]
    meta-data:
       allowedTargets: TypeDeclaration

traits:
types:
/vehicules:
      is: [ NewLibrary.secured ] #Les méthodes utilisées par la ressource « /vehicules » seront toutes sécurisées
      displayName: vehicules
      (meta-resource-method): Sur une ressource
      type: NewLibrary.Collection
      get:
          is: [ NewLibrary.filtre_par_prix, NewLibrary.pagination ]

/conducteurs:
       displayName: conducteurs
       (niveau_autorisation):
         niveau: élevé
         signature: 230-ghtwvfrs1itr
       get:
           (deprecie):
           (experimental):
           responses:
                 200:
                     body:
                         application/json:
                                type: NewLibrary.Conducteur[]
       /{id}:
            put:
                description: Met à jour un conducteur dont l′identifiant est fourni dans l′uri
                body:
                      application/json:
                            type: NewLibrary.Conducteur
            delete:
                responses:
                       204:

Dans l'exemple ci-dessus, remarquez que, dans le but d'utiliser les « types », « resourceTypes », et autres « traits » définis dans la bibliothèque, il est nécessaire de préfixer le nom de ces entités par le nom de la bibliothèque. Par exemple, pour utiliser le type « Conducteur » défini dans la bibliothèque, il est nécessaire d'écrire : « NewLibrary.Conducteur ».

IX. Utilisation d'un IDE (Integrated Development Environment)

Nous savons désormais comment écrire des spécifications RAMLRESTful API Modeling Language. Sachez maintenant que des outils tels « API Workbench » ou « API Designer » peuvent vous aider à la rédaction de telles spécifications en proposant notamment des fonctionnalités de création de projet RAMLRESTful API Modeling Language, de navigation, d'autocomplétion et surtout de validation.

Attardons-nous sur l'IDEIntegrated Development Environment « API Workbench » non pas parce que « API Designer » est moins bien, mais parce que « API Workbench » est le plus répandu.

Cet IDEIntegrated Development Environment supporte les versions « 0.8 » et « 1.0 » de RAMLRESTful API Modeling Language. Il permet non seulement de concevoir et rédiger les spécifications, mais aussi de fournir une documentation claire et de partager des APIApplication Programming Interface RESTRepresentational State Transfer à l'aide de « GIT ».

IX-A. Installation de « API Workbench »

« API Workbench » s'intègre parfaitement dans l'éditeur de texte « ATOM ». La version « Windows » de « ATOM » est téléchargeable à cette adresse : https://atom.io/.

  1. Une fois « ATOM » installé, ouvrez une invite de commande « Windows » puis déplacez-vous dans le répertoire « C:\Users\votre_profil\AppData\Local\atom\bin » comme le présente la capture d'écran ci-dessous :

    Image non disponible
  2. Si votre ordinateur se situe derrière un proxy, saisissez la commande suivante :
    apm config set proxy http://proxy.truc.fr:numero_port
    où la chaîne de caractères « http://proxy.truc.fr » devra être remplacée par l'adresse du proxy et « numero_port » devra être remplacée par le numéro du port permettant d'accéder au proxy.
    La commande « apm » (pour « Atom Package Manager ») a été installée lors de l'installation d'Atom.

  3. Saisissez ensuite la commande :
    apm install api-workbench

  4. À ce stade, lorsque vous ouvrez « ATOM », dans le menu « Packages », vous devriez avoir le sous-menu « API Workbench » comme le présente l'image ci-dessous :

    Image non disponible
  5. Ceci confirme que l'IDEIntegrated Development Environment « API Workbench » a bien été installé.

IX-B. Utilisation de « API Workbench »

Étant donné qu'un exemple vaut mieux qu'un long discours, vous trouverez, sur le site officiel de « API Workbench », à l'adresse http://apiworkbench.com/docs/, un exemple d'utilisation de l'outil, à suivre pas-à-pas, et qui traite de la création de la plupart des nœuds que nous avons étudiés en première partie de ce tutoriel.

Vous verrez notamment, à travers ces exemples, que « API Workbench » vous permet au maximum d'éviter d'écrire du code et vous épargne ainsi toute erreur de syntaxe.

X. Documentation des spécifications RAML

Maintenant que nous savons rédiger des spécifications RAMLRESTful API Modeling Language, à l'aide de l'IDEIntegrated Development Environment « API Workbench », il est désormais important de considérer un outil qui permet de générer la documentation de notre APIApplication Programming Interface.

L'objectif de cette documentation, pour les utilisateurs de notre APIApplication Programming Interface, est de visualiser, de manière rapide et claire, les spécifications que nous avons écrites.

L'IDEIntegrated Development Environment « API Workbench » permet déjà de visualiser rapidement la documentation d'une APIApplication Programming Interface RESTRepresentational State Transfer. Pour cela, il suffit, dans le menu « Packages » de « ATOM », de sélectionner le sous-menu « API Workbench » puis « Open API Console » comme le présente la capture d'écran ci-dessous :

Image non disponible

Dès lors, la vue ci-dessous apparaîtra :

Image non disponible

Néanmoins, il existe des outils plus puissants qui permettent de documenter une APIApplication Programming Interface, mais aussi de tester son implémentation à partir des spécifications. C'est ce que nous allons voir avec « API Console ».

Il existe plusieurs outils de génération de documentation à partir de spécifications RAMLRESTful API Modeling Language dont les plus connus sont les suivants :

RAML2THML ne semble pas encore supporter RAMLRESTful API Modeling Language 1.0. De plus, son développement semble s'être arrêté (momentanément ?) à la version 0.8 de RAMLRESTful API Modeling Language.

Pour cette raison, nous nous limiterons, ici, à utiliser l'outil « API Console ».

En outre, « API Console » permet à l'utilisateur de l'APIApplication Programming Interface (le développeur) d'interagir avec l'APIApplication Programming Interface depuis l'intérieur de la documentation. En effet, c'est un outil qui construit lui-même les formulaires à partir des paramètres à saisir lus dans les spécifications. Il valide les données saisies en fonction des définitions qu'il trouve dans l'APIApplication Programming Interface. Il permet également de tester l'authentification.

« API Console » permet de générer une documentation au format HTML à partir des spécifications RAMLRESTful API Modeling Language passées en paramètre. En raison du format HTML utilisé en sortie, il est possible de se déplacer facilement dans la documentation et de lire de manière claire les caractéristiques de l'APIApplication Programming Interface RESTRepresentational State Transfer.

X-A. Utilisation rapide de « API Console »

La procédure permettant de générer la documentation est la suivante :

  1. Téléchargez « API Console » à l'adresse https://github.com/mulesoft/api-console en cliquant sur le bouton vert nommé « Clone or download ».

    Image non disponible
  2. Il est possible d'utiliser directement l'outil en dézippant l'archive téléchargée puis en ouvrant le fichier « index.html » situé dans le répertoire « dist ». La fenêtre suivante apparaît :

    Image non disponible
  3. Dès lors, vous avez deux choix :

    1. soit vous renseignez le champ « INITIALIZE FROM THE URL OF A RAML FILE » à partir d'une URL du type « http://…/fichier.raml » ;
    2. soit vous renseignez le champ « OR PARSE RAML IN HERE ».

      Sachez toutefois que le premier choix ne fonctionnera pas si « API Console » n'est pas installé sur un serveur d'application. Nous verrons l'installation de « API Console » sous Tomcat plus loin dans ce paragraphe.

  4. Nous choisissons de renseigner la section « OR PARSE RAML IN HERE » en faisant un « copier-coller » du contenu de notre fichier RAML comme le présente la capture d'écran ci-dessous :

    Image non disponible

    L'utilisation de la fonctionnalité « OR PARSE RAML IN HERE » ne permet pas d'inclure des bibliothèques ou des schémas JSON ou XML.

  5. Cliquez ensuite sur le bouton « Load RAML ». Vous obtiendrez une documentation du type de celle présentée ci-dessous avec une jolie mise en page où toutes les zones sont cliquables :
Image non disponible
Image non disponible
Image non disponible

Sur cette dernière capture d'écran, on remarque le bouton « POST ». En effet, comme nous l'avons vu en introduction de ce paragraphe, « API Console » fournit des formulaires permettant de tester les différentes fonctionnalités de l'implémentation de notre APIApplication Programming Interface à partir de la documentation générée.

Ainsi, via cette capture d'écran, on peut voir que « API Console » a généré un formulaire permettant la saisie de véhicules. Les tests de notre service web RESTRepresentational State Transfer sont donc facilement faisables via « API Console ».

X-B. Utilisation de « API Console » via un serveur d'application

Nous avons vu, dans le paragraphe ci-dessus, qu'il est possible d'utiliser « API Console » de manière rapide en dézippant l'archive et en ouvrant le fichier « index.html ».

Cette solution simple permet de vérifier rapidement la consistance des spécifications de l'APIApplication Programming Interface que vous souhaitez développer en utilisant la section « OR PARSE RAML IN HERE ». L'utilisation de cette section souffre toutefois de quelques limitations.

Afin de profiter de toutes les fonctionnalités de « API Console » (utilisation des bibliothèques, import de schémas JSON et XML, possibilité de lancer « API Console » directement avec le fichier « .raml » souhaité, il est nécessaire d'installer l'outil « API Console » sur un serveur d'application.

Dans notre cas, nous utiliserons « Tomcat » dans sa version « 8.5.5 ».

L'installation de Tomcat est très simple. Il suffit de procéder comme suit :

  1. Allez sur le site http://tomcat.apache.org/ et dans la page qui apparaît, dans la section « Download » du menu de gauche, choisissez « Tomcat 8 » (nous nous abstenons, ici, d'utiliser la version 9 de « Tomcat », car au moment de la rédaction de cet article, il s'agit encore d'une version « Bêta »).

    Image non disponible
  2. Dans la page qui apparaît, et dans le cas où, comme nous, vous travaillez sous « Windows », cliquez sur le lien «  32-bit/64-bit Windows Service Installer (pgp, md5, sha1) » dans la section « Binary Distributions » :

    Image non disponible
  3. Installez « Tomcat » en cliquant sur l'exécutable téléchargé.

  4. Après son installation « Tomcat » est immédiatement opérationnel. Pour vous en assurer, ouvrez un navigateur Internet et saisissez l'adresse : http://localhost:8080 . La page suivante apparaît :

    Image non disponible
  5. Souvenez-vous de l'archive contenant « API Console » que nous avons téléchargée dans la section précédente.
    Une fois cette archive dézippée, placez le répertoire « dist », avec tous les sous-répertoires qu'il contient, dans le répertoire « C:\Program Files (x86)\Apache Software Foundation\Tomcat 8.5\webapps\ ». Il s'agit du répertoire par défaut dans lequel « Tomcat » gère ses applications si vous n'avez pas modifié les options lors de l'installation de « Tomcat ».
    Le chemin vers le répertoire « webapps » est à modifier si vous avez modifié les paramètres par défaut lors de l'installation de « Tomcat ».

  6. Dès lors, si, dans votre navigateur préféré, vous saisissez l'URL http://localhost:8080/dist, la page d'accueil de « API Console » apparaîtra :
Image non disponible

Il est désormais possible d'utiliser la section « INITIALIZE FROM THE URL OF A RAML FILE » en y renseignant l'URL d'un fichier RAML comme le présente la capture d'écran ci-dessous.

Cette section, contrairement à la section « OR PARSE RAML IN HERE », permet l'appel, dans les spécifications RAML, de bibliothèques ou de schémas JSON ou XML :

Image non disponible

À noter que « API Console » fournit un ensemble d'exemples dans le répertoire « dist/examples/ ». C'est à l'un de ces exemples que nous faisons appel dans la capture d'écran ci-dessus.

Un clic sur le bouton « Load from URL » provoque alors l'affichage de la documentation constituée à partir du fichier RAMLRESTful API Modeling Language passé en paramètre :

Image non disponible

Néanmoins, si vous souhaitez modifier votre fichier RAMLRESTful API Modeling Language après avoir visualisé sa documentation, il est nécessaire de rafraîchir la page pour revenir à la page de saisie de l'URL du fichier puis de ressaisir cette URL.

Pour éviter cet inconvénient, sachez que vous pouvez saisir directement l'URL de votre fichier RAMLRESTful API Modeling Language dans la barre d'adresse sous la forme suivante :

http://localhost:8080/dist/?raml=http://localhost:8080/dist/examples/parc.raml

Comme vous pouvez le voir, cette URL est composée d'une partie fixe contenant le paramètre « raml » (« http://localhost:8080/dist/?raml= ») et d'une partie variable contenant l'URL du fichier RAMLRESTful API Modeling Language placé sur le serveur d'application (dans le répertoire « dist » ou dans l'un de ses sous-répertoires).

Les bibliothèques et autres schémas « JSON » ou « XML » appelés dans l'APIApplication Programming Interface doivent être également placés dans le répertoire « dist » ou dans l'un de ses sous-répertoires.

Image non disponible

XI. Code source de l'exemple

Les fichiers RAMLRESTful API Modeling Language sur lesquels nous avons basé nos exemples sont disponibles ici : http://olivier-rozier.developpez.com/tutoriels/rest/raml/fichiers/exempleRAML.zip

XII. Les alternatives à RAML

Les langages et les projets de documentation d'APIApplication Programming Interface les plus populaires sont « RAMLRESTful API Modeling Language », « Swagger », « Slate » et « API BluePrint ». Il existe également une autre alternative telle « WADLWeb Application Description Language ».

Nous nous attacherons, dans ce paragraphe, à décrire le principe d'utilisation des alternatives à RAMLRESTful API Modeling Language puis à comparer plus en détail RAMLRESTful API Modeling Language avec l'alternative la plus populaire qu'est « Swagger ».

XII-A. OpenAPI Specification (Swagger)

Tout comme RAMLRESTful API Modeling Language, le but de « Swagger » est de générer la documentation d'APIApplication Programming Interface RESTRepresentational State Transfer compréhensible à la fois par l'ordinateur et par l'humain afin d'assimiler les capacités d'un service sans pour autant devoir lire le code source. La rédaction de ces spécifications peut s'effectuer en YAML ou en JSONJavaScript Object Notation.

Les spécifications écrites sous « Swagger » permettent de définir un ensemble de fichiers décrivant une APIApplication Programming Interface. Le projet « Swagger-UI » utilise ensuite ces fichiers pour générer une jolie documentation.

Il est ensuite possible d'utiliser « Swagger-Codegen » pour générer à la fois les bibliothèques client et implémenter le côté serveur dans différents langages.

Pour plus d'informations concernant « Swagger », n'hésitez pas à consulter le site officiel : http://swagger.io/

XII-B. API BluePrint

« API BluePrint » s'appuie uniquement sur le langage markdown. Contrairement à RAMLRESTful API Modeling Language et à « Swagger », « API BluePrint » requiert l'installation d'un outil permettant d'interpréter un fichier BluePrint et de générer des fichiers HTML statiques. Parmi ces outils, nous pouvons citer « Apiary », « Dredd », « Drafter »…

C'est le principal inconvénient de « API BluePrint » : il manque d'outils avancés et de générateur de code permettant de tester le bien-fondé des spécifications ou de générer l'API correspondante. C'est la principale raison de son adoption très lente par rapport à « Swagger » et à RAMLRESTful API Modeling Language.

XII-C. WADL

« WADLWeb Application Description Language » est un langage basé sur le XML et qui a été présenté au W3CWorld Wide Web Consortium par Sun Microsystem en 2009 pour décrire les interactions avec un service web RESTRepresentational State Transfer. Néanmoins, le consortium n'a toujours pas souhaité engager une standardisation de ce langage.

Dans un fichier WADLWeb Application Description Language, des balises « resource » décrivent les ressources accessibles et englobent des balises « method », permettant de décrire les méthodes d'accès à ces ressources. Les balises « method » contiennent, par l'intermédiaire d'une balise « request », la description des paramètres en entrée de ces méthodes. Une balise « response » précise le type de réponse attendu (xml, json…) L'ensemble de ces balises permet donc de décrire les requêtes et les réponses liées à une ressource.

On peut dire que « WADLWeb Application Description Language » est l'équivalent, côté service web, du WSDLWeb Services Description Language pour les services web SOAPSimple Object Access Protocol.

Il s'agit d'un langage assez « pompeux » pour créer des descriptions et pas toujours très compréhensible aux néophytes par rapport à RAMLRESTful API Modeling Language , « Swagger » ou « API BluePrint ».

La différence majeure entre RAML et les WSDL réside dans le fait que le contrat (ou la spécification de l'API) définit le « template » des URI utilisées en tant que « endpoint », mais également le format et la structure des données échangées lors de chaque requête.

En fonction des besoins du client, on peut se contenter de spécifier les formats d'échanges des données (JSON ou XML) ou uniquement quelques exemples d'échanges. Ces informations peuvent suffire à un développeur pour créer un client tout en négligeant le serveur. Contrairement à WSDL, il n'y a pas, en RAMLRESTful API Modeling Language, de contrat à proprement parler entre le client et le serveur : les clients ne sont pas couplés à un endpoint. Ils sont couplés à un type de média (JSON, XML…) transféré entre le client et le serveur. Avec RAML, on ne se soucie pas de la couche transport contrairement à WSDL.

XII-D. Slate

« Slate » utilise le langage markdown. Il permet uniquement de générer la documentation de l'APIApplication Programming Interface puis l'encapsule dans une page HTML statique qui, par conséquent, est très simple à héberger.

« Slate » est une alternative intéressante, car il constitue un bon compromis entre la syntaxe, la facilité d'utilisation et la présentation.

Néanmoins, il est intéressant de remarquer que « Slate », tout comme « API BluePrint », n'utilise pas un langage de description interprétable et sur lequel peuvent être générées des interfaces de tests comme c'est le cas pour RAMLRESTful API Modeling Language et « Swagger ».

Pour plus d'informations concernant « Slate », n'hésitez pas à consulter le site officiel : https://github.com/lord/slate

XII-E. Comparaison entre RAML et « Swagger »

RAMLRESTful API Modeling Language et « Swagger » constituent les deux principales solutions de documentation d'APIApplication Programming Interface RESTRepresentational State Transfer.

La qualité d'une API sera avant tout caractérisée par la qualité de sa documentation. C'est au développeur de choisir le langage ou le framework qui est le plus approprié à son objectif.

Avec « Swagger », la génération de SDKSoftware Development Kit est intégrée dans le système, notamment par le support de nombreux langages de programmation. Si l'objectif désiré est l'élaboration d'un SDKSoftware Development Kit pour l'interaction client, « Swagger » est la solution à retenir.

En revanche, si la génération de SDKSoftware Development Kit est gérée via des applications tierces ou que vous désirez utiliser une méthodologie bien particulière (voir par exemple le tutoriel « Tutoriel pour développer et déployer un service Web RESTFUL OSGI multibundle sous Eclipse et Karaf » dans lequel on divise le service web RESTRepresentational State Transfer en couches), ou que vous souhaitez faire participer des développeurs et des non-développeurs à la documentation, RAMLRESTful API Modeling Language est fait pour vous. En effet, même si de nombreux développeurs préfèrent le format JSONJavaScript Object Notation, RAMLRESTful API Modeling Language permet aux développeurs et aux non-développeurs de lire et de mettre à jour la spécification. RAMLRESTful API Modeling Language est donc parfait pour concevoir une API à partir de rien ou pour rendre la documentation plus lisible.

Au niveau de l'outil d'édition, RAMLRESTful API Modeling Language et « Swagger » disposent tous les deux d'éditeurs intuitifs possédant une console. Cette console fournit un aperçu montrant exactement à quoi ressemblera le produit fini en fonction du contenu de l'éditeur.

En ce qui concerne la génération d'une APIApplication Programming Interface à partir de ses spécifications, si vous avez utilisé RAMLRESTful API Modeling Language, vous pouvez utiliser « Mule Studio » avec « Apikit » tel que montré à l'adresse : https://docs.mulesoft.com/apikit/apikit-tutorial. Il s'agit de la méthode la plus rapide pour construire et déployer un projet depuis les spécifications RAMLRESTful API Modeling Language.

De manière plus poussée, vous pouvez utiliser l'outil « raml-for-jax-rs » pour passer de spécifications RAMLRESTful API Modeling Language à l'implémentation d'interfaces JAVA utilisant les annotations JAX-RS ou inversement (voir la page https://github.com/mulesoft/raml-for-jax-rs).

En plus de JAX-RS, un grand nombre de langages de programmation sont désormais disponibles. Pour plus de renseignements, n'hésitez pas à visiter la page : http://raml.org/developers/build-your-api

« Swagger », quant à lui, possède un outil appelé « Swagger Codegen » pour développer votre API dans le langage de programmation désiré parmi ceux répertoriés dans le lien suivant : https://github.com/swagger-api/swagger-codegen#swagger-codegen-core-team.

Il est à noter que les outils disponibles pour « Swagger » prennent en charge plus de langages de programmation que les outils disponibles pour RAMLRESTful API Modeling Language.

XIII. Conclusion

Dans ce tutoriel, nous avons vu que RAMLRESTful API Modeling Language constitue un moyen de décrire les API RESTful de manière tout à fait compréhensible pour un développeur et même pour un analyste grâce à l'utilisation d'IDEIntegrated Development Environment tel « API Workbench ».

Une spécification RAMLRESTful API Modeling Language représente un contrat qui reflète la structure de l'API tout en étant indépendant de son implémentation.

Il est ainsi possible de distinguer rapidement :

  • les types utilisés ainsi que les caractéristiques qui les composent ;
  • les ressources à appeler ;
  • les méthodes pouvant être utilisées sur les ressources.

En plus de ces avantages, RAMLRESTful API Modeling Language encourage la réutilisabilité du code (via les bibliothèques) et l'utilisation de « pattern ».

Je tiens à remercier Mickaël Baron pour la relecture technique de cet article et f-leb pour la relecture orthographique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2016 Olivier ROZIER. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.