Beseitigen Sie Redundanzen in RAML mit Ressourcentypen und Merkmalen

1. Überblick

In unserem Link:/raml-restful-api-modeling-language-Tutorial[RAML-Tutorial]haben wir die RESTful API Modeling Language eingeführt und eine einfache API-Definition erstellt, die auf einer einzigen Entität namens Foo basiert. Stellen Sie sich nun eine reale API vor, in der Sie über mehrere Entitätstyp-Ressourcen verfügen, die alle die gleichen oder ähnliche GET-, POST-, PUT- und DELETE -Operationen haben. Sie können sehen, wie Ihre API-Dokumentation schnell langweilig und wiederholt werden kann.

In diesem Artikel wird gezeigt, wie durch die Verwendung der resource types - und traits -Features in RAML Redundanzen in Ressourcen- und Methodendefinitionen ** durch das Extrahieren und Parametrieren gemeinsamer Abschnitte beseitigt werden können .

2. Unsere API

Um die Vorteile von resource types und traits zu demonstrieren, werden wir unsere ursprüngliche API durch Hinzufügen von Ressourcen für einen zweiten Entitätstyp namens Bar erweitern. Hier sind die Ressourcen, aus denen sich unsere überarbeitete API zusammensetzt:

  • 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}

  • GET/api/v1/bars

  • POST/api/v1/bars

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

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

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

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

3. Muster erkennen

Wenn wir die Liste der Ressourcen in unserer API durchlesen, werden einige Muster sichtbar. Beispielsweise gibt es ein Muster für die URIs und Methoden, die zum Erstellen, Lesen, Aktualisieren und Löschen einzelner Entitäten verwendet werden, und es gibt ein Muster für die URIs und Methoden, die zum Abrufen von Entitätensammlungen verwendet werden. Das Collection- und Collection-Item-Muster ist eines der am häufigsten verwendeten Muster zum Extrahieren von resource types in RAML-Definitionen.

Schauen wir uns ein paar Abschnitte unserer API an:

{leer}[Hinweis: In den folgenden Codeausschnitten zeigt eine Zeile mit nur drei Punkten (…​) an, dass einige Zeilen der Kürze halber übersprungen werden.]

----/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
----

Wenn wir die RAML-Definitionen der /foos - und /bars -Ressourcen einschließlich der verwendeten HTTP-Methoden vergleichen, sehen wir mehrere Redundanzen zwischen den verschiedenen Eigenschaften der einzelnen, und es treten wieder Muster auf.

Wo immer ein Muster in einer Ressourcen- oder Methodendefinition vorhanden ist, besteht die Möglichkeit, einen RAML-Ressourcentyp oder trait__ zu verwenden.

4. Ressourcentypen

Um die in der API gefundenen Muster zu implementieren, verwenden resource types reservierte und benutzerdefinierte Parameter, die in spitze Klammern (<< und >>) eingeschlossen sind.

4.1 Reservierte Parameter

In Ressourcentypdefinitionen können zwei reservierte Parameter verwendet werden:

  • << resourcePath >> steht für den gesamten URI (nach dem

baseURI ) und ** << resourcePathName >> stellt den Teil des URI dar, der auf den folgt

Schrägstrich (/) ganz rechts, alle geschweiften Klammern ignorieren \ {}.

Bei der Verarbeitung innerhalb einer Ressourcendefinition werden deren Werte basierend auf der definierten Ressource berechnet.

Angenommen, die Ressource /foos würde beispielsweise << resourcePath >> zu "/foos" und << resourcePathName >> zu "foos" auswerten.

Angesichts der Ressource /foos/\ {fooId} würde << resourcePath >> zu "/foos/\ {fooId}" und << resourcePathName >> zu "foos" ausgewertet werden.

4.2 Benutzerdefinierte Parameter

Eine resource type -Definition kann auch benutzerdefinierte Parameter enthalten.

