Modular RAML Using Includes, Libraries, Overlays and Extensions

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
.

Next »

« Previous

Leave a Reply

Your email address will not be published.