Éliminer les redondances dans RAML avec des types de ressources et des caractéristiques

1. Vue d’ensemble

Dans notre lien:/raml-restful-api-modeling-language-tutorial[tutoriel RAML], nous avons présenté le langage de modélisation API RESTful et créé une définition d’API simple basée sur une entité unique appelée Foo__. Imaginez maintenant une API du monde réel dans laquelle vous avez plusieurs ressources de type entité, ayant toutes les mêmes opérations GET, POST, PUT et DELETE, ou des opérations similaires. Vous pouvez voir comment la documentation de votre API peut rapidement devenir fastidieuse et répétitive.

Dans cet article, nous montrons comment l’utilisation des fonctionnalités resource types et traits dans RAML peut éliminer les redondances dans les définitions de ressources et de méthodes en extrayant et en paramétrant des sections communes, éliminant ainsi les erreurs de copier-coller tout en rendant vos définitions d’API plus concises. .

2. Notre API

Afin de démontrer les avantages de resource types et traits , nous allons développer notre API d’origine en ajoutant des ressources pour un second type d’entité appelé Bar . Voici les ressources qui composeront notre API révisée:

  • GET/api/v1/foos

  • POST/api/v1/foos

  • GET/api/v1/foos/\ {fooId}

  • PUT/api/v1/foos/\ {fooId}

  • DELETE/api/v1/foos/\ {fooId}

  • GET/api/v1/foos/name/\ {name}

  • GET/api/v1/foos? Name = \ {name} & ownerName = \ {ownerName}

  • GET/api/v1/bars

  • POST/api/v1/bars

  • GET/api/v1/bars/\ {barId}

  • PUT/api/v1/barres/\ {barId}

  • DELETE/api/v1/bars/\ {barId}

  • GET/api/v1/barres/fooId/\ {fooId}

3. Reconnaître les motifs

En lisant la liste des ressources de notre API, nous voyons apparaître des modèles. Par exemple, il existe un modèle pour les URI et les méthodes utilisées pour créer, lire, mettre à jour et supprimer des entités uniques, et un modèle pour les URI et les méthodes utilisées pour extraire des collections d’entités. Le modèle de collection et d’élément de collection est l’un des modèles les plus couramment utilisés pour extraire les types de ressources dans les définitions RAML.

Examinons quelques sections de notre API:

{vide}[Remarque: dans les extraits de code ci-dessous, une ligne contenant uniquement trois points (…​) indique que certaines lignes sont ignorées pour des raisons de brièveté.]

----/foos:
  get:
    description: |
      List all foos matching query criteria, if provided;
      otherwise list all foos
    queryParameters:
      name?: string
      ownerName?: string
    responses:
      200:
        body:
          application/json:
            type: Foo[]  post:
    description: Create a new foo
    body:
      application/json:
        type: Foo
    responses:
      201:
        body:
          application/json:
            type: Foo
.../bars:
  get:
    description: |
      List all bars matching query criteria, if provided;
      otherwise list all bars
    queryParameters:
      name?: string
      ownerName?: string
    responses:
      200:
        body:
          application/json:
            type: Bar[]  post:
    description: Create a new bar
    body:
      application/json:
        type: Bar
    responses:
      201:
        body:
          application/json:
            type: Bar
----

Lorsque nous comparons les définitions RAML des ressources /foos et /bars , y compris les méthodes HTTP utilisées, nous pouvons constater plusieurs redondances entre les différentes propriétés de chacune et nous voyons à nouveau des modèles émerger.

Chaque fois qu’il y a un motif dans une définition de ressource ou de méthode, il est possible d’utiliser un type de ressource RAML ou un chemin de traitement__.

4. Types de ressources

Afin d’implémenter les modèles trouvés dans l’API, resource types utilise des paramètres réservés et définis par l’utilisateur, entourés de crochets doubles (<< et >>).

4.1 Paramètres réservés

Deux paramètres réservés peuvent être utilisés dans les définitions de type de ressource:

  • << resourcePath >> représente l’URI entier (à la suite de la

baseURI ), et ** << resourcePathName >> représente la partie de l’URI suivant le

la barre oblique la plus à droite (/), en ignorant les accolades \ {}.

Lorsqu’elles sont traitées dans une définition de ressource, leurs valeurs sont calculées en fonction de la ressource en cours de définition.

Par exemple, avec la ressource /foos , << resourcePath >> serait évalué à «/foos» et << resourcePathName >> serait évalué à «foos».

Étant donné la ressource /foos/\ {fooId} , << ressourceRetraitement >> serait évalué à «/food/\ {nourriture}» et «ressource PathName >> serait évaluée à« foos ».

4.2 Paramètres définis par l’utilisateur

Une définition de type__resource peut également contenir des paramètres définis par l’utilisateur.

Contrairement aux paramètres réservés, dont les valeurs sont déterminées de manière dynamique en fonction de la ressource définie, les paramètres définis par l’utilisateur doivent se voir attribuer des valeurs à chaque fois que le type__resource qui les contient est utilisé et que ces valeurs ne changent pas.

Les paramètres définis par l’utilisateur peuvent être déclarés au début d’une définition resource type , bien que cela ne soit ni nécessaire ni courant, car le lecteur peut généralement déterminer leur utilisation prévue en fonction de leurs noms et des contextes dans lesquels ils sont utilisés.

4.3 Fonctions de paramètre

Une poignée de fonctions de texte utiles peuvent être utilisées partout où un paramètre est utilisé afin de transformer la valeur étendue du paramètre lorsqu’il est traité dans une définition de ressource.

Voici les fonctions disponibles pour la transformation de paramètres:

  • ! singularize

  • ! pluralize

  • ! uppercase

  • ! lowercase

  • ! uppercamelcase

  • ! lowercamelcase

  • ! upperunderscorecase

  • ! lowerunderscorecase

  • ! upperhyphencase

  • ! lowerhyphencase

Les fonctions sont appliquées à un paramètre en utilisant la construction suivante:

<< _ parameterName | ! functionName _ >>

Si vous devez utiliser plusieurs fonctions pour obtenir la transformation souhaitée, séparez chaque nom de fonction par le symbole de canal (“|”) et ajoutez un point d’exclamation (!) Avant chaque fonction utilisée.

Par exemple, étant donné la ressource /foos , où << _ resourcePathName _ >> renvoie «foos»:

  • << _ resourcePathName | ! singularize _ >> =⇒ “foo”

  • << _ resourcePathName | ! majuscule _ >> =⇒ “FOOS”

  • << _ resourcePathName | ! singularize | ! majuscule _ >> =⇒ “FOO”

Et étant donné la ressource /bars/\ {barId} , où << _ resourcePathName _ >> est évalué à «barres»:

  • << _ resourcePathName | ! majuscule _ >> =⇒ “BARRES”

  • << _ resourcePathName | ! uppercamelcase _ >> =⇒ “Bar”

5. Extraire un type de ressource pour les collections

Modifions les définitions de ressources /foos et /bars indiquées ci-dessus, en utilisant un resource type pour capturer les propriétés communes. Nous allons utiliser le paramètre réservé << nom du ressource >> , et le paramètre défini par l’utilisateur << typeName >> pour représenter le type de données utilisé.

5.1 Définition

Voici une définition de type__resource représentant un ensemble d’éléments:

resourceTypes:
  collection:
    usage: Use this resourceType to represent any collection of items
    description: A collection of <<resourcePathName>>
    get:
      description: Get all <<resourcePathName>>, optionally filtered
      responses:
        200:
          body:
            application/json:
              type: <<typeName>>[]    post:
      description: Create a new <<resourcePathName|!singularize>>
      responses:
        201:
          body:
            application/json:
              type: <<typeName>>

Notez que dans notre API, étant donné que nos types de données sont simplement des versions majuscules et capitalisées des noms de nos ressources de base, nous pourrions appliquer des fonctions au paramètre << _ resourcePathName >>, au lieu d’introduire le paramètre défini par l’utilisateur << typeName _ >> , pour obtenir le même résultat pour cette partie de l’API:

resourceTypes:
  collection:
  ...
    get:
      ...
            type: <<resourcePathName|!singularize|!uppercamelcase>>[]    post:
      ...
            type: <<resourcePathName|!singularize|!uppercamelcase>>

5.2 Application

En utilisant la définition ci-dessus qui incorpore le paramètre << _ typeName >>, voici comment appliquer la «collection» resource type aux ressources /foos et/ bars_ :

----/foos:
  type: { collection: { "typeName": "Foo" } }
  get:
    queryParameters:
      name?: string
      ownerName?: string
.../bars:
  type: { collection: { "typeName": "Bar" } }
----

Notez que nous sommes toujours en mesure d’intégrer les différences entre les deux ressources - dans ce cas, la section queryParameters - tout en tirant parti de tout ce que la définition de resource type a à offrir.

6. Extraction d’un type de ressource pour des éléments uniques d’une collection

Concentrons-nous maintenant sur la partie de notre API traitant des éléments uniques d’une collection: les ressources /foos/\ {fooId} et /bars/\ {barId} . Voici le code pour _/foos/\ {fooId} _ :

----/foos:
...
 /{fooId}:
    get:
      description: Get a Foo
      responses:
        200:
          body:
            application/json:
              type: Foo
        404:
          body:
            application/json:
              type: Error
              example: !include examples/Error.json
    put:
      description: Update a Foo
      body:
        application/json:
          type: Foo
      responses:
        200:
          body:
            application/json:
              type: Foo
        404:
          body:
            application/json:
              type: Error
              example: !include examples/Error.json
    delete:
      description: Delete a Foo
      responses:
        204:
        404:
          body:
            application/json:
              type: Error
              example: !include examples/Error.json
----

La définition de ressource /bars/\ {barId} a également des méthodes GET, PUT et DELETE et est identique à la définition/ foos/\ {fooId} , à l’exception des occurrences des chaînes "foo" et "bar" (et leurs formes respectives pluralisées et/ou capitalisées).

6.1 Définition

En extrayant le motif que nous venons d’identifier, voici comment nous définissons un type de ressource pour des éléments uniques d’une collection:

resourceTypes:
...
  item:
    usage: Use this resourceType to represent any single item
    description: A single <<typeName>>
    get:
      description: Get a <<typeName>>
      responses:
        200:
          body:
            application/json:
              type: <<typeName>>
        404:
          body:
            application/json:
              type: Error
              example: !include examples/Error.json
    put:
      description: Update a <<typeName>>
      body:
        application/json:
          type: <<typeName>>
      responses:
        200:
          body:
            application/json:
              type: <<typeName>>
        404:
          body:
            application/json:
              type: Error
              example: !include examples/Error.json
    delete:
      description: Delete a <<typeName>>
      responses:
        204:
        404:
          body:
            application/json:
              type: Error
              example: !include examples/Error.json

6.2 Application

Et voici comment nous appliquons le "item" resource type :

----/foos:
...
 /{fooId}:
    type: { item: { "typeName": "Foo" } }
----
... /bars:
...
 /{barId}:
    type: { item: { "typeName": "Bar" } }

7. Traits

Alors qu’un type resource est utilisé pour extraire des modèles à partir de définitions de ressources, un trait__ est utilisé pour extraire des modèles à partir de définitions de méthodes communes à toutes les ressources.

7.1 Paramètres

Avec << _ resourcePath >> et << resourcePathName _ >>, un paramètre réservé supplémentaire est disponible pour être utilisé dans les définitions de trait:

<< _ methodName >> correspond à la méthode HTTP (GET, POST, PUT, DELETE, etc.) pour laquelle le trait_ est défini. Les paramètres définis par l’utilisateur peuvent également apparaître dans une définition de trait et, lorsqu’ils sont appliqués, prendre la valeur de la ressource dans laquelle ils sont appliqués.

7.2 Définition

Notez que le type de ressource “item” est toujours plein de redondances.

Voyons comment les détroits peuvent aider à les éliminer. Nous allons commencer par extraire un trait pour toute méthode contenant un corps de requête:

traits:
  hasRequestItem:
    body:
      application/json:
        type: <<typeName>>

Maintenant, extrayons traits pour les méthodes dont les réponses normales contiennent des corps:

  hasResponseItem:
    responses:
      200:
        body:
          application/json:
            type: <<typeName>>
  hasResponseCollection:
    responses:
      200:
        body:
          application/json:
            type: <<typeName>>[]----

Enfin, voici un __trait__ pour toute méthode pouvant renvoyer une réponse d'erreur 404:

[source,javascript,gutter:,true]
hasNotFound:
  responses:
    404:
      body:
        application/json:
          type: Error
          example: !include examples/Error.json
====  **  7.3 Application **

Nous appliquons ensuite ce __trait__ à nos __ressources types__:

[source,javascript,gutter:,true]

