1. 简介
在我们之前关于 RAML(RESTful API Modeling Language)的第一篇和第二篇文章中,我们介绍了 RAML 的一些基本语法,包括数据类型和 JSON Schema 的使用,并展示了如何通过提取公共模式为 resource types 和 traits 来简化 RAML 定义。
本文将介绍 如何通过使用 includes、libraries、overlays 和 extensions 将 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
除了将所有的 types、resource types 或 traits 放在一个 include 文件中,你还可以使用一种特殊的 include 类型 —— typed fragments,将这些结构拆分成多个文件,每个文件定义一个 type、resource type 或 trait。
你也可以使用 typed fragments 来定义 用户文档项、命名示例、注解、libraries、overlays 和 extensions。我们将在后文介绍 overlays 和 extensions。
虽然不是必须的,但 typed fragment 文件的第一行通常是一个 RAML 片段标识符,格式如下:
#%RAML 1.0 <fragment-type>
例如,定义一个 trait 的 typed 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。
与普通 include 或 typed 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
Overlays 和 Extensions 是定义在外部文件中的模块,用于扩展 API。Overlay 用于扩展 API 的非行为部分,如描述、使用说明和用户文档;而 Extension 用于扩展或覆盖 API 的行为部分。
与 includes 不同,overlays 和 extensions 文件必须通过顶层的 masterRef 属性引用其主文件(可以是 RAML API 定义或其他 overlay/extension),并作用于该主文件。
5.1. 定义格式
Overlay 文件的第一行必须为:
#%RAML 1.0 Overlay
Extension 文件的第一行必须为:
#%RAML 1.0 Extension
5.2. 使用限制
使用一组 overlays 和/或 extensions 时,它们必须引用同一个主 RAML 文件。此外,RAML 处理工具通常要求根 RAML 文件及所有 overlay 和 extension 文件具有相同的文件扩展名(如 .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 模块化 traits、resource types 等元素
✅ 通过 overlays 和 extensions 扩展 API 的非行为和行为部分
如需深入了解 RAML 模块化技术,请参考 RAML 1.0 规范。
你可以在 GitHub 项目 中查看本文所用 API 定义的完整实现。