-
Notifications
You must be signed in to change notification settings - Fork 9.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Hypermedia and linking between resources #688
Comments
Parent issue: #586 |
This is very similar to my proposal. My goal was that I was hoping to re-use existing constructs and introduce as few new concepts as possible. |
Yeah, there are clearly a lot of overlapping ideas. However, some of the other discussions are around parsing and interpreting links embedded in response object at runtime. The suggestion here aims to make HATEOAS/HAL redundant and slimming down response documents and have all the necessary information in the schema. |
Potential issues:
Pagination is of particular interest, as it is a concept notoriously ill-defined and lacking in Swagger. APIs often have custom ways of dealing with it. Two common approach stand out:
In case of the former, the response contains the page number and so it might be possible to derive the pagination links from those values, but in case of the latter, the pagination properties lie beyond the schema. |
I guess the Bitbucket API is an extreme example, where all the links are just predictably built from the statically visible API, and not changing depending on other information (like status of resources). For cases like those I guess having the links here helps just for exploring the API, but they are not really used productively, and then putting them into the swagger API description instead might be more useful. In general I think "get rid of HATEOAS by putting all the relevant links inside the OpenAPI schema" is not the way to go, we still need links, and a way to describe how to use them. (This means, this is orthogonal, not really an alternative to #577.) |
Nice proposal. I agree dynamic runtime links are a better option. That is central to our HATEOAS design: the API provides links (affordances) only if an operation is available to the (authenticated) client. Not allowed to delete a resource? Then the API does not return the I like the ability to refer to an operation by operation id. As noted in other comments, however, this alone does not allow for links to resources outside of the API. I suggest also a top-level link-strategy annotation in OAS which tells what link representation the API uses (HAL, Collection+JSON, Atom, etc.). This is probably just a hint, but tools like Swagger UI could use this to allow navigating the resources via parsing the links in the responses and matching them to the operations. This would promote HATEOAS understanding of hypermedia APIs, rather than low-level method+url tight coupling. See my comment on #577. (So I guess I disagree with @ePaul because I think embedding the link relations in OAS is the way to go: it reveals the intended use of the API - that is, the client should be looking for links and the embedded href values in the links, in order to act on responses, rather than hard-coded transitions based on static operation+path+parameters (which is all one can do with Swagger 2.0). Please elaborate on what Finally, some of the ideas in OAI/Overlay-Specification#34 might be useful. In our APIs, we repeat common sets of links in multiple places - for example, most simple resources have |
So first off, this is great, and I think a very good proposal. I have a couple thoughts that we can cover on the TDC call:
Overall this is very good. I'm looking forward to talking through open questions and getting it merged 👍 |
👍 to getting this merged! Couple of changes, that I'd like to suggest based on a similar proposal I made.
for e.g. In the current proposal "/2.0/repositories/{username}/{slug}/pullrequests/{pid}": {
"parameters": [
{
"name": "username",
"type": "string",
"in": "path"
},
{
"name": "slug",
"type": "string",
"in": "path"
},
{
"name": "pid",
"type": "string",
"in": "path"
}
],
"get": {
"operationId": "getPullRequestsById",
"responses": {
"200": {
"schema": {
"$ref": "#definitions/pullrequest"
}
}
}
}
},
...
"pullrequest": {
"links": [ <-- Make these top level
{
"rel": "self",
"href": {
"operation": "getPullRequestById",
"parameters": { //<-- eliminate redefinition of parameters ^ above ^
"username": "{repository/owner/username}",
"slug": "{repository/slug}",
"pid": "{id}"
}
}
},
I propose that we make links first class citizens of OAS. {
"links": {
"pullRequests": {
"method": "GET",
"contentType": "application/json",
"$pathRef": "/2.0/repositories/{username}/{slug}/pullrequests/{pid}"
}
},
"paths": {
"/2.0/repositories/{username}/{slug}/pullrequests/{pid}": {
"get": {
"description": "",
"operationId": "getPullRequestsById",
"parameters": [],
"responses": {}
}
}
},
"definitions": {
"pullrequest": {
....
"links": {
"self" { <-- link to global link definition
"$ref" : "#links/pullRequests"
},
"pullRequests": { <-- Inlined link definition
"method": "GET",
"contentType": "application/json",
"$pathRef": "/2.0/repositories/{username}/{slug}/pullrequests/{pid}"
},
....
}
}
} Note that here the pull request node has two links, one that links to the global link definitions and one that is inlined. One caveat with this is approach is that that its got refs all over the place for e.g. The other nice thing about this approach is also that we can link to definitions of link relations that may not be described by OAS even. For eg. a given link relationship might already be defined by IANA. |
Copying @mpnally. |
I think one of the challenges we have here is that I am seeing two completely distinct goals being discussed.
It is possible that we can find a solution that will support both scenarios, but it may be the case that the solution is more complex than necessary than it would be if only supporting one. |
For "metadata needed to follow a link", a proposal is in #577. |
@ePaul I re-read though your proposal for the notion of an interface. I realize now that your interface is effectively what I want to be able to do, I just wouldn't call it interface. I actually don't make the distinction you do between link relation type and interface. To use your example, I see |
@darrelmiller IMO I do think they compliment each other. I'm certainly not opposed to have the resources describe the relationships to other links, in fact its useful if we know that a resource always comes with certain relationships we already know about at design time. To sum up, what I was suggesting was:
The one caveat I can think of when describing resources relationships is, how does one define the resource model with its links and relationships in a media type agnostic way. For e.g. HAL refers to links as |
At Medallia we are using swagger to define our internal APIs with a spec-first approach (where the spec serves as a contract between FE and BE teams and both work on the implementations in parallel). We wanted to include hypermedia information in the spec for two main reasons:
Here's an example of the "x-links" extension we're using: user:
type: object
properties:
id:
type: string
username:
type: string
...
x-links:
role:
schema: '#/definitions/role' It mainly adds 1. and 2. in a custom extension (x-links) to an object definition. We considered adding a reference to a path operationId to be able to auto-generate the link but we decided it was taking the approach too far and not a requirement for now. So in our case we tie this information to the resource definition, mainly for the same reason @dilipkrish describes. We wanted to share our current solution for hypermedia support, similar to the one described here, to give you guys another data point of possible use cases. We really look forward to seeing native support in the next version. |
@erdody Interesting approach. At Apigee we have used a somewhat analogous approach in a project called Rapier. Rather than extend OpenAPI, Rapier just extends JSON Schema, and then generates OpenAPI from that. Our current generator uses the JSON schema definition unaltered in the generated OpenAPI, so it would be the author's responsibility to lay out the JSON Schema in HAL format (which is doable, if a bit tedious). Alternatively, you could extend the generator to produce a HAL layout (or Siren or other) from a simpler schema definition. |
@fehguy you mentioned "the TDC call" -- what/when is that? I don't recall seeing any announcements/emails or subscriptions for TDC calls |
to me, @mpnally's approach sounds reasonable: instead of trying to solve everything in OpenAPI, separate the resource-centric view of it, and add a hypermedia layer on top of it. in order to support proper hypermedia use cases, those links would have to be discoverable at runtime (instead of being static). if only static links are supported, that seems to be an edge case that doesn't support more ambitious affordance-based hypermedia API designs. |
See #742 |
Bitbucket's API uses HAL-style links to make things more discoverable. An example of this is how a repository object embeds a link to its list of pull request and a pull request object links to its comments and reviewers.
Embedding links has a number of disadvantages though.
Limitations of Custom Links in Schema Objects
As our API grows, so does the number of associated resources and by extension the number of links embedded in every response, which has a compute and bandwidth cost. The extensive list embedded in our repository object reflects this:
Another downside is that the purpose of these links is invisible to Swagger clients. This means that even though a repository links to its pull requests, a Swagger code generator would not automatically add a method
.getPullRequests()
to the generated Repository class.Adding Hyper Linking to Open API
Support to express relationships between resources could be added to Open API in a number of ways:
Option 1 would make it possible to declare the shape and purpose of the links in the Open API schema so that code generators could take advantage of them at runtime. However, it would not reduce the overhead of embedding.
Option 2 is akin to the approach followed by the JSON Hyper-Schema draft. It would eliminate the need to embed any link information at runtime, while still allowing code generators to be aware of the resource relationships and generate appropriate functions.
Suggested Hyper Link Declaration Scheme
While the concept of the expired JSON Hyper-Schema is good, it is aimed at use in JSON Schema and does not apply well to Open API schemas. Instead, we suggest an extension to Open API's SchemaObject to declare links by adding an element
links
that contains an array of relationships to resource endpoints (operations, in Swagger lingo).A link entry refers to an
operationId
and provides the values to use in the target path's URI template. The parameter values are templates that use curlies for variables. The variables themselves are JSON pointers to properties of the schema object itself.For instance, in Bitbucket, a
repository
object can link to its pull requests like this:This provides a code generator with sufficient information to add a
getPullRequests()
method to its generated repository class. This method would not take any arguments, as the URL is constructed on method invocation time using the repository object's properties.Since the link points to an operationId, it is clear what schemas are associated with it and so the code generator can use the correct return type in the method declaration. Similar for any POST/PUT request body object.
Example
Following is a more thorough model showing the relationships between repositories, users and pull requests in Bitbucket's API:
The text was updated successfully, but these errors were encountered: