1. 简介

在我们之前关于 RAML(RESTful API Modeling Language)的第一篇第二篇文章中,我们介绍了 RAML 的一些基本语法,包括数据类型和 JSON Schema 的使用,并展示了如何通过提取公共模式为 resource typestraits 来简化 RAML 定义。

本文将介绍 如何通过使用 includeslibrariesoverlaysextensions 将 RAML API 定义模块化

2. 我们的 API

为了方便说明,我们以 API 中涉及的一个实体类型 Foo 为例。

以下是构成我们 API 的资源:

  • GET /api/v1/foos
  • POST /api/v1/foos
  • GET /api/v1/foos/{fooId}
  • PUT /api/v1/foos/{fooId}
  • DELETE /api/v1/foos/{fooId}

3. Includes

Include 的作用是将 RAML 定义中复杂的属性值模块化,通过将其提取到外部文件中实现。

在第一篇文章中,我们曾简单提到过 includes,当时我们正在定义那些在 API 中重复出现的数据类型和示例。

3.1. 基本用法和语法

!include 标签接受一个参数:外部文件的路径,该文件中包含要引用的属性值。路径可以是绝对 URL,也可以是相对于根 RAML 文件的路径,或者相对于当前文件的路径。

/ 开头的路径表示相对于根 RAML 文件的路径,不以 / 开头的路径表示相对于当前文件的路径。

这意味着一个被 include 的文件中也可以包含其他的 !include 指令。

以下是 !include 标签的三种使用方式示例:

#%RAML 1.0
title: Baeldung Foo REST Services API
...
types: !include /types/allDataTypes.raml
resourceTypes: !include allResourceTypes.raml
traits: !include http://foo.com/docs/allTraits.raml

3.2. Typed Fragments

除了将所有的 typesresource typestraits 放在一个 include 文件中,你还可以使用一种特殊的 include 类型 —— typed fragments,将这些结构拆分成多个文件,每个文件定义一个 typeresource typetrait

你也可以使用 typed fragments 来定义 用户文档项命名示例注解librariesoverlaysextensions。我们将在后文介绍 overlaysextensions

虽然不是必须的,但 typed fragment 文件的第一行通常是一个 RAML 片段标识符,格式如下:

#%RAML 1.0 <fragment-type>

例如,定义一个 traittyped fragment 文件的第一行应为:

#%RAML 1.0 Trait

如果使用了片段标识符,则文件内容必须只包含对应片段类型的合法 RAML 语法。

我们先来看一下 API 中 traits 部分的原始定义:

traits:
  - hasRequestItem:
      body:
        application/json:
          type: <<typeName>>
  - hasResponseItem:
      responses:
          200:
            body:
              application/json:
                type: <<typeName>>
                example: !include examples/<<typeName>>.json

为了通过 typed fragments 模块化这部分内容,我们可以将其重写为:

traits:
  - hasRequestItem: !include traits/hasRequestItem.raml
  - hasResponseItem: !include traits/hasResponseItem.raml

然后编写 hasRequestItem.raml 文件:

#%RAML 1.0 Trait
body:
  application/json:
    type: <<typeName>>

hasResponseItem.raml 文件则如下:

#%RAML 1.0 Trait
responses:
    200:
      body:
        application/json:
          type: <<typeName>>
          example: !include /examples/<<typeName>>.json

4. Libraries

RAML libraries 可用于模块化任意数量和组合的 数据类型安全方案资源类型traits注解

4.1. 定义 Library

Library 通常定义在外部文件中,然后通过 include 引用,但也可以直接内联定义。外部文件中的 library 还可以引用其他 libraries

与普通 includetyped fragment 不同,外部文件中的 library 必须声明所定义的顶级元素名称。

我们将 traits 部分改写为一个 library 文件如下:

#%RAML 1.0 Library
# This is the file /libraries/traits.raml
usage: This library defines some basic traits
traits:
  hasRequestItem:
    usage: Use this trait for resources whose request body is a single item
    body:
      application/json:
        type: <<typeName>>
  hasResponseItem:
    usage: Use this trait for resources whose response body is a single item
    responses:
        200:
          body:
            application/json:
              type: <<typeName>>
              example: !include /examples/<<typeName>>.json

4.2. 应用 Library

通过顶层的 uses 属性来应用 libraries,其值是一个或多个对象,对象的属性名是 library 名称,属性值是 library 内容。

创建好 libraries 后,我们可以在根 RAML 文件中引用它们:

#%RAML 1.0
title: Baeldung Foo REST Services API
uses:
  mySecuritySchemes: !include libraries/security.raml
  myDataTypes: !include libraries/dataTypes.raml
  myResourceTypes: !include libraries/resourceTypes.raml
  myTraits: !include libraries/traits.raml

4.3. 引用 Library

引用 library 时,使用格式为:<library名称>.<元素名称>(例如数据类型、资源类型、trait 等)。

回想一下之前的文章,我们曾使用 traits 重构了 resource types。以下示例展示了如何将“item” resource type 定义为 library,并在其中引用 traits library,并使用 myTraits 作为前缀引用 trait:

