Eliminate Redundancies in RAML with Resource Types and Traits – 用资源类型和特质消除RAML中的冗余现象

最后修改: 2016年 1月 15日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In our RAML tutorial article, we introduced the RESTful API Modeling Language and created a simple API definition based on a single entity called Foo. Now imagine a real-world API in which you have several entity-type resources, all having the same or similar GET, POST, PUT, and DELETE operations. You can see how your API documentation can quickly become tedious and repetitive.

在我们的RAML教程文章中,我们介绍了RESTful API建模语言,并基于一个名为Foo的单一实体创建了一个简单的API定义。现在想象一下现实世界中的API,你有几个实体类型的资源,都有相同或类似的GET、POST、PUT和DELETE操作。你可以看到你的API文档是如何迅速变得乏味和重复的。

In this article, we show how the use of the resource types and traits features in RAML can eliminate redundancies in resource and method definitions by extracting and parameterizing common sections, thereby eliminating copy-and-paste errors while making your API definitions more concise.

在这篇文章中,我们展示了如何使用RAML中的resource typestraits功能,通过提取和参数化公共部分来消除资源和方法定义中的冗余,从而消除复制和粘贴错误,同时使你的API定义更加简明。

2. Our API

2.我们的API

In order to demonstrate the benefits of resource types and traits, we will expand our original API by adding resources for a second entity type called Bar. Here are the resources that will make up our revised API:

为了展示资源类型特质的好处,我们将通过为第二个实体类型Bar添加资源来扩展我们的原始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}
  • 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. Recognizing Patterns

3.认识模式

As we read through the list of resources in our API, we begin to see some patterns emerge. For example, there is a pattern for the URIs and methods used to create, read, update, and delete single entities, and there is a pattern for the URIs and methods used to retrieve collections of entities. The collection and collection-item pattern is one of the more common patterns used to extract resource types in RAML definitions.

当我们阅读我们的API中的资源列表时,我们开始看到一些模式出现了。例如,用于创建、读取、更新和删除单个实体的URI和方法有一种模式,而用于检索实体集合的URI和方法也有一种模式。集合和集合-项目模式是RAML定义中用于提取资源类型的比较常见的模式之一。

Let’s look at a couple of sections of our API:

让我们看一下我们的API的几个部分。

[Note: In the code snippets below, a line containing only three dots (…) indicates that some lines are being skipped for brevity.]

[注意:在下面的代码片段中,只包含三个点(…)的行表示为简洁起见跳过了一些行。]

/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

When we compare the RAML definitions of the /foos and /bars resources, including the HTTP methods used, we can see several redundancies among the various properties of each, and we again see patterns begin to emerge.

当我们比较/foos/bars资源的RAML定义时,包括所使用的HTTP方法,我们可以看到每个资源的各种属性之间存在一些冗余,我们再次看到模式开始出现。

Wherever there is a pattern in either a resource or method definition, there is an opportunity to use a RAML resource type or trait.

只要在资源或方法定义中存在模式,就有机会使用RAML的资源类型特性

4. Resource Types

4.资源类型

In order to implement the patterns found in the API, resource types use reserved and user-defined parameters surrounded by double angle brackets (<< and >>).

为了实现API中的模式,资源类型使用保留的和用户定义的参数,并由双角括号(<<和>>)包围。

4.1. Reserved Parameters

4.1.保留参数

Two reserved parameters may be used in resource type definitions:

在资源类型定义中可以使用两个保留参数。

  • <<resourcePath>> represents the entire URI (following the baseURI), and
  • <<resourcePathName>> represents the part of the URI following the right-most forward slash (/), ignoring any braces { }.

When processed inside a resource definition, their values are calculated based on the resource being defined.

当在一个资源定义内处理时,它们的值是根据被定义的资源计算出来的。

Given the resource /foos, for example, <<resourcePath>> would evaluate to “/foos” and <<resourcePathName>> would evaluate to “foos”.

例如,鉴于资源/foos<<resourcePath>>将评估为”/foos”,<<resourcePathName>>将评估为 “foos”。

Given the resource /foos/{fooId}, <<resourcePath>> would evaluate to “/foos/{fooId}” and <<resourcePathName>> would evaluate to “foos”.

鉴于资源/foos/{fooId}<<resourcePath>>/em>将评估为”/foos/{fooId}”,<<resourcePathName>>/em>将评估为 “foos”。

