1. 概要

RAMLチュートリアルの記事では、 RESTful APIモデリング言語を紹介し、Fooという単一のエンティティに基づいて単純なAPI定義を作成しました。 ここで、いくつかのエンティティタイプのリソースがあり、すべてが同じまたは類似のGET、POST、PUT、およびDELETE操作を持っている実際のAPIを想像してみてください。 APIドキュメントがすぐに面倒で反復的になる可能性があることがわかります。

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

2. 私たちのAPI

リソースタイプ特性の利点を示すために、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}&ownerName = {ownerName}
  • / api / v1/barsを取得
  • POST / api / v1 / bars
  • GET / api / v1 / birds / {barId}
  • PUT / api / v1 / birds / {barId}
  • / api / v1 / bars/{barId}を削除します
  • GET / api / v1 / birds / fooId / {fooId}

3. パターンの認識

APIのリソースのリストを読むと、いくつかのパターンが現れ始めます。 たとえば、単一のエンティティの作成、読み取り、更新、および削除に使用されるURIとメソッドのパターンがあり、エンティティのコレクションの取得に使用されるURIとメソッドのパターンがあります。 コレクションおよびコレクションアイテムパターンは、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リソースタイプまたは特性を使用する機会があります。

4. リソースタイプ

APIで見つかったパターンを実装するために、 リソースタイプダブルアングルブラケット(<<および>>)で囲まれた予約済みおよびユーザー定義のパラメーターを使用します。

4.1. 予約済みパラメータ

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

  • < >> URI全体を表します( baseURI )、 と
  • < >> 中括弧{}を無視して、右端のスラッシュ(/)に続くURIの部分を表します。

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

与えられたリソース / foos 、 例えば、 < >> 「/foos」と評価され、 < >> 「foos」と評価されます。

与えられたリソース / foos / {fooId} < >> 「/foos/{fooId}」と評価され、 < >> 「foos」と評価されます。

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

リソースタイプ定義には、ユーザー定義のパラメーターを含めることもできます。 定義されているリソースに基づいて値が動的に決定される予約済みパラメーターとは異なり、ユーザー定義パラメーターには、それらを含むリソースタイプが使用される場合は常に値を割り当てる必要があり、それらの値は変更されません。

ユーザー定義のパラメーターは、リソースタイプ定義の最初に宣言できますが、これは必須ではなく、一般的な方法ではありません。読者は通常、名前とコンテキストを指定して、使用目的を理解できるためです。それらが使用される場所。

4.3. パラメータ関数

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

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

  • singularize
  • 複数化
  • 大文字
  • 小文字
  • アッパーキャメルケース
  • ロワーキャメルケース
  • アッパーアンダースコアケース
  • lowerunderscorecase
  • 大文字
  • 小文字

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

<< parameterName | ! functionName >>

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

たとえば、与えられたリソース / foos 、ここで<< resourcePathName >>「foos」と評価されます:

  • << resourcePathName | ! 特異化 >>==>「foo」
  • << resourcePathName | ! 大文字 >>==>「FOOS」
  • << resourcePathName | ! 特異化 | ! 大文字 >>==>「FOO」

そして、与えられたリソース / bars / {barId} 、ここで<< resourcePathName >>「バー」に評価されます:

  • << resourcePathName | ! 大文字 >>==>「BARS」
  • << resourcePathName | ! アッパーキャメルケース >>==>「バー」

5. コレクションのリソースタイプの抽出

リソースタイプを使用して共通のプロパティをキャプチャし、上記の /foosおよび/barsリソース定義をリファクタリングしてみましょう。 予約済みのパラメータを使用します < >> 、およびユーザー定義パラメーター < >> 使用されるデータ型を表します。

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では、データ型は単に大文字の、基本リソースの名前の単数バージョンであるため、<<に関数を適用できた可能性があることに注意してください。 resourcePathName >>パラメータ、ユーザー定義の代わりに<< typeName >>パラメータ、APIのこの部分で同じ結果を達成するには:

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

5.2. 応用

<<を組み込んだ上記の定義を使用する typeName >>パラメータ、これが「コレクション」の適用方法ですリソースタイプリソースに / foos と / バー

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

リソースタイプ定義が提供するすべての機能を活用しながら、2つのリソース(この場合は 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. 応用

そして、これが「アイテム」リソースタイプを適用する方法です。

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

7. 特性

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

7.1. パラメーター

<<と一緒に resourcePath >>と<< resourcePathName >>、特性定義で使用できる追加の予約済みパラメーターが1つあります:<< methodName >> HTTPメソッド(GET、POST、PUT、DELETEなど)に評価されます。 特性が定義されています。 ユーザー定義のパラメーターは、特性定義内に表示される場合もあり、適用される場合は、それらが適用されているリソースの値を引き継ぎます。

7.2. 意味

「アイテム」リソースタイプはまだ冗長性でいっぱいであることに注意してください。 特性がそれらを排除するのにどのように役立つかを見てみましょう。 まず、リクエスト本文を含むメソッドのtraitを抽出します。

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

次に、通常の応答に本文が含まれているメソッドの特性を抽出しましょう。

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

最後に、404エラー応答を返す可能性のあるメソッドのtraitを次に示します。

  hasNotFound:
    responses:
      404:
        body:
          application/json:
            type: Error
            example: !include examples/Error.json

7.3. 応用

次に、このtraitリソースタイプに適用します。

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:

リソース内で定義されたメソッドに特性を適用することもできます。 これは、リソースとメソッドの組み合わせが1つ以上の特性と一致するが、定義されたリソースタイプとは一致しない「1回限りの」シナリオで特に役立ちます。

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

8. 結論

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

まず、リソースの冗長なセクションを特定し、それらのパターンを認識して、リソースタイプを抽出しました。 次に、特性を抽出するために、リソース全体で共通のメソッドに対して同じことを行いました。 次に、 traits リソースタイプと、定義済みのの1つと厳密に一致しない「1回限りの」リソースとメソッドの組み合わせに適用することで、さらなる冗長性を排除することができました。リソースタイプ

その結果、2つのエンティティのみのリソースを使用する単純なAPIが、177行から100行をわずかに超えるコードに削減されました。 RAML リソースタイプおよび特性の詳細については、RAML.org1.0仕様にアクセスしてください。

このチュートリアルの完全な実装は、githubプロジェクトにあります。

これが最終的なRAMLAPI全体です。

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