«1. Обзор

В нашей учебной статье по RAML мы представили язык моделирования RESTful API и создали простое определение API, основанное на единственном объекте с именем Foo. Теперь представьте реальный API, в котором у вас есть несколько ресурсов сущностного типа с одинаковыми или похожими операциями GET, POST, PUT и DELETE. Вы видите, как ваша документация по API может быстро стать утомительной и повторяющейся.

В этой статье мы покажем, как использование функций типов ресурсов и признаков в RAML может устранить избыточность в определениях ресурсов и методов путем извлечения и параметризации общих разделов, тем самым устраняя ошибки копирования и вставки и делая определения API более информативными. лаконичный.

2. Наш API

Чтобы продемонстрировать преимущества типов и характеристик ресурсов, мы расширим наш исходный API, добавив ресурсы для второго типа объекта, называемого Bar. Вот ресурсы, которые составят наш обновленный API:

    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}\u0026ownerName={ownerName} 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. Распознавание шаблонов

Когда мы читаем список ресурсов в нашем API, мы начинаем замечать появление некоторых шаблонов. Например, существует шаблон для URI и методов, используемых для создания, чтения, обновления и удаления отдельных сущностей, а также шаблон для URI и методов, используемых для извлечения коллекций сущностей. Шаблон коллекции и элемента коллекции является одним из наиболее распространенных шаблонов, используемых для извлечения типов ресурсов в определениях RAML.

Давайте рассмотрим пару разделов нашего API:

[Примечание: в приведенных ниже фрагментах кода строка, содержащая только три точки (…), указывает на то, что некоторые строки пропущены для краткости.] ~~ ~

/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

Когда мы сравниваем RAML-определения ресурсов /foos и /bars, включая используемые HTTP-методы, мы можем увидеть несколько дублирующих друг друга свойств каждого из них, и мы снова видим, что начинают появляться закономерности.

Везде, где есть шаблон в определении ресурса или метода, есть возможность использовать тип или признак ресурса RAML.

4. Типы ресурсов

Для реализации шаблонов, обнаруженных в API, типы ресурсов используют зарезервированные и определяемые пользователем параметры, заключенные в двойные угловые скобки (\u003c\u003c и \u003e\u003e).

4.1 Зарезервированные параметры

В определениях типов ресурсов могут использоваться два зарезервированных параметра:

    \u003c\u003cresourcePath\u003e\u003e представляет собой полный URI (после baseURI), а \u003c\u003cresourcePathName\u003e\u003e представляет часть URI после крайней правой косой черты (/), игнорируя фигурные скобки { }.

При обработке внутри определения ресурса их значения рассчитываются на основе определяемого ресурса.

Учитывая ресурс /foos, например, \u003c\u003cresourcePath\u003e\u003e оценивается как «/foos», а \u003c\u003cresourcePathName\u003e\u003e оценивается как «foos».

Учитывая ресурс /foos/{fooId}, \u003c\u003cresourcePath\u003e\u003e оценивается как «/foos/{fooId}», а \u003c\u003cresourcePathName\u003e\u003e оценивается как «foos».

4.2 Пользовательские параметры

Определение типа ресурса может также содержать пользовательские параметры. В отличие от зарезервированных параметров, значения которых определяются динамически на основе определяемого ресурса, определяемым пользователем параметрам должны присваиваться значения везде, где используется тип ресурса, содержащий их, и эти значения не изменяются.

Пользовательские параметры могут быть объявлены в начале определения типа ресурса, хотя это не требуется и не является общепринятой практикой, поскольку читатель обычно может понять их предполагаемое использование, зная их имена и контексты, в которых они используются. используются.

4.3 Функции параметров

Несколько полезных текстовых функций доступны для использования везде, где используется параметр, для преобразования расширенного значения параметра при его обработке в определении ресурса.

«Вот функции, доступные для преобразования параметров:

    !singularize !pluralize !uppercase !lowercase !uppercamelcase !lowercamelcase !upperunderscorecase !lowerunderscorecase !upperhyphencase !lowerhyphencase

Функции применяются к параметру с использованием следующей конструкции:

\u003c \u003cимя_параметра | !functionName\u003e\u003e

Если вам нужно использовать более одной функции для достижения желаемого преобразования, вы должны отделить каждое имя функции символом вертикальной черты (“|†) и добавить восклицательный знак (!) перед каждым используемая функция.

Например, для ресурса /foos, где \u003c\u003cresourcePathName\u003e\u003e оценивается как «foos»:

    \u003c\u003cresourcePathName | !singularize\u003e\u003e ==\u003e «foo» \u003c\u003cresourcePathName | !uppercase\u003e\u003e ==\u003e «FOOS» \u003c\u003cresourcePathName | !единственное число | !uppercase\u003e\u003e ==\u003e \»FOO\»

