In the first article in this series, RESTful API Modeling Language 101, we saw how to design basic RAML and we had banking API as a use case. Here we will walk through some of the advanced RESTful API Modeling Language (RAML) to achieve code reusability and better code readability.
Several RESTFul API Modeling Language components, such as Traits, Resource Types, Data Types, and Security Schemes are used to enable stronger code reusability. Code reusability and readability are essential and allow us to write modular code and promote reuse while avoiding redundancy. Reusability and readability also enable us to design more efficient APIs and speed up the API design and creation processes.
Simplifying the API design and improving readability with Traits
A Trait, like a method, can provide method-level nodes such as description, headers, query string parameters, and responses. Methods that use one or more Traits inherit nodes of those Traits. Usage of Traits is optional but is highly encouraged since it simplifies the API design and improves readability. Traits should be created as separate files and included at the start of the API RAML file.
For this example, we will define error response for each method within resources, meaning we will keep writing code duplication for each method within resources. Instead, the error response is defined in Traits and reused across any method within resources.
#%RAML 1.0 Trait responses: 400: body: application/json: example: | {"errorMessage":"Bad Request", "errorCode":"400"} 404: body: application/json: example: | {"errorMessage":"Not Found", "errorCode":"404"} 401: body: application/json: example: | {"errorMessage":"Unauthorized", "errorCode":"401"} 500: body: application/json: example: | {"errorMessage":"Internal Server Error", "errorCode":"500"}
The Traits node within the API RAML is used to refer to external Traits.
traits: errorTraits: !include traits/errorTrait.raml
Traits can be mapped in the API RAML by referencing the assigned Trait.
/accounts: get: description: Fetch all the accounts for customers. is: [errorTraits] headers: customerId: required: true type: string example: "323232" maxLength: 20 responses: 200: body: application/json: example: | [{ "accountId": "32323232", "accountType": "Current", "accountName": "Mr. James John", "currency": "USD", "status": "Active" }]
Reduce API design complexity with Resource Types
Resource Types are a powerful way to reuse patterns across multiple resources and methods. The patterns provided by Resource Types encourage consistency and reduce complexity for API designers and consumers. Usage of Resource Types is optional but is highly encouraged since it simplifies the API design and improves readability. Resource Types should be created as separate files and included at the start of the API RAML file.
Resource Types are like a resource as they can specify the descriptions, methods, and parameters. Resources that use Resource Types can inherit its nodes, while Resource Types can use and inherit from other Resource Types.
Resource Types is a template used to define the descriptions, methods, and parameters that multiple resources can use without writing duplicate or repeating code.
In our use case, the pattern remains the same for each method within resources, but only examples change for each method. But with Resource Types, we can define the patterns for the HTTP method and can be re-used within root RAML across multiple methods within resources.
We can define Resource Types in two ways, and one way is to create RAML of type Resource Types as shown below directly. For the following example, we will create Resource Types where we will define patterns for the GET and POST methods which are reusable across various resources.
#%RAML 1.0 ResourceType get?: description: Fetch the <<resourcePathName>> responses: 200: body: application/json: example: | <<exampleReference1>> post?: description: Create the <<resourcePathName>> body: application/json: example: | <<exampleReference2>> responses: 200: body: application/json: example: | <<exampleReference3>>
The Resource Types can be referred to in root RAML using the syntax below.
resourceTypes: accountRespTypes: !include resourceTypes/resourceType.raml
Resource Types can be mapped in the API RAML by referencing the assigned type.
accounts: type: accountRespTypes: exampleReference1: !include examples/accounts.json exampleReference2: !include examples/accounts.json exampleReference3: !include examples/accountResp.json get: is: [errorTraits] post: /{accountId}: type: accountRespTypes: exampleReference1: !include examples/accounts.json get: /balances: type: accountRespTypes: exampleReference1: !include examples/balances.json get: /transactions: type: accountRespTypes: exampleReference1: !include examples/transactions.json get: /{transactionId}: type: accountRespTypes: exampleReference1: !include examples/transactions.json get:
To define the Resource Type via another method, we will create a library of type Resource Types where we will define patterns for the GET and POST methods which can be reused across various resources.
RAML libraries are pre-defined sets of Data Types, Resource Types, Traits, Security Schemes, and reusable assets — all in a namespaced environment. One of the main advantages of Libraries is modularization and they can enable reusability. It is a kind of “typed fragment,” and you can define multiple types in one library, and that can be referred from RAML. It is also possible to define a library as an inline.
#%RAML 1.0 Library resourceTypes: accountCollection: get?: description: Fetch the <<resourcePathName>> responses: 200: body: application/json: example: | <<exampleReference1>> post?: description: Create the <<resourcePathName>> body: application/json: example: | <<exampleReference2>> responses: 200: body: application/json: example: | <<exampleReference3>>
- “?” means when you inherit the library of type Resource Types for any resources in your root RAML, you don’t have to implement all the methods for that resource.
- <<exampleReference1>> or <<exampleReference2>> or <<exampleReference3>> is a parameter that can be passed from root RAML as response or request example may vary depending on resources.
- <<resourcePathName>> is the keyword to read resource name dynamically.
The library of Resource Types can be referred to in root RAML using the syntax below.
uses: accountResType: library/accountResourceTypes.raml
The library of Resource Types can be mapped in the API RAML by referencing the assigned type.
/accounts: type: {accountResType.accountCollection: {exampleReference1: !include examples/accounts.json,exampleReference2: !include examples/accounts.json,exampleReference3: !include examples/accountResp.json}} get: is: [errorTraits] post: /{accountId}: type: {accountResType.accountCollection: {exampleReference1: !include examples/accounts.json}} get: /balances: type: {accountResType.accountCollection: {exampleReference1: !include examples/balances.json}} get: /transactions: type: {accountResType.accountCollection: {exampleReference1: !include examples/transactions.json}} get: is: [tranTraits] /{transactionId}: type: {accountResType.accountCollection: {exampleReference1: !include examples/transactions.json}} get:
Configure security with Security Schemes
Each authentication pattern supported by the API must be expressed as an element of the Security Schemes node value. The Security Schemes should be created under a separate folder and included within the API RAML file.
Supported Security Schemes are defined below.
- OAuth 1.0: API authentication requires using OAuth 1.0
- OAuth 2.0: API authentication requires using OAuth 2.0
- Basic authentication: API authentication relies on using basic authentication, and will need to pass username and password checks.
- Digest authentication: API authentication relies on using digest authentication.
- Pass-through: Headers or query parameters are passed through to the API based on a defined mapping.
- x-{other}: API authentication relies on another authentication mechanism. You can define custom Security Schemes prefixed with “x-”. Please refer to the below example for custom Security Schemes.
#%RAML 1.0 SecurityScheme type: x-custom-security description: Custom Security Headers describedBy: headers: client_id: description: Client Id for Authentication type: string client_secret: description: Client Secret for Authentication type: string responses: 401: description: "Unauthorized Access"
The Security Schemes node within the API RAML refers to external Security Schemes.
securitySchemes: clientSecurity: !include security-schemes/banking-fragment.raml securedBy: - clientSecurity
Validate data against a type declaration with Data Types
RAML Data Types provide a concise and powerful way of describing the information within the API. Additionally, Data Types add rules for validating data against a type declaration. Data Types should be created as separate files and included at the start of the API RAML file.
#%RAML 1.0 DataType type: properties: firstName: type: string example: "James" minLength: 1 maxLength: 10 lastName: type: string example: "John" age: type: number example: 30 address: type: string example: "404 Oxford" city: type: string example: "Mumbai" postalCode: type: number example: 43432432 state: type: string example: "Maharashtra" country: type: string example: "India" minLength: 1 maxLength: 15
The types node within the API RAML refers to external Data Types.
types: custDataTypes: !include dataTypes/customerDataTypes.raml
Conclusion
In this RESTFul API Modeling Language 201 article, we have seen various components that enable API security, data validation, and better code reusability and readability. It is always recommended to use Traits and Resource Types to promote code reusability and modularization and help write less code.
To learn more about RESTful API Modeling Language, you can watch the recording session from the recent Surat MuleSoft meetup.
Jitendra Bafna is one of our MuleSoft Ambassadors. If you would like to connect, you can find him on LinkedIn.