Resource oriented #30
Replies: 14 comments 42 replies
-
The importance of URI paths is always an interesting one because something like OpenAPI puts significant importance on them - to the extent that this issue is talking about them as "a hierarchy of resources", whereas HTTP doesn't care at all - it's just an opaque string - and Hypermedia APIs mean that the client doesn't ever care even if the server does. Specifically, a Hypermedia API would mean that the URI path is just an opaque string to the client with no meaning at all, which runs counter to some of how things like OpenAPI work. That's not to say that there's no merit to this proposal. For systems that make heavy use of OpenAPI I think this would probably be a significant improvement. (It's exactly how RAML works, for example) but it's something to bear in mind. |
Beta Was this translation helpful? Give feedback.
-
I like the idea:
Then define
Now those two URI templates and their operations are no longer unrelated and show their connection to a resource, which would be an alternative to the nested paths proposed #23. |
Beta Was this translation helpful? Give feedback.
-
JSON Hyper-Schema in its last iteration before being tabled while we sorted out the Core and Validation specs was highly resource-oriented. A resource was defined by a schema for its representation, with a There was no overall equivalent to an OpenAPI document, although you could define a home document schema linking to all endpoints that would largely serve that purpose. That schema didn't necessarily even have to have a functional "self" link as long as it was clear how to get it. This idea was not unlike mnot's old HTTP JSON Home I-D, although I didn't encounter that until later. Each link would define which HTTP operations are allowed (by mimicking the HTTP For POST, a separate For user-submitted query string variables or any other user-submitted URI Template data, there was an Hyper-Schema's goal is/was to describe hypermedia resources that comply with HTTP semantics. It can describe those in a very compact manner; much more so than OpenAPI. However, the farther you went from that, the more difficult or awkward it became to use HyperSchema, while OpenAPI's verbosity stays constant. It's definitely a trade-off. |
Beta Was this translation helpful? Give feedback.
-
This is a bit of of a 'me too', but I would also love to see resources disconnected from the uri template. It feels like defining a list of resources at the top level, and a separate object that effectively routes uri templates to resources gives you everything you have today, and opens the door up to this being used in HATEOAS scenarios in the future (even if it doesn't do links/following/state transitions day 1). |
Beta Was this translation helpful? Give feedback.
-
I've built a few systems for docs and codegen on top of OpenAPI, and I've always had to build my own resource map on top of OpenAPI. For example, did this at Stripe to power the sidebar/structure of the API docs, and the organization of the client libraries. Having it baked-in would be great. If it helps, something I do today is roughly like this: resources:
foos:
models:
Foo: #/components/schemas/foo
methods:
create: post /foos
retrieve: get /foos/{id}
list: get /foos
resources:
bars:
methods:
create: post /foos/{id}/bars Some methods tend to live on the top level, of course, and some models tend to be "shared"/"common" (like an Address type). Today, people often use tags for this, but it's a hack and they don't allow nesting, attaching to schemas, etc. I don't currently have much opinion on how exactly this should look for OAI, but I'd be delighted to collaborate on concepts if the OpenAPI maintainers are interested in exploring this. |
Beta Was this translation helpful? Give feedback.
-
Hello! Wanted to bump this thread - we are yet another group who model our APIs as resource-oriented (http://aep.dev/), an in-development fork of https://google.aip.dev/ which is also resource-oriented. It would be great if there was a way to model this relationship. I think this goes beyond the relationship between a request / response and it's schema - that is primarily about defining the structure of objects to build structured objects off of it. How would we get some legs on this proposal? would it help if there was a specific PR or an issue filed? |
Beta Was this translation helpful? Give feedback.
-
The URI hierarchy is what defines the resource hierarchy. It makes no sense to define two separate hierarchies. It is true that the client application sees a URL as an opaque string, but the API designer absolutely does not and neither should the human consuming the API. Designing URLs is part of HTTP API design. I have written about this in more detail here https://apievolution.tavis.ca/posts/2023/the-majesty-of-trees/ and I have talked about it more here https://www.youtube.com/watch?v=9Fvt3QzMI34&list=PL3hoUDjLa7eQfYOEmuQNG8he3AeOeWaz8&index=11 @rattrayalex Why not build the docs and the client libraries off the URL hierarchy instead of inventing a new hierarchy? |
Beta Was this translation helpful? Give feedback.
-
I think one source of contention here might be that OpenAPI serves both client and server implementations. For server implementations, I agree that the hierarchical design of URLs is essential. The hierarchical URL captures naming, state, and dependencies; enforcing architecture and design upon the resources in an API that are hard to achieve otherwise. But when it comes to the client side, I strongly oppose the notion that clients need to know at compile time what every URL that may exist in the API will look like. The URL is not what identifies the content served from the URL. That's the role of the headers served with the URL, most importantly I'm fine with
To me, a I attempt to explain this and other resource-oriented design principles in my 2016 talk, The REST And Then Some. |
Beta Was this translation helpful? Give feedback.
-
Each endpoint also serves multiple content-types. Most notably, |
Beta Was this translation helpful? Give feedback.
-
Past resource-oriented API description case studyI've often mentioned the generic hypermedia client I wrote over a decade ago, which is still in use. For some reason, it never occurred to me to talk about the ReSchema Service Definition Format that drove it until a recent discussion with @rattrayalex ReSchema started as a documentation-only system, probably at almost exactly the same time Swagger started as an internal tool. Swagger 1.2 was published about the time we put ReSchema and sleepwalker into production. The CTO office director who came up with it was skeptical that such a thing could drive runtime behavior, or that a generic hypermedia-ish client was possible. I took this as a challenge and proved it could be done, which led to us updating the format to the structure I'll describe here. If you read the ReSchema docs that I'll link, do keep in mind that it started out as one thing and became another. Also keep in mind that it was originally internal, and then it got published as things were starting to go downhill so it was never proofread or edited from its original form. :-/ I'm not going to discuss the generic client there, but it worked - we did not use code generation, and we did not use higher-level SDKs internally. The generic client was more robust. I'll go through some notable points about the format, but if you don't care about that, skip down to "Thoughts for Moonwalk." TerminologyThis comment uses standard / OAS terminology rather than ReSchema's terminology, which I'll explain at the very end when linking to its docs. I'm using the terms link, relation type, context, and target as they are defined in RFC 8288 §2. ReSchema's TerminologyNote that ReSchema documentation calls the "self" link plus all operations The I'll stick with the standard terminology for the rest of this comment, but be aware of the difference if you read the ReSchema docs yourself. ResourcesEach resource is organized around:
This assumes a client that maintains a context resource URL, like the browser's address bar, but without any implied HTTP operation. Your current URL determines what operations and links are availabe. A session started by "bind"-ing to a resource, possibly by filling out a URI Template with information from elsewhere. Or starting on a non-templated resource. Binding did not trigger an HTTP request. LinksLike OAS Link Objects, ReSchema links specified how to fill out the target resource's URI Template based on the context resource's representation data and/or input. Unlike OAS Link Objects, there was no associated HTTP request. They identified a target resource instead of a target operation, and following a link just changed your current resource context. Link relation typesLinks are assumed to have a relation type, most notably:
We should have used RFC 6573's "collections" and "items" instead of "instances" and "full", but we'd started by trying to use hyper-schema and those relation names stuck. Links from individual fieldsLinks could be attached at the schema root, using the whole resource as the context, or they could be attached to individual fields. For example, each item in a collection representation had its own "full" link that took you to the individual resource for that specific item. Each resource that could be an item in a collection had an "instances" link pointing to the collection. A "full" link could be attached to any resource id field, e.g. an "author_id" field in the representation of a book. This use of "full" really could have been done with "self", but we did not understand that at the time (this is why there is no IANA-registered "full" link relation type). OperationsReSchema required that HTTP methods, response codes, headers, etc. be used in accordance with the relevant RFCs. POST, of course, is allowed to do anything, so it had additional support. GET, PUT, and DELETEStandard CRUDThe operation names "get", "set", and "delete" were special, and indicated standard GET, PUT, and DELETE usage, respectively. If we'd revised the format, we would have added shorthand for these because they were always specified exactly the same way. There was no need to explicitly associate the representation schema with requests and responses, as it was predictable based on the HTTP method and (were relevant) the Special casesAdditional named operations using GET, PUT, or DELETE could be added as special cases. For example, a named "get" that applies a specific use case's filter to a collection. Updating the ReSchema file would then update the filter without any need to change the code. It was also common for collections to have a "clear" operation that used PUT to empty the collection without deleting it. PATCHThe reason "set" was a special operation name and "put" was not is that we had the usual debate about whether to use PUT or PATCH. Or both. Or maybe even POST. We found that most devs thought of PATCH because they thought about changes in isolation: "I want to set field X to Y", so they just want to send that. But for actual use cases, they always already had, or needed to get, a current representation. It would have been more work to construct a patch than to just modify the representation and PUT it back. We told devs we'd implement PATCH support if it was ever needed, and it never was. If we had implemented it, we would have required a patch media type for the request and used that media type's rules plus the representation schema to determine the valid patch syntax. POSTPOST operations could use the "self" link like the others, but were also the only operations that could specify their own URI Template. This was done when the POST target was only relevant from a specific context. A POST operation of this sort was shorthand for declaring the POST target as a separate resource, linking to it in both directions, following the link, executing the POST, and following the reverse link back. Because of this, it had all of the data-to-URI Template mapping capabilities of links that did not perform an operation, plus the operation capabilities. Creating via collectionThe "create" operation was a special case that used POST from a collection context with a request body using the representation schema of the related item resource. If the response was a 201 with a As with the other special cases, this would have gotten a shorthand form if we'd revised the format. Arbitrary verbsCustom named POST operations were also used for anything that didn't fit the other methods. They could use arbitrary request / response schemas that did not need to match any regular resource. ErrorsErrors used the Error types were defined globally for the resource, and were defined flexibly enough that the usage for specific operations should be clear. I don't recall anyone running into any problems with this or asking for per-operation error response schemas. Schema Re-UseReSchema had a Single representation schemas for resourcesWe could get away with a single schema per resource representation because we defined conventions around GET/PUT/POST-to-create variations. If we were to use a concept of a single resource representation in Moonwalk, we'd need to work through more of the details here- there are several discussions on this topic already IIRC. MergesReSchema also supported My dislike for merging as anything other than a preprocessing step (e.g. Overlays, or just applying a patch of whatever sort) has been reinforced by having to implement OAS 3.1's keyword overrides for Reference Objects and the Path Item Object's fuzzy rules around adjacent keywords for Thoughts for MoonwalkThings that I think are worth keeping in mind:
There's something here relating to signatures that I can't quite put my finger on yet. I'll have to keep thinking on it. But really the biggest surprise for me going back through the ReSchema docs after so many years is that it fits the "Big Tent" principle quite well. It's opinionated, and in some cases restrictive (the error media type, for example). But the restrictions did not come from the resource-ness of it. As I noted, I'm pretty sure RPC is actually easier in ReSchema than OAS. I was not expecting that. |
Beta Was this translation helpful? Give feedback.
-
PATCH support When it comes to API description methods these are well understood CRUD methods until we get to PATCH where we require the ability to partially update a resource but how we do this has not been defined in any manner in OpenApi. From a Java Application Server point of view PATCH has been described and supported in Java EE/Jakarta EE via the JsonPatch object in javax.json and Jakarta.json packages. JsonPatch is an RFC6902 implementation where JSON entities are changed via a directing JSON message containing operation, path, and value. However, when we get to API description via OpenApi we do not want to describe PATCH request bodies in terms of RFC6902. A better solution would be to use the same schema definition entry for POST/PUT/PATCH, so the complete data element is understood in terms of the operation, however, when the media type is defined as “application/json-patch+json” (From RFC6902) then the runtime message format utilises RFC6902 message type formats. However, there must be caveats. When describing an API allowing new fields to be added to an object should be discouraged unless additionalPropties:true is defined in schema. Removing a field that is defined as required should similarly be discouraged. From an OpenApi specification point of view defining what PATCH is and how it should operate would make for better and more conformant API’s. I would appreciate hearing your thoughts in this one. Thanks and regards, |
Beta Was this translation helpful? Give feedback.
-
See also the following issues that were filed in the OAI/OpenAPI-Specification repo but are closed as they are better discussed here. Please feel free to copy over comments that are particularly relevant:
|
Beta Was this translation helpful? Give feedback.
-
They have been discuessed here, but there is no notion of URI templates in Roy Fielding's thesis defining REST. In a API using REpresentational State Transfer (REST), you have representattions of resources containing links for each action. A link in REST works a lot like a path in OAS, with the only real difference being that instead of building the URL from a template, the client gets the URL from a link in a resource representation. In a REST API, you still have at least one path, the entry point of the API (you could have several entry points). OAS could support describing REST APIs with an added info:
title: Sample Banking REST API
paths:
/:
get:
summary: API entry point
security: BearerAuth: []
responses:
'200':
description: A users's representation
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
properties:
accounts:
type: array
items:
type: Link
linkTarget: Account
links:
Account:
get:
responses:
'200':
content:
application/json:
schema:
type: object
properties:
balance:
type: number
transfer:
type: Link
linkTarget: Transfer
close:
type: Link
linkTarget: AccountClosing
AccountClosing:
post:
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
transferBalanceToIBAN:
type: string
responses:
'200':
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
'409':
content:
application/json:
schema:
type: object
properties:
error:
type: string
explanation:
type: string
Transfer:
post:
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
amount:
type: number
transferToIBAN:
type: string
responses:
'200':
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
'409':
content:
application/json:
schema:
type: object
properties:
error:
type: string
explanation:
type: string
|
Beta Was this translation helpful? Give feedback.
-
Designing an API usually starts by identifying resources. Operations usually indicate how to transition through the system. The paths/request/response are semantics for utilizing an operation as it pertains to a resource.
Therefore, it makes sense to structure the document as a
hierarchynetwork of resources and transitions. However, the path is hoisted all the way to the top which isn't natural in this paradigm.There is certainly a lot more to explore on this idea, but wanted to present it as an alternative solution to many issues this attempts to address.
Beta Was this translation helpful? Give feedback.
All reactions