И учитывая ресурс /bars/{barId}, где \u003c\u003cresourcePathName\u003e\u003e оценивается как \»bars\»:

    \u003c\u003cresourcePathName | !uppercase\u003e\u003e ==\u003e «BARS» \u003c\u003cresourcePathName | !uppercamelcase\u003e\u003e ==\u003e «Bar»

5. Извлечение типа ресурса для коллекций

Давайте реорганизуем определения ресурсов /foos и /bars, показанные выше, используя тип ресурса для захвата общих свойств. Мы будем использовать зарезервированный параметр \u003c\u003cresourcePathName\u003e\u003e и определяемый пользователем параметр \u003c\u003ctypeName\u003e\u003e для представления используемого типа данных.

5.1 Определение

Вот определение типа ресурса, представляющее набор элементов:

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

Обратите внимание, что в нашем API, поскольку наши типы данных представляют собой просто заглавные версии имен наших базовых ресурсов в единственном числе, мы можно было применить функции к параметру \u003c\u003cresourcePathName\u003e\u003e вместо введения определяемого пользователем параметра \u003c\u003ctypeName\u003e\u003e для достижения того же результата для этой части API:

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

5.2 Приложение

Используя приведенное выше определение, включающее параметр \u003c\u003ctypeName\u003e\u003e, вот как можно применить тип ресурса «коллекция» к ресурсам /foos и /bars:

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

Обратите внимание, что мы по-прежнему можем включать различия между двумя ресурсами — в данном случае разделом queryParameters — при этом используя все преимущества, которые может предложить определение типа ресурса.

6. Извлечение типа ресурса для отдельных элементов коллекции

Теперь сосредоточимся на части нашего API, связанной с отдельными элементами коллекции: ресурсах /foos/{fooId} и /bars/{barId} . Вот код для /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

Определение ресурса /bars/{barId} также имеет методы GET, PUT и DELETE и идентично определению /foos/{fooId}, в остальном чем вхождение строк «foo» и «bar» (и их соответствующих форм во множественном числе и/или с заглавной буквы).

6.1 Определение

Извлекая шаблон, который мы только что идентифицировали, вот как мы определяем тип ресурса для отдельных элементов коллекции:

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 Приложение

А вот как мы применяем †Тип ресурса «item»:

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

7. Признаки

В то время как тип ресурса используется для извлечения шаблонов из определений ресурсов, признак используется для извлечения шаблонов из определений методов, общих для ресурсов.

7.1 Параметры

Наряду с \u003c\u003cresourcePath\u003e\u003e и \u003c\u003cresourcePathName\u003e\u003e, один дополнительный зарезервированный параметр доступен для использования в определениях трейтов: \u003c\u003cmethodName\u003e\u003e возвращает метод HTTP (GET, POST, PUT , DELETE и т. д.), для которых определена черта. Определяемые пользователем параметры также могут появляться в определении признаков и, если они применяются, принимают значение ресурса, в котором они применяются.

7.2 Определение

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

Обратите внимание, что тип ресурса «item» по-прежнему полон избыточности. Давайте посмотрим, как черты могут помочь устранить их. Мы начнем с извлечения типажа для любого метода, содержащего тело запроса:

  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

Наконец, вот признак для любого метода, который может вернуть ответ об ошибке 404:

7.3 Приложение

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:

Затем мы применяем этот трейт к нашим типам ресурсов:

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

«

«Мы также можем применять трейты к методам, определенным в ресурсах. Это особенно полезно для «разовых» сценариев, когда комбинация ресурсов и методов соответствует одной или нескольким характеристикам, но не соответствует какому-либо определенному типу ресурса:

8. Заключение

В этом руководстве мы Мы показали, как значительно уменьшить или, в некоторых случаях, устранить избыточность в определении RAML API.

Сначала мы идентифицировали избыточные разделы наших ресурсов, распознали их шаблоны и извлекли типы ресурсов. Затем мы сделали то же самое для методов, которые были общими для ресурсов для извлечения признаков. Затем мы смогли устранить дополнительную избыточность, применив черты к нашим типам ресурсов и к «одноразовым» комбинациям ресурсов и методов, которые строго не соответствовали одному из определенных нами типов ресурсов.

В результате наш простой API с ресурсами только для двух сущностей сократился со 177 до чуть более 100 строк кода. Чтобы узнать больше о типах и свойствах ресурсов RAML, посетите спецификацию RAML.org 1.0.

#%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 } ]
Next »

Modular RAML Using Includes, Libraries, Overlays and Extensions

« Previous

Introduction to RAML – The RESTful API Modeling Language