Approach to effectively structure OpenAPI files

Any software development team that works on some web backend service implementing REST will need to document their architecture. OpenAPI is the go-to technology to document such an interface. As documentations grow, so does their readability sink. I’ve seen spec files easily exceeding 3000 LOC.

In this article, I want to present an approach to effectively structure OpenAPI documentations for higher scalability in terms of maintainability and readablity.

References in OpenAPI

The official OpenAPI specification extends JSON Schema while not supporting every aspect of it. In JSON Schema you can provide $ref attributes to reference external files or internal anchors. Refs “replace” the content of a section with the content of the ref. This is also possible in OpenAPI. We can use this feature to outsource aspects of our documentation that supports the ref attribute.

Refs are straightforward to use. A ref consists of a file reference, followed an anchor, similar to how URLs work.

$ref: 'path/to/file#/anchor

Refs are not valid at every place. The official OpenAPI specification clearly defines where references are supported. Below is an excerpt of the supported objects:

This is the feature which we can use to split our main file into smaller ones.

Using references to split files

Given we have the following specification:

mainfile.yaml
openapi: 3.0.2
---
components:
    schemas:
        User:
            type: object
            properties:
                userName:
                    type: string
                firstName:
                    type: string
                lastName:
                    type: string
                age:
                    type: number
paths:
    /users:
        get:
            tags:
                - users
            description: Gets all users
            responses:
                200:
                    description: OK
                    content:
                        application/json:
                            schema:
                                type: array
                                items:
                                    $ref: '#/components/schemas/User'

As we identified Path Item Object and Schema Object to be referenceable, we can now extract the User model and the /users path into their own files:

mainfile.yaml
openapi: 3.0.2
---
components:
    schemas:
        User:
            $ref: 'components/schemas/User.yaml#/User'
paths:
    /users:
        $ref: 'paths/users.yaml#/~1users'
components/schemas/User.yaml
User:
    type: object
    properties:
        userName:
            type: string
        firstName:
            type: string
        lastName:
            type: string
        age:
            type: number
paths/users.yaml
/users:
    get:
        tags:
            - users
        description: Gets all users
        responses:
            200:
                description: OK
                content:
                    application/json:
                        schema:
                            type: array
                            items:
                                $ref: '../mainfile.yaml#/components/schemas/User'

Note that in refs forward slashes / are escaped with ~1, so if we reference a path which includes slashes, we need to escape them. Referencing also allows any name to be used, so in paths/users.yaml our object could also have been declared with /any-name and then be referenced with ~1any-name. However, I consider this to be bad practice, since nobody will be able to understand synonymous names after the files grow.

Single source of truth

The attentive reader will also notice that we updated references inside paths/users.yaml: the response object and the request body content schema. I like to have one single file of truth, in our case the mainfile.yaml.

The idea is that the get all users path should return a list of Users. The path asks the mainfile, what the correct User is. Should the name of the User schema ever change, then we would have to only update it in the mainfile. We are loosening the coupling between the path file and the schema file.

Scalabality

Since the referenced Path Item Object gets pasted in the reference, we could add more HTTP methods, without editing the mainfile at all. Notice how in the file below, we added a new post method that expects a User and returns a User. This method will be included in the mainfile, without us touching it.

paths/users.yaml
 /users:
    get:
    ...
    post:
        tags:
            - users
        description: Creates a user
        requestBody:
            required: true
            content:
                application/json:
                    schema:
                        $ref: '../mainfile.yaml#/components/schemas/User'
        responses:
            201:
                description: OK
                content:
                    application/json:
                        schema:
                            $ref: '../mainfile.yaml#/components/schemas/User'

Extension support

Depending on the default editor you’re using, you might want to have support when writing split specification files.

I’m mainly working with the free editor Visual Studio Code which brings along a large ecosystem of extensions. You can combine the OpenAPI extension alongside my OpenAPI snippets extension. Utilize the main extension to validate the main file and to render your specification. Use my extension to validate external files and speed up your writing performance with the provided snippets. That way you sum up scalability, readability, robustness (because of validation) and speed of development for an optimal specification result.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.