4.2. User-Defined Parameters

4.2.用户定义的参数

A resource type definition may also contain user-defined parameters. Unlike the reserved parameters, whose values are determined dynamically based on the resource being defined, user-defined parameters must be assigned values wherever the resource type containing them is used, and those values do not change.

资源类型定义也可包含用户定义的参数。与保留参数不同的是,保留参数的值是根据被定义的资源动态确定的,而用户定义的参数必须在使用包含它们的资源类型时被分配值,并且这些值不会改变。

User-defined parameters may be declared at the beginning of a resource type definition, although doing so is not required and is not common practice, as the reader can usually figure out their intended usage given their names and the contexts in which they are used.

用户定义的参数可以在资源类型定义的开头宣布,尽管这样做不是必须的,也不是常见的做法,因为读者通常可以根据它们的名称和使用的上下文弄清它们的用途。

4.3. Parameter Functions

4.3.参数函数

A handful of useful text functions are available for use wherever a parameter is used in order to transform the expanded value of the parameter when it is processed in a resource definition.

在使用参数的地方都有一些有用的文本函数,以便在资源定义中处理参数时转换其扩展值。

Here are the functions available for parameter transformation:

以下是可用于参数转换的函数。

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

Functions are applied to a parameter using the following construct:

使用以下结构将函数应用于一个参数。

<<parameterName | !functionName>>

<parameterName | !functionName>>

If you need to use more than one function to achieve the desired transformation, you would separate each function name with the pipe symbol (“|”) and prepend an exclamation point (!) before each function used.

如果你需要使用一个以上的函数来实现所需的转换,你可以用管道符号(”|”)来分隔每个函数的名称,并在每个使用的函数前加一个感叹号(!)。

For example, given the resource /foos, where <<resourcePathName>> evaluates to “foos”:

例如,给定资源/foos,其中<resourcePathName>>评估为 “foos”。

  • <<resourcePathName | !singularize>> ==> “foo”
  • <<resourcePathName | !uppercase>> ==> “FOOS”
  • <<resourcePathName | !singularize | !uppercase>> ==> “FOO”

And given the resource /bars/{barId}, where <<resourcePathName>> evaluates to “bars”:

并给定资源/bars/{barId},其中<resourcePathName>>评估为 “bar”。

  • <<resourcePathName | !uppercase>> ==> “BARS”
  • <<resourcePathName | !uppercamelcase>> ==> “Bar”

5. Extracting a Resource Type for Collections

5.为集合提取资源类型

Let’s refactor the /foos and /bars resource definitions shown above, using a resource type to capture the common properties. We will use the reserved parameter <<resourcePathName>>, and the user-defined parameter <<typeName>> to represent the data type used.

让我们重构上面所示的/foos/bars资源定义,使用资源类型来捕获共同属性。我们将使用保留参数<<resourcePathName>>,以及用户定义的参数<<typeName>>来表示使用的数据类型。

5.1. Definition

5.1.定义

Here is a resource type definition representing a collection of items:

这里是一个资源类型定义,代表一个项目的集合。

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

Note that in our API, because our data types are merely capitalized, singular versions of our base resources’ names, we could have applied functions to the <<resourcePathName>> parameter, instead of introducing the user-defined <<typeName>> parameter, to achieve the same result for this portion of the API:

请注意,在我们的API中,由于我们的数据类型仅仅是基本资源名称的大写单数版本,我们可以对<resourcePathName>>参数应用函数,而不是引入用户定义的<typeName>>参数,以实现API这一部分的相同结果。

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

5.2. Application

