RAML 1.0 Final Candidate
We’re about to finalize the next version of RAML. Last month we published the result of many months of community feedback, development modeling, and API analysis, in which we figured out how a rather small number of changes in RAML 1.0 (relative to its predecessor, RAML 0.8) could result in dramatic improvements to the modeling capabilities. The list resolves some gaps, enhances capabilities, and maintains the simplicity of RAML. This month, that list will turn into RAML 1.0.
Why so few changes?
First, because RAML 0.8 has been working out quite well for an amazing spectrum of developers, companies, and entire enterprises. Quietly, behind the scenes and often with little fanfare, we’ve seen experimentation turn into adoption and finally into standardization. Much of it is behind the scenes, where the majority of APIs lie today. Some is in the open, as public APIs, though you can’t always tell it’s RAML that defines them and enables their lifecycle. Household names such as Cisco, Spotify, British Sky Broadcasting, SAP Hybris, Oracle’s Mobile Cloud Service, Anheuser Busch, and Sonic Drive In are some of the notable names, but there are major banks, retailers, healthcare companies, car companies, major industrial manufacturers, and of course numerous smaller companies across the spectrum who have adopted it internally. In many cases they’ve written their own tools and projects, as have independent developers – hundreds have been open-sourced so far on github. Despite its limitations as a first release, it’s working quite nicely where it matters: in the real world, in the API economy.
Which brings us to the second reason for so few changes: we’ve spent almost a year carefully studying the list of candidates that’s been accumulating for the next version of RAML. This was born out of experience with APIs both internal and external, simple and complex, in fast-moving and in highly-conservative environments. About 50 candidates for changes were created and published as github issues, out of the feedback of the first year of RAML 0.8, and then we went to work on them, along two tracks. In one track, we studied which of them reflected the major pains people in the RAML and swagger and API Blueprint communities were experiencing. Were there things all of us were missing? Were there lessons to be learned from the other specs, as they had been learning from RAML? What could we tell by selecting the most popular public APIs and expressing them thoroughly in RAML, via MuleSoft’s 100 APIs project, including schemas and functional tests? In a parallel track, a small team of developers was tasked with building a dynamic workbench in which we could all play with variations of a next-gen RAML and feel, concretely and viscerally, what it would be like to design or capture an API in various ways. To allow us to iterate rapidly, RAML itself had to be modeled precisely from the ground up, with all the tooling you’d expect for a productive API development and consumption lifecycle being auto-generated based on this model for RAML. Then we changed the model this way and that, and could immediately see the changes reflected in the tooling and hence in the API design and capture and usage experiences.
And that, in turn, explains the third reason for the simplicity of the RAML 1.0 proposal: because RAML’s primary design principle is to be as simple as possible to write and to read, while encouraging simplicity and reusability of design. This could not be a pile-on of features. We had to resist bloat at all costs, and had to find solutions that not only didn’t introduce complexity – they’d eliminate it where possible, while making the overall experience even more powerful and delightful.
The result was expressed in the short list of candidates published to github in September. In summary, RAML 1.0 brings:
Data types: a unified, streamlined, and powerful way to model data wherever it appears in an API. We’ve eliminated the special syntax of “named parameters” (which applied to URI parameters, query parameters, headers, and form parameters). We’ve eliminated the need to remember the intricacies of XML and JSON Schema – but you can still use XML and JSON Schema if you like, or use just sub-schemas from them, and embed them anywhere in a data type hierarchy. There is now one easy way to model data, as a set of reusable types, with a clean, easy, YAML-based syntax. An example of a RAML 1.0 API definition showing a few aspects of the new type system appears in the examples section at the bottom.
Improved examples: RAML finally allows documenting every part of the API with multiple examples, which can now be expressed in YAML, can be validated, and can be annotated to inject semantics about which example applies in which case.
Annotations: RAML is now extensible, allowing annotations to be declared and typed and then applied in various places in the RAML syntax, essentially as extra properties beyond those built into RAML.
Overlays: To add layers of metadata to an API without weighing down the interface itself, the non-structural aspects of the API – descriptions, annotations, examples, etc – can be separated from the main RAML file and kept in separate files that a RAML processor may overlay on the main RAML file without affecting its structure. This is useful for localizing human-readable documentation, or for vendor-specific and implementation-describing annotations that should not be in the contract between provider and consumer.
Libraries: To foster even more reuse and leverage of RAML’s data types, resource types, traits, and security schemes, sets of reusable assets may be contained in RAML 1.0 libraries, then imported into an API definition using the standard RAML !include
tag, with built-in namespacing.
Improved security schemes: OAuth support has been rounded out to fill the few gaps that were exposed; a new APIKey schema has been standardized for APIs that simply need tokens in the right places; new (custom) security schemes may now be strongly typed; and extension points with scripting hooks may be defined, attaching custom functionality where needed, e.g. to digitally sign requests.
Those are the more significant changes, but there are a number of smaller ones you can find in the github issues list. We hope you’ll agree it’s still streamlined, writeable as well as readable, and an even better construct for modeling practically-RESTful APIs.
If you like RAML 0.8, you can continue to use it, in most cases also with tools that expect RAML 1.0 with only minor changes; there are few breaking changes in 1.0. And if you like RAML 1.0 more, you can slowly evolve to it by starting to embrace its features. Of course, if you’re starting from scratch, RAML 1.0 will make your API modeling even more effective and satisfying.
As always, let’s continue the dialog in the forums, in github issues, and in the comments on this post. By the end of this month, the plan is to finalize and publish the RAML 1.0 spec… and that workbench mentioned above ;-)
– Uri, on behalf of the RAML Workgroup
Example 1: Data Types
#%RAML 1.0 title: A CRUD API for Users and Groups mediaType: application/json types: ############### # Common: ############### Email: pattern: "^\\w+(\\.\\w+)?@company.com" StaticGroupNums: description: Predefined user groups enum: [ 12, 26, 30, 31, 32 ] DynamicGroupNum: description: Dynamically-defined user groups pattern: "D\\-\\d+" RecordId: usage: An id of any record in the system type: number Record-base: usage: Pattern for any record in the system properties: id: type: RecordId required: true created: type: date required: true ############### # User: ############### User-base: usage: The base type for user records properties: firstname: required: true lastname: required: true User-new: usage: Data for creating a new user type: User-base properties: HRAuth: description: Authorization received from HR required: true User-update: usage: Changeable properties of an existing user properties: firstname: lastname: User: usage: A user in the system type: [ User-base, Record-base ] properties: emails: Email[] ############### # Group: ############### Group-base: usage: The base type for group records properties: name: required: true Group-new: usage: Data for creating a new group type: Group-base properties: ITAuth: description: Authorization received from IT required: true Group-update: usage: Changeable properties of an existing group properties: name: Group: usage: A group in the system type: [ Group-base, Record-base ] properties: groupnum: type: StaticGroupNums | DynamicGroupNum required: true resourceTypes: ############### # Collection: ############### collection: get: responses: 200: body: properties: total: type: number required: true members: <<typename>>[] post: body: type: <<typename-new>> responses: 201: headers: Location: required: true body: type: <<typename>> ############### # Member: ############### member: get: responses: 200: body: type: <<typename>> patch: body: type: <<typename-update>> responses: 200: body: type: <<typename>> delete: responses: 204: deleteOnlyMember: delete: responses: 204: ############### # API: ############### /users: description: All the users type: collection: typename: User typename-new: User-new /{userId}: description: A specific user uriParameters: userId: RecordId type: member: typename: User typename-update: User-update /groups: description: The groups to which this user belongs type: collection: typename: Group typename-new: RecordId /{groupId}: type: deleteOnlyMember delete: description: Remove this user from this group /groups: description: All the groups type: collection: typename: Group typename-new: Group-new /{groupId}: description: A specific group uriParameters: groupId: RecordId type: member: typename: Group typename-update: Group-update /users: description: The users belonging to this group type: collection: typename: User typename-new: User-new /{userId}: type: deleteOnlyMember delete: description: Remove this user from this group
Example 2: Data Types Using Libraries
#%RAML 1.0 title: A CRUD API for Users and Groups mediaType: application/json uses: - Common: !include libraries/types/common.raml - User: !include libraries/types/user.raml - Group: !include libraries/types/group.raml - CRUD: !include libraries/resourceTypes/crud.raml /users: description: All the users type: CRUD.collection: typename: User.full typename-new: User.new /{userId}: description: A specific user uriParameters: userId: Common.RecordId type: CRUD.member: typename: User.full typename-update: User.update /groups: description: The groups to which this user belongs type: CRUD.collection: typename: Group.full typename-new: Common.RecordId /{groupId}: type: CRUD.deleteOnlyMember delete: description: Remove this user from this group /groups: description: All the groups type: CRUD.collection: typename: Group.full typename-new: Group.new /{groupId}: description: A specific group uriParameters: groupId: Common.RecordId type: CRUD.member: typename: Group.full typename-update: Group.update /users: description: The users belonging to this group type: CRUD.collection: typename: User.full typename-new: User.new /{userId}: type: CRUD.deleteOnlyMember delete: description: Remove this user from this group
Example 3: libraries/resourceTypes/crud.raml
#%RAML 1.0 Library mediaType: application/json resourceTypes: ############### # Collection: ############### collection: get: responses: 200: body: properties: total: type: number required: true members: <<typename>>[] post: body: type: <<typename-new>> responses: 201: headers: Location: required: true body: type: <<typename>> ############### # Member: ############### member: get: responses: 200: body: type: <<typename>> patch: body: type: <<typename-update>> responses: 200: body: type: <<typename>> delete: responses: 204: deleteOnlyMember: delete: responses: 204:
Example 4: libraries/types/common.raml
#%RAML 1.0 Library types: Email: pattern: "^\\w+(\\.\\w+)?@company.com" StaticGroupNums: description: Predefined user groups enum: [ 12, 26, 30, 31, 32 ] DynamicGroupNum: description: Dynamically-defined user groups pattern: "D\\-\\d+" RecordId: usage: An id of any record in the system type: number Record-base: usage: Pattern for any record in the system properties: id: type: RecordId required: true created: type: date required: true
Example 5: libraries/types/user.raml
#%RAML 1.0 Library uses: - Common: !include common.raml types: base: usage: The base type for user records properties: firstname: required: true lastname: required: true new: usage: Data for creating a new user type: base properties: HRAuth: description: Authorization received from HR required: true update: usage: Changeable properties of an existing user properties: firstname: lastname: full: usage: A user in the system type: [ base, Common.Record-base ] properties: emails: Email[]
Example 6: libraries/types/group.raml
#%RAML 1.0 Library uses: - Common: !include common.raml types: base: usage: The base type for group records properties: name: required: true new: usage: Data for creating a new group type: base properties: ITAuth: description: Authorization received from IT required: true update: usage: Changeable properties of an existing group properties: name: full: usage: A group in the system type: [ base, Common.Record-base ] properties: groupnum: type: StaticGroupNums | DynamicGroupNum required: true