Im Gegensatz zu den reservierten Parametern, deren Werte dynamisch basierend auf der definierten Ressource bestimmt werden, müssen den benutzerdefinierten Parametern überall dort Werte zugewiesen werden, wo der resource type verwendet wird, und diese Werte ändern sich nicht.

Benutzerdefinierte Parameter können zu Beginn einer resource type -Definition deklariert werden, obwohl dies nicht erforderlich ist und keine gängige Praxis ist, da der Leser normalerweise die beabsichtigte Verwendung anhand seines Namens und der Kontexte, in denen sie verwendet werden, ermitteln kann.

4.3 Parameterfunktionen

Eine Handvoll nützlicher Textfunktionen steht überall dort zur Verfügung, wo ein Parameter verwendet wird, um den erweiterten Wert des Parameters zu transformieren, wenn er in einer Ressourcendefinition verarbeitet wird.

Hier sind die Funktionen, die für die Parametrierung verfügbar sind:

  • ! singularize

  • ! pluralize

  • ! uppercase

  • ! lowercase

  • ! uppercamelcase

  • ! lowercamelcase

  • ! upperunderscorecase

  • ! lowerunderscorecase

  • ! upperhyphencase

  • ! lowerhyphencase

Funktionen werden mit folgendem Konstrukt auf einen Parameter angewendet:

<< _ parameterName | ! Funktionsname _ >>

Wenn Sie mehr als eine Funktion benötigen, um die gewünschte Transformation zu erreichen, trennen Sie jeden Funktionsnamen mit dem Pipe-Symbol („|“) und setzen Sie vor jeder verwendeten Funktion ein Ausrufezeichen (!).

Angenommen, die Ressource /foos wird angegeben, wobei << _ resourcePathName _ >> zu "foos" ausgewertet wird:

  • << _ resourcePathName | ! singularize _ >> =⇒ "foo"

  • << _ resourcePathName | ! Großbuchstaben _ >> =⇒ "FOOS"

  • << _ resourcePathName | ! singularize | ! Großbuchstaben _ >> =⇒ "FOO"

Und gegeben der Ressource /bars/\ {barId} , wobei << _ resourcePathName _ >> zu "bars" ausgewertet wird:

  • << _ resourcePathName | ! Großbuchstaben _ >> =⇒ "BARS"

  • << _ resourcePathName | ! uppercamelcase _ >> =⇒ "Bar"

5. Extrahieren eines Ressourcentyps für Sammlungen

Lassen Sie uns die oben angegebenen Ressourcendefinitionen /foos und /bars mit einem resource type umsetzen, um die allgemeinen Eigenschaften zu erfassen. Wir verwenden den reservierten Parameter << resourcePathName >> und den benutzerdefinierten Parameter << typeName >> , um den verwendeten Datentyp darzustellen.

5.1 Definition

Hier ist eine resource type -Definition, die eine Sammlung von Elementen darstellt:

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>>

Beachten Sie, dass in unserer API, da unsere Datentypen lediglich aus Großbuchstaben bestehen, einzelne Versionen der Namen unserer Basisressourcen, wir möglicherweise Funktionen auf den Parameter << _ resourcePathName >> angewendet haben, anstatt den benutzerdefinierten Parameter << typeName _ >> einzuführen Um dasselbe Ergebnis für diesen Teil der API zu erzielen:

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

5.2 Bewerbung

Wenn Sie die obige Definition verwenden, die den Parameter << _ typeName >> enthält, wenden Sie die Auflistung resource type wie folgt auf die Ressourcen /foos und/ bars_ an:

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

Beachten Sie, dass wir immer noch in der Lage sind, die Unterschiede zwischen den beiden Ressourcen - in diesem Fall dem Abschnitt queryParameters - zu berücksichtigen, während Sie dennoch alle Vorteile der Definition des __resource-Typs nutzen.

6. Extrahieren eines Ressourcentyps für einzelne Elemente einer Sammlung

Konzentrieren wir uns nun auf den Teil unserer API, der sich auf einzelne Elemente einer Sammlung bezieht: die Ressourcen /foos/\ {{fooId} und /bars/\ {barId} . Hier ist der Code für _/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
----

Die Ressourcendefinition /bars/\ {barId} verfügt auch über die Methoden GET, PUT und DELETE und ist identisch mit der Definition von/ foos/\ {fooId} , mit Ausnahme der Vorkommen der Zeichenfolgen "foo" und "bar". (und ihre jeweiligen pluralisierten und/oder kapitalisierten Formen).

6.1 Definition

Um das gerade identifizierte Muster zu extrahieren, definieren wir einen resource type für einzelne Elemente einer Sammlung:

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 Bewerbung

Und so wenden wir den "item" resource type an:

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

7. Züge

Während ein resource type zum Extrahieren von Mustern aus Ressourcendefinitionen verwendet wird, wird ein trait zum Extrahieren von Mustern aus Methodendefinitionen verwendet, die für alle Ressourcen gelten.

7.1 Parameter

Neben << _ resourcePath >> und << resourcePathName _ >> steht ein zusätzlicher reservierter Parameter zur Verwendung in Merkmaldefinitionen zur Verfügung:

<< _ methodName >> wertet die HTTP-Methode (GET, POST, PUT, DELETE usw.) aus, für die der trait_ definiert ist. Benutzerdefinierte Parameter können auch in einer Merkmalsdefinition enthalten sein und, sofern angewendet, den Wert der Ressource annehmen, auf die sie angewendet werden.

7.2 Definition

Beachten Sie, dass der "Ressourcentyp" "item" noch immer voller Redundanzen ist.

Mal sehen, wie traits sie beseitigen kann. Wir beginnen mit dem Extrahieren eines trait für jede Methode, die einen Anfragetext enthält:

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

Jetzt extrahieren wir traits für Methoden, deren normale Antworten Körper enthalten:

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

Schließlich ist hier ein __trait__ für jede Methode, die eine 404-Fehlerantwort zurückgeben könnte:

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

Wir wenden diesen __trait__ dann auf unsere __resource-Typen__ an:

[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:

Wir können __traits__ auch auf Methoden anwenden, die in Ressourcen definiert sind. Dies ist besonders nützlich für "einmalige" Szenarien, in denen eine Kombination aus Ressourcen und Methoden mit einem oder mehreren __traits__ übereinstimmt, jedoch keinen definierten __resource type__:

[source,javascript,gutter:,true]

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

===  **  8. Fazit**

In diesem Lernprogramm haben wir gezeigt, wie Sie Redundanzen aus einer RAML-API-Definition erheblich reduzieren oder in einigen Fällen beseitigen können.

Zuerst haben wir die redundanten Abschnitte unserer Ressourcen identifiziert, ihre Muster erkannt und __Ressourcentypen__ extrahiert. Dann haben wir dasselbe für die Methoden getan, die für alle Ressourcen üblich waren, um __traits__ zu extrahieren. Dann konnten wir weitere Redundanzen eliminieren, indem wir __traits__ auf unsere __resource-Typen__ und auf einmalige Ressourcen-Methoden-Kombinationen anwenden, die nicht mit einem unserer definierten __resource-Typen__ übereinstimmen.

Als Ergebnis wurde unsere einfache API mit Ressourcen für nur zwei Entitäten von 177 auf etwas mehr als 100 Codezeilen reduziert. Weitere Informationen zu RAML __resource types__ und __traits__ finden Sie unter https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md#resource-types-and- Eigenschaften[RAML.org 1.0 spec].

Die **  vollständige Implementierung **  dieses Tutorials finden Sie unter https://github.com/eugenp/tutorials/tree/master/raml/resource-types-and-traits des github-Projekts].

Hier ist unsere endgültige RAML-API in ihrer Gesamtheit:

[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 }]----

Nächster "

https://www.baeldung.com/modular-raml-includes-overlays-bibliotheken-erweiterung [Modular RAML mit Includes, Bibliotheken, Overlays und Erweiterungen

  • "** Bisherige