リソースタイプと特性を使用してRAMLの冗長性を排除

1概要

リンク:/raml-restful-api-modeling-language-tutorial[RAMLチュートリアル]の記事では、 RESTful API Modeling Language を紹介し、 Foo という単一のエンティティに基づいて単純なAPI定義を作成しました。実世界のAPIを想像してみてください。そこには、すべて同じ、または類似のGET、POST、PUT、およびDELETE操作を持つ、いくつかのエンティティタイプのリソースがあります。あなたのAPIドキュメントがどのようにしてすぐに退屈で繰り返しになるかを見ることができます。

この記事では、RAMLで resource types および traits 機能を使用することで、共通セクションを抽出してパラメータ化することによってリソース定義とメソッド定義の冗長性を排除し、それによってAPI定義をより簡潔にしながらコピーアンドペーストエラーを排除する方法を示します。 。

2私たちのAPI

resource types traits の利点を実証するために、 Bar と呼ばれる2番目のエンティティタイプのリソースを追加して、元のAPIを拡張します。改訂された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パターンは、RAML定義内の リソースタイプ を抽出するために使用される最も一般的なパターンの1つです。

APIのいくつかのセクションを見てみましょう。

{空}[注:以下のコードスニペットで、3つのドット(…​)だけを含む行は、簡潔にするために一部の行がスキップされていることを示します。]

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

使用されているHTTPメソッドを含む、 /foos および /bars リソースのRAML定義を比較すると、それぞれのさまざまなプロパティ間にいくつかの冗長性があり、パターンが出現し始めています。

リソース定義またはメソッド定義のいずれかにパターンがある場合は常に、RAML resource type または trait を使用する機会があります。

** 4リソースタイプ

APIにあるパターンを実装するために、 resource types は、二重山括弧( [と] )で囲まれた予約済みのユーザー定義パラメーターを使用します。

4.1予約済みパラメータ

2つの予約済みパラメータをリソースタイプ定義で使用できます。

baseURI )、 ** [resourcePathName] は、次のURIの一部を表します。

波括弧\ {}を無視して、右端のスラッシュ(/)。

リソース定義内で処理されると、それらの値は定義されているリソースに基づいて計算されます。

たとえば、リソース /foos を指定すると、 [resourcePath] は "/foos"に評価され、 [resourcePathName] は "foos"に評価されます。

リソース /foos/\ {fooId} を考えると、 [resourcePath] は“/food/\ {food}”に評価され、 << resource PathName >> は“ foos”に評価されます。

4.2ユーザー定義パラメータ

resource type definitionには、ユーザー定義のパラメータも含めることができます。

値が定義されているリソースに基づいて動的に決定される予約済みパラメータとは異なり、ユーザ定義パラメータには、それを含むリソースタイプが使用されている場所には必ず値を割り当てる必要があります。

ユーザ定義パラメータは resource type 定義の始めに宣言することができますが、そうすることは必須ではなく一般的なやり方ではありません。

4.3パラメータ関数

パラメータがリソース定義で処理されるときにパラメータの拡張された値を変換するために、パラメータが使用される場合はいつでも、少数の便利なテキスト関数を使用できます。

パラメータ変換に利用できる関数は次のとおりです。

  • シングラリゼーション

  • 複数形

  • 大文字

  • スーパーカメラケース

  • 低文字ケース

  • アンダースコアコアケース

  • アンダースコアコアケース

  • !__英大文字大文字

  • ローワーハイフンケース

関数は、次の構文を使ってパラメータに適用されます。

<< _ parameterName | ! functionName _ >>

目的の変換を実行するために複数の関数を使用する必要がある場合は、各関数名をパイプ記号(「|」)で区切り、各関数を使用する前に感嘆符(!)を追加します。

たとえば、< resourcePathName __>>が“ foos”と評価されるリソース /foos__が与えられた場合

  • << _ resourcePathName | ! 単数形 _ >> =⇒“ foo”

  • << _ resourcePathName | ! 大文字 _ >> =⇒“ FOOS”

  • << _ resourcePathName | ! ! 大文字 _ >> =⇒“ FOO”

そして、リソース</bars/\ {barId} を考えると、<< resourcePathName __>>は“ bars”と評価されます。

  • << _ resourcePathName | ! 大文字 _ >> =⇒“ BARS”

  • << _ resourcePathName | ! 大文字大文字 _ >> =⇒「小節」

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では、データ型は大文字に変換された単なる基本リソース名の名前なので、ユーザー定義の<< _typeName >>パラメータを導入する代わりに、<< resourcePathName___ >>パラメータに関数を適用することができます。 APIのこの部分でも同じ結果が得られます。

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

5.2申し込み

<< __typeName >>パラメータを組み込んだ上記の定義を使用して、「collection」 resource type をリソース /foos および/ bars__に適用する方法は次のとおりです。

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

2つのリソース(この場合は 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メソッドを持ち、文字列“ foo”と“ bar”の出現以外は/ foos/\ {fooId} 定義と同じ(およびそれぞれの複数形および/または大文字の形式)。

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. 特色

resource type はリソース定義からパターンを抽出するために使用されますが、 trait はリソース間で共通のメソッド定義からパターンを抽出するために使用されます。

7.1パラメータ

<< _resourcePath >>および<< resourcePathName _ >>とともに、特性定義で使用するための追加の予約済みパラメーターが1つあります。

<< __methodName >>は、 trait__が定義されているHTTPメソッド(GET、POST、PUT、DELETEなど)に評価されます。ユーザー定義のパラメーターも特性定義内に現れることがあり、適用される場合は、それらが適用されているリソースの値を取ります。

7.2定義

“ item” 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>>[]----

最後に、404エラーレスポンスを返す可能性のあるメソッドの__trait__です。

[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__を適用することもできます。これは、リソースとメソッドの組み合わせが1つ以上の__traits__と一致するが、定義済みの__resource type__とは一致しない「1回限りの」シナリオで特に役立ちます。

[source,javascript,gutter:,true]

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

===  **  8結論**

このチュートリアルでは、RAML API定義から冗長性を大幅に減らす、場合によっては冗長性をなくす方法を説明しました。

最初に、私達は私達のリソースの冗長セクションを特定し、それらのパターンを認識し、そして__resource types__を抽出しました。その後、__traits__を抽出するために、リソース間で共通のメソッドについても同じことを行いました。それから、__traits__を__resource types__と、定義した__resource types__の1つと厳密には一致しない「一回限りの」リソースメソッドの組み合わせに適用することで、さらに冗長性を排除することができました。

その結果、2つのエンティティのみのリソースを持つ単純な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仕様]。

このチュートリアルの完全な実装はhttps://github.com/eugenp/tutorials/tree/master/raml/resource-types-and-traits[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 }]----

"

  • «** 前へ