resourceTypes: collection: usage: Use this resourceType to represent any collection of items description: A collection of [resourcePathName|!uppercamelcase] get: description: | Get all [resourcePathName|!uppercamelcase] , optionally filtered is:[hasResponseCollection: { typeName: [typeName] }] post: description: Create a new [resourcePathName|!singularize] is:[hasRequestItem: { typeName: [typeName] }] item: usage: Use this resourceType to represent any single item description: A single [typeName] get: description: Get a [typeName] is:[hasResponseItem: { typeName: [typeName] }, hasNotFound] put: description: Update a [typeName] is: |[hasRequestItem: { typeName: [typeName] }, hasResponseItem: { typeName: [typeName] }, hasNotFound] delete: description: Delete a [typeName] is:[hasNotFound] responses: 204:

Nous pouvons également appliquer __traits__ aux méthodes définies dans les ressources. Ceci est particulièrement utile pour les scénarios «ponctuels» où une combinaison ressource-méthode correspond à un ou plusieurs __traits__ mais ne correspond à aucun type de ressource défini__:

[source,javascript,gutter:,true]

----/foos:
...
 /name/{name}:
    get:
      description: List all foos with a certain name
      is:[hasResponseCollection: { typeName: Foo }]----

===  **  8. Conclusion**

Dans ce didacticiel, nous avons montré comment réduire de manière significative, voire dans certains cas, éliminer les redondances d’une définition d’API RAML.

Premièrement, nous avons identifié les sections redondantes de nos ressources, reconnu leurs modèles et extrait les types de ressources. Nous avons ensuite procédé de la même manière pour les méthodes communes à toutes les ressources afin d’extraire __traits__. Ensuite, nous avons pu éliminer d'autres redondances en appliquant __traits__ à nos types de ressources et à des combinaisons méthode-ressource «uniques» qui ne correspondaient pas strictement à l'un de nos types de ressources définis.

En conséquence, notre API simple avec des ressources pour seulement deux entités a été réduite de 177 à un peu plus de 100 lignes de code. Pour en savoir plus sur RAML __resource types__ et __traits__, visitez le https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md#resource-types-and- traits[RAML.org 1.0 spec].

La **  mise en œuvre complète **  de ce didacticiel est disponible à l'adresse https://github.com/eugenp/tutorials/tree/master/raml/resource-types-and-traits[le projet github].

Voici notre dernière API RAML dans son intégralité:

[source,javascript,gutter:,true]

#%RAML 1.0 title: Baeldung Foo REST Services API version: v1 protocols:[HTTPS]baseUri: http://rest-api.baeldung.com/api/{version} mediaType: application/json securedBy: basicAuth securitySchemes: basicAuth: description: | Each request must contain the headers necessary for basic authentication type: Basic Authentication describedBy: headers: Authorization: description: | Used to send the Base64 encoded "username:password" credentials type: string responses: 401: description: | Unauthorized. Either the provided username and password combination is invalid, or the user is not allowed to access the content provided by the requested URL. types: Foo: !include types/Foo.raml Bar: !include types/Bar.raml Error: !include types/Error.raml resourceTypes: collection: usage: Use this resourceType to represent a collection of items description: A collection of [resourcePathName|!uppercamelcase] get: description: | Get all [resourcePathName|!uppercamelcase] , optionally filtered is:[hasResponseCollection: { typeName: [typeName] }] post: description: | Create a new [resourcePathName|!uppercamelcase|!singularize] is:[hasRequestItem: { typeName: [typeName] }] item: usage: Use this resourceType to represent any single item description: A single [typeName] get: description: Get a [typeName] is:[hasResponseItem: { typeName: [typeName] }, hasNotFound] put: description: Update a [typeName] is:[hasRequestItem: { typeName: [typeName] }, hasResponseItem: { typeName: [typeName] }, hasNotFound] delete: description: Delete a [typeName] is:[hasNotFound] responses: 204: traits: hasRequestItem: body: application/json: type: [typeName] hasResponseItem: responses: 200: body: application/json: type: [typeName] hasResponseCollection: responses: 200: body: application/json: type: [typeName] [] hasNotFound: responses: 404: body: application/json: type: Error example: !include examples/Error.json/foos: type: { collection: { typeName: Foo } } get: queryParameters: name?: string ownerName?: string /{fooId}: type: { item: { typeName: Foo } } /name/{name}: get: description: List all foos with a certain name is:[hasResponseCollection: { typeName: Foo }]/bars: type: { collection: { typeName: Bar } } /{barId}: type: { item: { typeName: Bar } } /fooId/{fooId}: get: description: Get all bars for the matching fooId is:[hasResponseCollection: { typeName: Bar }]----

Suivant "

  • "** Précédent