5.2.应用[/span>

Using the above definition that incorporates the <<typeName>> parameter, here is how you would apply the “collection” resource type to the resources /foos and /bars:

使用上述包含<typeName>>参数的定义,以下是你如何将 “集合”资源类型应用于资源/foos和/bars

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

Notice that we are still able to incorporate the differences between the two resources — in this case, the queryParameters section — while still taking advantage of all that the resource type definition has to offer.

请注意,我们仍然能够纳入两种资源之间的差异–在这种情况下,queryParameters部分–同时仍然利用了resource type定义所提供的所有优势。

6. Extracting a Resource Type for Single Items of a Collection

6.为集合的单个项目提取资源类型

Let’s focus now on the portion of our API dealing with single items of a collection: the /foos/{fooId} and /bars/{barId} resources. Here is the code for/foos/{fooId}:

现在让我们关注一下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

The /bars/{barId} resource definition also has GET, PUT, and DELETE methods and is identical to the /foos/{fooId} definition, other than the occurrences of the strings “foo” and “bar” (and their respective pluralized and/or capitalized forms).

/bars/{barId}资源定义也有GET、PUT和DELETE方法,并且与/foos/{fooId}定义相同,只是出现了字符串 “foo “和 “bar”(以及它们各自的复数和/或大写形式)。

6.1. Definition

6.1.定义

Extracting the pattern we just identified, here is how we define a resource type for single items of a collection:

提取我们刚刚确定的模式,下面是我们如何为一个集合的单个项目定义一个资源类型

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

6.2.应用[/span>

And here is how we apply the “item” resource type:

这里是我们如何应用 “项目”资源类型

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

7. Traits

7.特质

Whereas a resource type is used to extract patterns from resource definitions, a trait is used to extract patterns from method definitions that are common across resources.

资源类型用于从资源定义中提取模式,而特质则用于从方法定义中提取跨资源通用的模式。

7.1. Parameters

7.1.参数[/span>

Along with <<resourcePath>> and <<resourcePathName>>, one additional reserved parameter is available for use in trait definitions: <<methodName>> evaluates to the HTTP method (GET, POST, PUT, DELETE, etc) for which the trait is defined. User-defined parameters may also appear within a trait definition, and where applied, take on the value of the resource in which they are being applied.

除了<resourcePath>>和<resourcePathName>>之外,还有一个额外的保留参数可用于特性定义中。<methodName>>评估为HTTP方法(GET、POST、PUT、DELETE等),该trait被定义。用户定义的参数也可以出现在特性定义中,并且在应用时,采用它们所应用的资源的值。

7.2. Definition

7.2.定义

Notice that the “item” resource type is still full of redundancies. Let’s see how traits can help eliminate them. We’ll start by extracting a trait for any method containing a request body:

请注意,”项目”资源类型仍然充满了冗余。让我们看看traits如何帮助消除它们。我们将首先为任何包含请求体的方法提取一个trait

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

Now let’s extract traits for methods whose normal responses contain bodies:

现在让我们为那些正常响应包含体的方法提取traits

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

Finally, here’s a trait for any method that could return a 404 error response:

最后,这里有一个trait用于任何可能返回404错误响应的方法。

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

7.3. Application

7.3.应用[/span>

We then apply this trait to our resource types:

然后我们将这个特质应用于我们的资源类型

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:

We can also apply traits to methods defined within resources. This is especially useful for “one-off” scenarios where a resource-method combination matches one or more traits but does not match any defined resource type:

我们还可以将traits应用于定义在资源中的方法。这对于 “一次性 “的情况特别有用,即资源-方法组合符合一个或多个traits,但不符合任何定义的resource type

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

8. Conclusion

8.结论

In this tutorial, we’ve shown how to significantly reduce or, in some cases, eliminate redundancies from a RAML API definition.

在本教程中,我们已经展示了如何大幅减少或在某些情况下消除RAML API定义中的冗余内容。

First, we identified the redundant sections of our resources, recognized their patterns, and extracted resource types. Then we did the same for the methods that were common across resources to extract traits. Then we were able to eliminate further redundancies by applying traits to our resource types and to “one-off” resource-method combinations that did not strictly match one of our defined resource types.

首先,我们确定了资源的冗余部分,识别其模式,并提取了资源类型。然后,我们对资源中常见的方法进行同样的处理,以提取traits。然后,我们能够通过将traits应用于我们的资源类型和 “一次性的 “资源-方法组合,来消除进一步的冗余,这些组合并不严格符合我们定义的资源类型

As a result, our simple API with resources for only two entities, was reduced from 177 to just over 100 lines of code. To learn more about RAML resource types and traits, visit the RAML.org 1.0 spec.

因此,我们的简单API中只有两个实体的资源,从177行减少到100多行代码。要了解有关RAML 资源类型属性的更多信息,请访问RAML.org 1.0规格

The full implementation of this tutorial can be found in the github project.

本教程的完整实现可以在github项目中找到。

Here is our final RAML API in its entirety:

下面是我们最终的RAML API的全部内容。

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