I just announced the Master Class of my "REST With Spring" Course:
1. Introduction
In our first two articles on RAML – the RESTful API Modeling Language – we introduced some basic syntax, including the use of data types and JSON schema, and we showed how to simplify a RAML definition by extracting common patterns into resource types and traits.
In this article, we show how you can break your RAML API definition into modules by making use of includes, libraries, overlays, and extensions.
2. Our API
For the purpose of this article, we shall focus on the portion of our API involving the entity type called Foo.
Here are the resources making up our 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
The purpose of an include is to modularize a complex property value in a RAML definition by placing the property value in an external file.
Our first article touched briefly on the use of includes when we were specifying data types and examples whose properties were being repeated inline throughout the API.
3.1. General Usage and Syntax
The !include tag takes a single argument: the location of the external file containing the property value. This location may be an absolute URL, a path relative to the root RAML file, or a path relative to the including file.
A location starting with a forward slash (/) indicates a path relative to the location of the root RAML file, and a location beginning without a slash is interpreted to be relative to the location of the including file.
The logical corollary to the latter is that an included file may itself contain other !include directives.
Here is an example showing all three uses of the !include tag:
#%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
Rather than placing all the types, resource types or traits in their own respective include files, you can also use special types of includes known as typed fragments to break each of these constructs into multiple include files, specifying a different file for each type, resource type or trait.
You can also use typed fragments to define user documentation items, named examples, annotations, libraries, overlays, and extensions. We will cover the use of overlays and extensions later in the article.
Although it is not required, the first line of an include file that is a typed fragment may be a RAML fragment identifier of the following format:
#%RAML 1.0 <fragment-type>
For example, the first line of a typed fragment file for a trait would be:
#%RAML 1.0 Trait
If a fragment identifier is used, then the contents of the file MUST contain only valid RAML for the type of fragment being specified.
Let’s look first at a portion of the traits section of our API:
traits: - hasRequestItem: body: application/json: type: <<typeName>> - hasResponseItem: responses: 200: body: application/json: type: <<typeName>> example: !include examples/<<typeName>>.json
In order to modularize this section using typed fragments, we first rewrite the traits section as follows:
traits: - hasRequestItem: !include traits/hasRequestItem.raml - hasResponseItem: !include traits/hasResponseItem.raml
We would then write the typed fragment file hasRequestItem.raml:
#%RAML 1.0 Trait body: application/json: type: <<typeName>>
The typed fragment file hasResponseItem.raml would look like this:
#%RAML 1.0 Trait responses: 200: body: application/json: type: <<typeName>> example: !include /examples/<<typeName>>.json
4. Libraries
RAML libraries may be used to modularize any number and combination of data types, security schemes, resource types, traits, and annotations.
4.1. Defining a Library
Although usually defined in an external file, which is then referenced as an include, a library may also be defined inline. A library contained in an external file can reference other libraries as well.
Unlike a regular include or typed fragment, a library contained in an external file must declare the top-level element names that are being defined.
Let’s rewrite our traits section as a library file:
#%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. Applying a Library
Libraries are applied via the top-level uses property, the value of which is one or more objects whose property names are the library names and whose property values make up the contents of the libraries.
Once we have created the libraries for our security schemes, data types, resource types, and traits, we can apply the libraries to the root RAML file:
#%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. Referencing a Library
A library is referenced by concatenating the library name, a dot (.), and the name of the element (e.g. data type, resource type, trait, etc) being referenced.
You may recall from our previous article how we refactored our resource types using the traits that we had defined. The following example shows how to rewrite our “item” resource type as a library, how to include the traits library file (shown above) within the new library, and how to reference the traits by prefixing the trait names with their library name qualifier (“myTraits“):
#%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 and Extensions
Overlays and extensions are modules defined in external files that are used to extend an API. An overlay is used to extend non-behavioral aspects of an API, such as descriptions, usage directions, and user documentation items, whereas an extension is used to extend or override behavioral aspects of the API.
Unlike includes, which are referenced by other RAML files to be applied as if they were being coded inline, all overlay and extension files must contain a reference (via the top-level masterRef property) to its master file, which can be either a valid RAML API definition or another overlay or extension file, to which they are to be applied.
5.1. Definition
The first line of an overlay or extension file must be formatted as follows:
RAML 1.0 Overlay
And the first line of an overlay file must be formatted similarly:
RAML 1.0 Extension
5.2. Usage Constraints
When using a set of overlays and/or extensions, all of them must refer to the same master RAML file. In addition, RAML processing tools usually expect the root RAML file and all overlay and extension files to have a common file extension (e.g. “.raml”).
5.3. Use Cases for Overlays
The motivation behind overlays is to provide a mechanism for separating interface from implementation, thus allowing the more human-oriented parts of a RAML definition to change or grow more frequently, while the core behavioral aspects of the API remain stable.
A common use case for overlays is to provide user documentation and other descriptive elements in multiple languages. Let’s rewrite the title of our API and add some user documentation items:
#%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.
Here is how we would define a Spanish language overlay for this section:
#%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.
Another common use case for overlays is to externalize annotation metadata, which essentially are a way of adding non-standard constructs to an API in order to provide hooks for RAML processors such as testing and monitoring tools.
5.4. Use Cases for Extensions
As you may infer from the name, extensions are used to extend an API by adding new behaviors and/or modifying existing behaviors of an API. An analogy from the object-oriented programming world would be a subclass extending a superclass, where the subclass can add new methods and/or override existing methods. An extension may also extend an API’s non-functional aspects.
An extension might be used, for example, to define additional resources that are exposed only to a select set of users, such as administrators or users having been assigned a particular role. An extension could also be used to add features for a newer version of an API.
Below is an extension that overrides the version of our API and adds resources that were unavailable in the previous version:
#%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 ]
And here is a Spanish-language overlay for that extension:
#%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}
It is worth noting here that although we used an overlay for the Spanish-language overrides in this example because it does not modify any behaviors of the API, we could just as easily have defined this module to be an extension. And it may be more appropriately defined as an extension, given that its purpose is to override properties found in the English-language extension above it.
6. Conclusion
In this tutorial, we have introduced several techniques to make a RAML API definition more modular by separating common constructs into external files.
First, we showed how the include feature in RAML can be used to refactor individual, complex property values into reusable external file modules known as typed fragments. Next, we demonstrated a way of using the include feature to externalize certain sets of elements into reusable libraries. Finally, we extended some behavioral and non-behavioral aspects of an API through the use of overlays and extensions.
To learn even more about RAML modularization techniques, please visit the RAML 1.0 spec.
You can view the full implementation of the API definition used for this tutorial in the github project.