Устранить избыточности в RAML с помощью типов ресурсов и характеристик

1. Обзор

В нашей ссылке:/raml-restful-api-моделирование-language-tutorial[RAML tutorial]мы представили RESTful API Modeling Language и создали простое определение API на основе единой сущности с именем Foo . Теперь представьте себе реальный API, в котором у вас есть несколько ресурсов типа сущностей, у всех из которых есть одинаковые или похожие операции GET, POST, PUT и DELETE. Вы можете увидеть, как ваша документация по API может быстро стать утомительной и повторяющейся.

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

2. Наш API

Чтобы продемонстрировать преимущества resource types и traits , мы расширим наш исходный 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}

  • 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 и методов, используемых для извлечения коллекций объектов. Шаблон collection и collection-item является одним из наиболее распространенных шаблонов, используемых для извлечения resource types в определениях 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 resource type или trait .

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

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

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

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

  • << resourcePath >> представляет весь URI (после

baseURI ) и ** << resourcePathName >> представляет часть URI, следующую за

крайний правый слеш (/), игнорируя фигурные скобки \ {}.

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

Например, учитывая ресурс /foos , << resourcePath >> будет оцениваться как «/foos», а << resourcePathName >> будет оцениваться как «foos».

Учитывая, что ресурс /foos/\ {fooId} , << resourcePath >> будет иметь значение «/foos/\ {fooId}», а << resourcePathName >> будет иметь значение «foos».

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

Определение resource type может также содержать пользовательские параметры.

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

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

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

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

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

  • ! singularize

  • ! pluralize

  • ! верхний регистр

  • ! lowercase

  • ! uppercamelcase

  • ! lowercamelcase

  • ! upperunderscorecase

  • ! lowerunderscorecase

  • ! upperhyphencase

  • ! lowerhyphencase

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

<< _ имя параметра _ | ! FunctionName __ >>

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

Например, для ресурса /foos , где << _ourcePathName _ >> имеет значение «foos»:

  • << _ resourcePathName | ! единственное число _ >> =⇒ «foo»

  • << _ resourcePathName | ! прописные буквы _ >> =⇒ «FOOS»

  • << _ resourcePathName | ! singularize | ! прописные буквы _ >> =⇒ «FOO»

И учитывая ресурс /bars/\ {barId} , где << _ resourcePathName _ >> оценивается как «bars»:

  • << _ resourcePathName | ! прописные буквы _ >> =⇒ «БАРЫ»

  • << _ resourcePathName | ! uppercamelcase _ >> =⇒ «Бар»

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

Давайте проведем рефакторинг приведенных выше определений ресурсов /foos и /bars , используя resource type для захвата общих свойств. Мы будем использовать зарезервированный параметр << resourcePathName >> и пользовательский параметр << typeName >> для представления используемого типа данных.

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

Вот определение resource type , представляющее коллекцию элементов:

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, поскольку наши типы данных являются просто заглавными, единственными версиями имен наших базовых ресурсов, мы могли бы применить функции к параметру << _ resourcePathName >> вместо введения пользовательского параметра << typeName _ >> , чтобы достичь того же результата для этой части API:

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

5.2 Заявка

Используя приведенное выше определение, включающее параметр << _ typeName >>, вы можете применить «коллекцию» resource type к ресурсам /foos и/ bars_ :

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

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

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 Определение

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

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 Заявка

И вот как мы применяем «элемент» resource type :

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

7. Черты

Принимая во внимание, что resource type используется для извлечения шаблонов из определений ресурсов, trait используется для извлечения шаблонов из определений методов, которые являются общими для всех ресурсов.

7.1 Параметры

Наряду с << _ resourcePath >> и << resourcePathName _ >> доступен еще один зарезервированный параметр для использования в определениях признаков:

<< _ methodName >> соответствует методу HTTP (GET, POST, PUT, DELETE и т. д.), для которого определено trait_ . Определенные пользователем параметры могут также появляться в определении черты и, когда они применяются, принимать значение ресурса, к которому они применяются.

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

Обратите внимание, что «элемент» resource type по-прежнему полон избыточностей.

Давайте посмотрим, как traits может помочь устранить их. Начнем с извлечения trait для любого метода, содержащего тело запроса:

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

Теперь давайте извлечем traits для методов, чьи обычные ответы содержат тела:

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

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

[source,javascript,gutter:,true]
hasNotFound:
  responses:
    404:
      body:
        application/json:
          type: Error
          example: !include examples/Error.json
====  **  7.3 Приложение **

Затем мы применяем это __trait__ к нашим __resource 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:

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

[source,javascript,gutter:,true]

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

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

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

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

В результате наш простой API с ресурсами только для двух сущностей был сокращен с 177 до чуть более 100 строк кода. Чтобы узнать больше о RAML __resource types__ и __traits__, посетите https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md#resource-types-and- черты[RAML.org 1.0 spec].

**  Полная реализация **  этого руководства может быть найдена в https://github.com/eugenp/tutorials/tree/master/raml/resource-types-and-traits[the проект github].

Вот наш окончательный RAML API в полном объеме:

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

Следующий "

https://www.baeldung.com/modular-raml-include-overlays-libraries-extensions [Modular RAML Использование включений, библиотек, наложений и расширений

  • «** Предыдущая