#%RAML 1.0 Library
# This is the file /libraries/resourceTypes.raml
usage: This library defines the resource types for the API
uses:
  myTraits: !include traits.raml
resourceTypes:
  item:
    usage: Use this resourceType to represent any single item
    description: A single <<typeName>>
    get:
      description: Get a <<typeName>> by <<resourcePathName>>
      is: [ myTraits.hasResponseItem, myTraits.hasNotFound ]
    put:
      description: Update a <<typeName>> by <<resourcePathName>>
      is: [ myTraits.hasRequestItem, myTraits.hasResponseItem, myTraits.hasNotFound ]
    delete:
      description: Delete a <<typeName>> by <<resourcePathName>>
      is: [ myTraits.hasNotFound ]
      responses:
        204:

5. Overlays 和 Extensions

OverlaysExtensions 是定义在外部文件中的模块,用于扩展 API。Overlay 用于扩展 API 的非行为部分,如描述、使用说明和用户文档;而 Extension 用于扩展或覆盖 API 的行为部分。

includes 不同,overlaysextensions 文件必须通过顶层的 masterRef 属性引用其主文件(可以是 RAML API 定义或其他 overlay/extension),并作用于该主文件。

5.1. 定义格式

Overlay 文件的第一行必须为:

#%RAML 1.0 Overlay

Extension 文件的第一行必须为:

#%RAML 1.0 Extension

5.2. 使用限制

使用一组 overlays 和/或 extensions 时,它们必须引用同一个主 RAML 文件。此外,RAML 处理工具通常要求根 RAML 文件及所有 overlayextension 文件具有相同的文件扩展名(如 .raml)。

5.3. Overlay 的使用场景

Overlays 的设计初衷是将接口与实现分离,使得面向用户的部分可以独立变化,而 API 的核心行为保持稳定。

常见的使用场景包括提供多语言用户文档和描述性内容。例如:

#%RAML 1.0
title: API for REST Services used in the RAML tutorials on Baeldung.com
documentation:
  - title: Overview
  - content: |
      This document defines the interface for the REST services
      used in the popular RAML Tutorial series at Baeldung.com.
  - title: Copyright
  - content: Copyright 2016 by Baeldung.com. All rights reserved.

定义一个西班牙语的 overlay 如下:

#%RAML 1.0 Overlay
# File located at (archivo situado en):
# /overlays/es_ES/documentationItems.raml
masterRef: /api.raml
usage: |
  To provide user documentation and other descriptive text in Spanish
  (Para proporcionar la documentación del usuario y otro texto descriptivo
  en español)
title: |
  API para servicios REST utilizados en los tutoriales RAML
  en Baeldung.com
documentation:
  - title: Descripción general
  - content: |
      Este documento define la interfaz para los servicios REST
      utilizados en la popular serie de RAML Tutorial en Baeldung.com.
  - title: Derechos de autor
  - content: |
      Derechos de autor 2016 por Baeldung.com.
      Todos los derechos reservados.

另一个常见场景是外部化 annotation 元数据,这些元数据用于为 RAML 处理工具(如测试和监控工具)提供扩展点。

5.4. Extension 的使用场景

顾名思义,Extensions 用于扩展 API 的行为,例如新增资源或修改现有行为。类比于面向对象中的子类继承。

例如,定义一个 extension 用于为 API v2 添加新资源:

#%RAML 1.0 Extension
# File located at:
# /extensions/en_US/additionalResources.raml
masterRef: /api.raml
usage: This extension defines additional resources for version 2 of the API.
version: v2
/foos:
  /bar/{barId}:
    get:
      description: |
        Get the foo that is related to the bar having barId = {barId}
      typeName: Foo
      queryParameters:
        barId?: integer
        typeName: Foo
        is: [ hasResponseItem ]

其对应的西班牙语 overlay 如下:

#%RAML 1.0 Overlay
# Archivo situado en:
# /overlays/es_ES/additionalResources.raml
masterRef: /api.raml
usage: |
  Se trata de un español demasiado que describe los recursos adicionales
  para la versión 2 del API.
version: v2
/foos:
  /bar/{barId}:
    get:
      description: |
        Obtener el foo que se relaciona con el bar tomando barId = {barId}

⚠️ 注意:虽然我们在此例中使用 overlay 来实现语言切换,因为没有修改行为,但也可以将其定义为 extension,特别是当其目的是覆盖上层英文 extension 的属性时。

6. 总结

本文介绍了多种将 RAML API 定义模块化的方法:

✅ 使用 includes 将复杂属性值拆分为可复用的 typed fragments
✅ 使用 libraries 模块化 traitsresource types 等元素
✅ 通过 overlaysextensions 扩展 API 的非行为和行为部分

如需深入了解 RAML 模块化技术,请参考 RAML 1.0 规范

你可以在 GitHub 项目 中查看本文所用 API 定义的完整实现。


原始标题:Modular RAML: Includes, Libraries, Overlays, Extensions