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
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.$ref
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:
- Path Item Object
- Schema Object inside components/schemas
- ResponseObject inside components/schemas
- Schema Object inside a mediaType Object (e.g. response of a path)
- …
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.