Allow documenting HATEOAS APIs (pathless operation / interface / URI class / operation type) #577

Open
ePaul opened this Issue Feb 22, 2016 · 42 comments

Projects

None yet

7 participants

@ePaul
ePaul commented Feb 22, 2016

TLDR

(added after some discussion)

  • "interfaces" (like path items, just without path parameters, and not linked to a path template)
  • a path item can refer to an interface (which is implemented), possibly adding path parameters to it
  • a string property (containing an URI) in a schema can refer to an interface (which means the consumer can know which operations can be used on this URI)

Background / Current Situation

I work at a company where our Restful API Guidelines prescribe both "document your API using OpenAPI" and "Use HATEOAS" ("Hypertext as the Engine of Application State", one of the core principles of REST).

Unfortunately both do not work together nicely:

  • Swagger/OpenAPI 2.0's model is "we have paths which can be distinguished in some way, and describe the operations on those paths" (including their data format).
  • The HATEOAS model is "The client gets a result which might contain links to other resources, and can decide to follow them, no matter which URI format they have".

Currently the intersection between both is "have links in your result, but just to URIs which are also described as paths in your Swagger definition, and describe in descriptions what kind of URIs you can expect".

Ideas

A core point of HATEOAS are media types and link relations. An API definition should mainly be a description of the media types and what operations they imply for links included in them. (Paraphrased from a blogpost of Roy Fielding.)

Media type definitions are analogous to what OpenAPI's definitions are currently, might even be an extension of that. For models with a property of type string, format uri we can then additionally refer to the URI class of that URI.

A URI class is defined analogously to the current path definitions, but with some name to be used in model definitions. The URI class defines what operations are possible on an URI, which parameters are allowed/needed (no path parameters, I guess), and what can be expected in return. (The URI class is not a property of the URI itself, but of its usage in a specific place. The same URI could appear with multiple URI classes.)

I'm not yet sure how link relations can come into this – I guess we might need a way to define the URI class depending on link relation or similar.

A client needs just some initial "bookmark URI" (so a single path definition might be fine), and then can take a link (with a given URI class) and use the operation behind the URI class to do what it wants to do. The format of the second URI doesn't matter anymore to the client – it can even have a different domain name than the bookmark one. But still everything can be strongly typed if wanted.

@ePaul
ePaul commented Feb 22, 2016

I guess this is a bit related to #576 (that discussion caused me to write this now down, though I had the ideas in me for a while), but this is much wider in scope.

@mpnally
mpnally commented Feb 23, 2016

See my comments on #576 for one possible approach to this.

@ePaul
ePaul commented Feb 23, 2016

@mpnally's comment on #576 gives some ideas on how this could be done, thanks.
I would prefer not to use YAML-specific features and stay within what can be expressed in JSON, too.

I can imagine something like this (modified from the linked comment):

uri-classes:
   Widget:
      get:
        ...
      put:
        ...
paths:
   /widget/{widget_id}:
      parameters:
         - name: widget_id
           in: path
           ...
      uri-class:
        $ref: '#/uri-classes/Widget'

definitions:
  Doohickey:
    properties:
      widget:
        description: hyperlink to a Widget
        format: uri
        type: string
        uri-class:
          $ref: '#/uri-classes/Widget'

(The path definition here isn't even needed if clients don't have to know the widget_id, and can reach that kind of uri from somewhere else. It might be needed for implementation help on the server side, but is not needed in the interface to be used by the client.)

I'm not sure if interface (or x-interface) is a good name for this "kind of resource to be find behind a URI", which I called "URI class", missing a better term.

Of course, using $ref here seems to imply an URI class can also be defined inline instead of referring to one defined in a special section.

@mpnally
mpnally commented Feb 24, 2016

Terminology aside, your use of $ref is better than using the YAML merge operator (<<). I used YAML merge because I needed a solution that was legal today in swagger 2.0 and so would work with existing tools.

@ePaul ePaul changed the title from Allow documenting HATEOAS APIs to Allow documenting HATEOAS APIs (pathless operations) Feb 24, 2016
@webron webron added the Sub Issue label Mar 1, 2016
@DavidBiesack
Contributor

The use of x-interfaces / x-interface in #576 seems to solve a different problem (that of several APIs or API subresources implementing an API contract/interface/pattern.)

I'm more interested in the OP question of defining and documenting an API via HATEOAS. We'd like to hide the paths in the API (i.e. in the UI) and encourage clients to consume links rather that hard-code the API to fixed paths. (The paths become more of an implementation detail. They still exist and could be revealed in the UI for reference / lookup, or presented in an alternate view.)

Thus, an API starts at the root and the client can discover the set of links to nested resources via compound keys that include a link relation (name), a verb (PUT, POST, etc.), a type (request and/or response media type), and a description. The definition of a link may also include a path but that is more of an implementation attribute. Thus, each path in an OAS can be associated with one or more contained resources

For example if the API has two primary resources, models and activities, the root
would contain

paths:
  /:
    resources:
      root:
        links:
          models: 
               rel: models
               method: GET
               uri: /models
               type: application/vnd.collection+json
               description: Retrieve a paginated list of models
          createModel:
               method: POST
               uri: /models
               type: application/vnd.example.model+json
               description: Create a new model resource in this collection
          activities: 
               method: GET
               uri: /activities
               type: application/vnd.collection+json
               description: Retrieve a paginated list of activities
          createActivity:
               method: POST
               uri: /models
               type: application/vnd.example.activity+json
               description: Create a new activity resource in this collection


  /models:
    resources:
      models:
        collectionItems:
          model:
             uri: /models/{modelId}
        links:      # these are links associated with the collection
          self:
            ...
          next:
            ...
          prev:
            ...
          first:
            ...
          last:
            ...
          create:
            ...
  /models/{modelId}:
    resources:
      model:
        links:
          self:
            method: GET
            type: application/vnd.example.model+json
            description: Fetch a representation of this model
          image:
            method: GET
            type: image/png
            description: Fetch a graphical representation of this model
          pdf:
            method: GET
            type: application/pdf
            description: Fetch a PDF representation of this model
          update:
            method: PUT
            type: application/vnd.example.model+json
            description: Replace the representation of this mdoel
          patch:
            method: PATCH
            type: application/vnd.example.model+json
            description: Update the representation of this mdoel with partial representation
          delete:
            method: DELETE
            description: Delete this model.
          analyze:
            method: POST
            type: application/vnd.example.activities+json
            uri: /activities?model={modelId}
            description: Create an analysis activity

So from the root, one can find models and activities (as resources), and also discover how to create activities, and from the collection, one can access an individual model. From a model, there are links for operations on that model (the uri is inherited from the current path context).

The UI could then navigate the API by exploring links (hiding the URI by default).

@ePaul
ePaul commented Mar 18, 2016

@DavidBiesack I'm not sure if I understand you right ... is the example code there a proposed syntax for a API specification, example output, or something else?

I guess resource (or rather resource-type) could be a better name than interface for what I want to describe in my API, though your description still seems a lot too path-centric for me.

@DavidBiesack
Contributor

It is a sketch for additions to OAS -- i.e. addition of a resources element within a path element to allow specifying what resource(s) are found at that path, and what links the API will return on GET operations to the collection or item resources.

Yes, It is path-centric in that I'm trying to integrate with the existing Swagger 2.0 schema of paths rather than completely undo it all. I'm trying to express resource-oriented architecture, not RPC style. again, the UI or other tools can hide the paths (optionally) and present a link/resource/discovery focused view of the API.

@ePaul
ePaul commented Mar 18, 2016

@DavidBiesack I would define the resource types separately, and then in the paths (and in content schema definitions for URIs) just refer to them.

The resource types in your case would be (if I understood right):

  • entry point (= bookmark URI)
    • allows retrieval (GET), gives links to list of models, model creation, list of activities, and activity creation.
  • list of models
    • allows retrieval (GET), gives list links to single models (maybe with those models already inlined)
  • model creation
    • (by POST) → results in a new single model resource being created.
  • single model
    • allows retrieval (GET) → gives JSON representation, includes links to PDF and PNG versions
    • allows update (PUT/PATCH)
    • allows removal (DELETE)
  • list of activities
    • allows retrieval (GET)
  • activity creation
    • (by POST) → results in a new single activity resource being created
  • single activity
    • allows retrieval (GET)
    • allows update (PUT/PATCH)
    • allows removal (DELETE)

I guess it would be possible to merge the "list of" with the "creation" resources, if that is supposed to always be the same URI.

Then we can map the paths to those resource types:

  • / → entry point
  • /models → list of models, model creation
  • /activities → list of activities, activity creation
  • /models/{modelId} → single model
  • /activities → activity creation, list of activities
  • /activities/{activityId} → single activity
  • /activities?model={modelId} → activity creation, list of activities

Except for the entry point this mapping doesn't need to be public in the API description (but it can, for legacy users/tools who don't really know how HATEOAS works). The actual links are then provided by actually invoking the resources containing the links, they are not hardcoded in the OpenAPI definition.

(My point of view is that the API definition is something stable, from which e.g. a client library can be generated and used in some program using it. The actual URIs in use can then change on the fly without any changes necessary to the client. This is something separate from the use of tools like Swagger UI, who are able to construct the UI and do the interactions on the fly from the API definition, and thus can work easily with changing definitions).

@DavidBiesack
Contributor

@ePaul yes, defining resources separately and using a $ref mechanism is fine. I just started with a sketch, it certainly needs refinement. For example, some APIs expose resources via multiple paths, so sharing the resource definitions (which includes links but may have more...) is important. For example, the sets of links would need to be mergable/inherited -- that is, it is important to be able to use composition to define a specific resource. (The creation operations/links are an example of that: we wish to expose the createModel link both at the API root and also in the "list of models" result.) We've been calling these aspects of the API 'traits' (a word we borrowed from RAML).

Regarding stability -- our view is that we also want clients to be coded to consume links at runtime, so that the client has as few hard-coded paths as possible (for example, the root). Start at the root, get the "models" or "createModel" link (where the API provides the uri) and invoke that operation from that information.

@mpnally
mpnally commented Mar 21, 2016

@DavidBiesack, the x-interfaces / x-interface design in #576 is trying to address the problem you want to solve. When you write:

definitions:
  Doohickey:
    properties:
      widget:
        description: hyperlink to a Widget
        format: uri
        type: string
        x-interface:
          $ref: '#/x-interfaces/Widget'

the x-interface declaration is saying that the 'widget' property of a Doohickey will hold an opaque URI—there is no path declaration for this URI and its format is not modeled is any other way. Further, this opaque URI supports the methods defined at #/x-interfaces/Widget. Of course, just because this design addresses your problem, doesn't mean you like it :) An advantage of this design is that it is quite incremental over the current Swagger 2.0—in fact it is legal Swagger 2.0 today. Note that the definition at /x-interfaces/Widget is also legal Swagger 2.0—it is the same Swagger that would normally be nested under a path. It can be included in the definition of a regular Swagger path, or, as above, used to define the interface that is supported by an opaque URL.
In practice, in our usage, we define one of these interfaces for each resource type, so the x-interface declaration is really saying "the opaque URL in the widget property of a Doohickey resource supports the method interface defined by Widget resources". We discovered these patterns while generating Swagger from another format that is custom-designed for hypermedia APIs.

@darrelmiller
Member

@mpnally I finally spent enough time reading your x-interfaces posts to fully understand what you are suggesting. My ignorance, not your lack of clarity.

When you commented on my suggestion to introduce "OperationTypes" in #563 I didn't understand why you suggested I would need to use YAML if my URL had parameters. Now I understand.

I consider the definition of the parameters to be part of the "OperationType" and therefore wouldn't need to be defined explicitly under the path. Where (and if) the parameters defined by the OperationType appear in the URL is the job of the path template. This allows me to use a simple $ref under the path to identify the operation type. The only missing piece of the puzzle is a standard place to store these definitions.

My use of the term OperationType was thinly veiled attempt to get Link relation support into Open API.
In the hypermedia world, a client needs to understand a link relation type to be able to activate that link. If the href uses a template, then the link relation type docs need to explain what goes in the parameters.

Personally, I think @ePaul 's term URI Class is synonymous with Link Relation Types. I realize that some people choose to use link relations very loosely but RFC 5988 does allow them to completely specify how an HTTP interaction will occur.

@mpnally
mpnally commented Apr 8, 2016

@darrelmiller I think parameters sometimes belong in the 'OperationType' itself and sometimes belong in the path. Here is an example. Suppose I am a person, and my URI is https://example.org/people/12345. I have a sibling, Jane. One way to address Jane is via her own URL, https://example.org/people/98765. Another way to address her is as my sibling, using https://example.org/people/12345/siblings/Jane. The second URL has a path parameter, {name}, in the last path segment. Since both these URLs identify the same resource (at least until Jane decides to change her name), you want them to have the same 'OperationType', so you want this particular parameter to be part of the path, not part of the OperationType. It is also easy to construct examples of parameters that do belong in the 'OperationType'—for example, query parameters that are valid on GET for all people.

'Interface' seems like a more intuitive name to me than 'OperationType' or 'URI class'. I think all 3 are trying to get at the same idea, unless I have perhaps misunderstood the other 2. Link Relation Types from RFC 5988 look to me more like predicate names, which would map more naturally to JSON properties. Many 'link relation types' may reference people, but there is a single 'interface' for all people across all those 'link relation types'.

@ePaul
ePaul commented Apr 8, 2016

@darrelmiller When I think of "Link relation type", it always depends on two resources, the linked one, and the linking one. My "URI class" depends only on the linked one.

Going with @mpnally's example, https://example.org/people/12345 would have a link with relation type "sibling" to https://example.org/people/12345/siblings/Jane, while that link would have the operation class/interface "Person".

@darrelmiller
Member

@ePaul The context resource of a link relation type may affect how the response is processed, but it is rare that it affects how the HTTP request to the target is actually made. Think rel="search", rel="stylesheet", rel="help". I can't think of any example where the mechanics of the request (parameters, payload, headers, response body) are affected by the identity of the context resource.

@mpnally
mpnally commented Apr 8, 2016

@darrelmiller I agree with what you said in this last post, Darrel, but I don't see how it relates to @ePaul 's comment, which also seems correct to me. I must be missing something.

@darrelmiller
Member

@mpnally With reference to your first comment, I think I see the challenge. I'm used to using hypermedia to define relationships between resources, not using URL structures to do it.
So, I would point to Jane's https://example.org/people/98765 from within your person resource. I follow the Issue-14 Range resolution of resources only having one URL that actually returns a 200. That prevents the issues of cache bloat and challenging cache invalidation scenarios.

Having resources that are identified using URI parameters that are not communicated using link relation types (or some kind of embedded form) creates out of band coupling. However, I recognize that approach is targeted at people who are aiming for very low coupling and high evolvability, which comes at a cost.

I need to think more about this from a more "coupling tolerant" perspective.

@ePaul
ePaul commented Apr 8, 2016

(Not related to the current discussion about link relations, I'll answer separately on this.)

A note about implementation

Assume we defined an API with pathless operations (or "Operation types", "URI classes", "Interfaces" – however those be called), which are referenced in URI properties in response models.

A client receiving such a response can then simply take the URI, and know which operations to do on it (from the Operation types). I can imagine a code generator putting an object implementing a generated interface/class in place of the URI, and when some of its methods are called, the correct HTTP operation is applied on the URI. (Of course, the server could do the same with a (foreign) URI it receives in some parameter property.)

For the server producing such URIs, and implementing the URI class, this is obviously not that easy – in contrast to the current model, where every path supported by the server has its "interface" defined in the API definition, now we might additionally have ones which are not. How can the HTTP-facing layer of the application know which implementation part to call for an incoming request?

One possible strategy would be to have a second (non-public) API definition file, which actually has all those paths defined, referring to the interface definitions in the public file for details. Then each one of those can be implemented separately. Yet we can update the server, add more paths implementing the same interfaces (or remove ones in the hope no client has stored the URI), and refer to the from the responses, without having to change anything in the public definition file, or any client.

A more dynamic strategy would be to use a database storing the association of path to interface. When we generate a new URI on our server (i.e. at latest when it is handed out in some response, but could happen earlier), we store that URI (or the path portion of it), its interface (or some implementation specific information also identifying the interface), and enough implementation information to produce the actual content.
This way similarly-looking URIs can have different interfaces, yet neither server nor client gets confused.

@darrelmiller
Member

@mpnally I'm just saying that when a client follows a link relation, the action it needs to take to make the request is not, in my experience, dependent on the context resource. It is also true that the action the client takes is not simply dependent on the type of the target resource. A "cancel" operation might apply to many different kinds of resources.

@darrelmiller
Member

@ePaul I think you are trying to take this much further that I was trying. I was trying to associate OpenAPI paths with a single "OperationType/UriClass/Interface". I think you are suggesting that there could be multiple and I think you are asking how the server can disambiguate which one was called. If that is the case, the problem there is that you are trying to layer a new set of semantics on top of HTTP, whereas, I was simply trying to package up some HTTP semantics and communicate them to the client.

@ePaul
ePaul commented Apr 8, 2016

@darrelmiller The difference between Interface/URI class/Operation type on the one hand, and link relation type on the other hand is more of a conceptual nature than on a technical one.

The link relation type describes to the client how the linked resource relates to the context resource. All your examples "help", "stylesheet", "search", even "cancel" have different meanings (for the client) depending on where you find those links. (And yes, I agree that the relation type is important too.)

But this is not what our "interface" is about. This is more about "In whichever relation I find a link declared as link to person, when I do a GET on it, I'll be able to get an object of this format back". This partially incorporates the media type concept (though an operation might offer multiple media types using content-negotiation).

To expand, there might be this interface Person, defined like this (extract):

  definitions:
    Person:
      type: object
      properties:
         name:
           type: string
         self:
           type: string
           format: uri
           interface:
              $ref: "#/interfaces/Person"
         siblings:
           type: array
           items:
             type: string
             format: uri
             relation: sibling
             interface:
                $ref: "#/interfaces/Person"
         partners:
           type: array
           items:
             type: string
             format: uri
             relation: partner
             interface:
                $ref: "#/interfaces/Person"
  interfaces:
    Person:
      get:
        responses:
          200:
            schema:
              $ref: '#/definitions/Person'

Doing a GET on a Person URI (e.g. https://example.org/people/12345), might result in this object:

{
  "name": "MP",
  "self": "https://example.org/people/12345",
  "siblings": [ "https://example.org/people/98765", "https://example.org/people/98764" ],
  "partners" : [ "https://example.org/people/56789" ]
}

The links in the first array here would have rel = sibling, and interface = Person, the ones in the second array would have rel = partner and interface = Person – all those would be already defined in the OpenAPI definition.

@ePaul
ePaul commented Apr 8, 2016

@darrelmiller My "implementation note essay" is not about the case that there are multiple interfaces for a path, but where in the API specification there is no path for an interface, i.e. the case where the association path → interface is hidden at API definition time (and only known at run time to the client – and at "implementation time" (which is after API definition time) to the server).

@mpnally
mpnally commented Apr 8, 2016

@darrelmiller If I were implementing the 'Person' API in the example, I would do exactly what you would do—I would always use Jane's https://example.org/people/98765 URL to link to Jane from other resources. However, I would also provide the URL https://example.org/people/12345/siblings/Jane as part of my API. This URL identifies the result of the query 'the person whose first_name is Jane from the siblings relationship of the Person identified by https://example.org/people/12345'. The URL template for these queries looks like {person_URL}/siblings/{name}. It would be more obvious that this URL identifies the result of a query if the query had been encoded in the query-string portion of the URL, but this particular query is not easy to express as a set of query parameters. The advantage of providing a query capability like this is that, if you have the URL for me, you can GET Jane directly without having to first GET me, GET the list of my siblings, locate the URL of Jane amongst them, and then GET Jane. I think that failure to provide a useful query capability like this is one of the primary reasons that hypermedia APIs have gotten a bad reputation amongst practical people who just need to get their apps written. A hypermedia API without query capability is a bit like a database that can only retrieve single records via a primary key—not very useful. One way of looking at the sort of non-hypermedia APIs that most people describe with OpenAPI is that they _only_ implement queries, and ignore the value of hyperlinks. IMO, _only_ implementing hyperlinks, and ignoring the importance of query is not an improvement, and in fact the popularity of the typical OpenAPI style of API shows that if you can only have one, query is probably the one to pick. Fortunately, you don't have to pick just one.

@darrelmiller
Member

@ePaul Regarding your "essay". Now I understand. OpenAPI is all about being explicit about what resources exist. If you only want to discover them at runtime (a la hypermedia) then OpenAPI quickly becomes mismatched.

I think I see what you are trying to achieve with the notion of interface. Personally, I'm quite happy to use a combination of link relations and media type in the content-type header do that description for me. I'm not really sure I am a fan of introducing another concept for clients and servers to couple on.

@darrelmiller
Member

@mpnally Yup. Got it. I would use http://example.org/person/12345/siblings?firstname=Jane Same thing, just slightly different URL structure. But to me this is a completely different resource than /person/{personid} It may be worth noting for clarification that I don't believe resource identifiers stop at the path portion of a URL. The query string components are part of the resource identifier too.

@mpnally
mpnally commented Apr 8, 2016

@darrelmiller Right. So, back on the original topic, what are the HTTP requests I can send to http://example.org/person/12345/siblings?firstname=Jane, and what responses can I expect? One response would be "I'm not going to tell you in advance—you will have to try it and see". Some hypermedia proponents make this response, but it doesn't make them popular with people trying to write practical software. A more helpful response would be "The same requests and responses that are supported by https://example.org/people/98765". I think this is the idea that 'interface' or 'URL class' is trying to let you say.

@darrelmiller
Member

@mpnally I use link relations to tell me what I will get back when media type is not sufficient. I have no need to couple resource identifiers or resource identifier templates to behaviour at compile time. I haven't found it an obstacle to producing "practical software".

@mpnally
mpnally commented Apr 8, 2016

@darrelmiller What are some examples of media types you use and how you use them?

@mpnally
mpnally commented Apr 8, 2016

@ePaul I think you and I are thinking about this problem very similarly @darrelmiller I'm not sure I've really grasped your POV yet. If we are abusing the intent of the issue/comment format, maybe we should talk offline

@darrelmiller
Member

@mpnally I think this is definitely the right place to consider all these different perspectives. The objective is try and ensure OpenAPI meets the needs of its users without adding complexity that might prevent people from being successful with it.

I had thought that we might be able to bridge the gap between people looking for static API contract descriptions and hypermedia "dynamic discovery" folks, but I think there may be too many shades of grey in-between to find a solution that makes everyone happy.

I've used a wide range of media types in projects that I have worked on. I've used HAL, Collection+Json, HTML, plain text, text/url-list, iCal, Atom, http-problem, json-home. I've created a few custom ones. Most of my thoughts on media types are documented in the chapters I wrote for Designing Evolvable Web APIs for O'Reilly. In that book we designed an Issue tracking media type and I've since done some work on a conference talk media type

@mpnally
mpnally commented Apr 9, 2016

@darrelmiller Suppose I had an application that modeled clubs, and a club looks like this:
{"name": "Sailing",
"president": "http://example.org/xxxxx",
"treasurer": "http://example.org/yyyyy",
"members": "http://example.org/wwwww",
"charter": "http://example.org/zzzzz"
}
As a programmer wishing to use your API, how in your model would I learn which requests I can send to each of the URLs? Assume president and treasurer are both [electronic records for] people, members is a list/collection that includes the URLs of other people, and charter is some other kind of resource.

@dilipkrish
Contributor

This seems very similar to #555 (just to add to all the myriad concepts ☝️ , that is already making my head spin on a friday evening!).

I proposed a similar solution in spirit here tho' not quite. The angle I'm coming from is subtly different tho'.

Just to state my assumptions:

  • OAS is (and has been in its swagger incarnation) a spec to describe service operations. Its has been very successful at that. Its very simple in that it can describe the different http methods and payloads (parameters) that a given url can support.
  • Any good hypermedia api (consequently the media type that it supports e.g. HAL, collection+json, siren etc) will likely have first class support for home urls (or entry urls), links relationships and possibly even be able to describe resource schemas.
  • We are trying to be DRY with regard to service descriptions (keep either the media type or OAS to be the source of truth) So we would ideally like to make OAS be complimentary to the media types used by the API. Some media types may need more help that others.

Having said that I'd like provide an example of what I was hoping to achieve. Given say a HAL based API. Lets take a person resource

GET http://myhost/person/1
{
  "_links" : {
    "self": { "href": "http://myhost/person/1" },
    "curies": {
         "name": "ex",
         "href": "http://example.com/rels/{rel}",
         "templated": true
    },
    "ex:edit" : { "href": "http://myhost/person/1" }
  },
  "firstname" : "Dave",
  "lastname" : "Matthews"
}

Now as of today we'd describe getting and editing a person like this in OAS

{
    "/person/{id}": {
        "get": {
            "description": "",
            "operationId": "getPersonById",
            "parameters": [
               ...
            ],
            "responses": {
               ...
            },
        },
        "put": {
            "description": "",
            "operationId": "updatePerson",
            "parameters": [
               ...
            ],
            "responses": {
               ...
            },
        },
    }
}

What is missing is that we do not know what _ex:edit_ means. we need a way to describe what that is. I would propose adding a first class concept in OAS called links that can de-reference link relations to bridge the semantic gap between the rel type; ex:edit the string and what that means to the client.

{
    "links": {
        "ex:edit": {
            "method": "PUT",
            "contentType": "application/json",
            "path": "#paths/person/{id}"
        }
    },
    "paths": {
        "/person/{id}": {
            "get": {
                "description": "",
                "operationId": "getPersonById",
                "parameters": [],
                "responses": {}
            }
        }
    }
}

Now this is really coming from a runtime discovery perspective. Some of the concepts above are from a design time perspective. May be we refine this issue into those concerns as seperate?

@darrelmiller
Member

@mpnally Based on the information you have given me, I don't have much choice but to treat the property names as if they are link relation types and provide documentation to the client developer explaining how to dereference a "president" link, a "treasurer" link, etc. That knowledge would need to be hard coded into the client. The link relation documentation may or may not specify the nature of the response formats. Without knowing what we are trying to do with the data, it is a bit difficult to say.

However, your example representation isn't really representative of the types of things I deal with. It is more a model of the data. I tend to build APIs based around use case scenarios, so the representations tend to end up much more task focused than data focused.

Having said all this, based on the discussions in some of these issues, I'm not sure how I build APIs is going to map into OpenAPI very effectively. That doesn't mean that we can't improve the current situation to address other people's requirements.

@darrelmiller
Member

@dilipkrish Couple of thoughts.

How would you handle a rel that can support multiple HTTP methods?
I'm not sure I follow what the path property in the link rel object is for.

@dilipkrish
Contributor

DISCLAIMER: the examples above are not exhaustive. apologies! It is just representative of the intent I'd like this story to represent.

How would you handle a rel that can support multiple HTTP methods?

I suspect you're referring to the case where you have a rel which is a noun? for e.g. address. And you could edit using a PUT and delete using a DELETE. I'd actually added this specific example with the method only so that it you could be as specific as the rel represents. DELETE would make no sense for an edit relationship.

for e.g. in the case of an address relationship, that doesn't imply an action I'd model it without the http method.

{
    "links": {
        "ex:edit": {
            "method": "PUT",
            "contentType": "application/json",
            "path": "#paths/person/{id}"
        },
        "ex:address": {
            "contentType": "application/json",
            "path": "#paths/address/{id}"
        }
    },
    "paths": {
        "/person/{id}": {
            "get": {
                "description": "",
                "operationId": "getPersonById",
                "parameters": [],
                "responses": {}
            }
        }
    }
}

I'm not sure I follow what the path property in the link rel object is for.

Maybe calling path, $pathRef might make that clearer. Its just reference to a path definition; in this case within the same document. It could be also a reference to a path definition in another OAS specification too.

@darrelmiller
Member

@dilipkrish Ok cool, I understand now. You might run into some issues where a link rel is actually used to refer to multiple different paths that all have the same interaction characteristics.

@dilipkrish
Contributor

True, but if you control the universe of link relations (non-standard rels) you'd be careful to avoid those collisions. After all, its there to make the job of the client easier when making these difficult life choices 😉

@ePaul
ePaul commented Apr 9, 2016

@darrelmiller:

@ePaul Regarding your "essay". Now I understand. OpenAPI is all about being
explicit about what resources exist. If you only want to discover them at runtime
(a la hypermedia) then OpenAPI quickly becomes mismatched.

Over the night I found an analogy to programming paradigms.


An HTTP API where all the URIs (or URI patterns) are known at API design time (and thus at the time the client app is developed) is like procedural programming – all functions/procedures you call are known at programming time.

(Without a static type system, we now have something like early BASIC.)

What OpenAPI 2.0 (and likely Swagger versions before, I'm not sure) are adding to this is a type system – we know the types of parameters and return values (and possibly exceptions).

(We get something like Pascal, or C without function references.)

A HATEOAS API has (in addition to static paths, or almost only) URIs contained in the responses, which a client can follow to call other functions. This brings us to functional programming (a function is returned) or object oriented programming (an object containing methods is returned) – the client can then call the functions/methods without their implementation being known at programming time.

(Without a (static) type system, this might be something like JavaScript or Lisp.)

My proposal here (interface/URI class/resource type) extends the type system to the returned methods/functions, so we don't only see "here is a function/method which can be called", but also can see what parameters that function/method takes, and what return values it would have.

(Now we have a statically typed object-oriented and/or functional programming language like Java, Ceylon, Scala, Haskell.)


Please note that my programming paradigm comparison does not mean that you need to use this paradigm to implement client and/or server. But the interface feature (+ typed URIs) enables both programmers and tools to use the URIs which are contained in the responses.

@fehguy wrote about this issue (in #640):

If the server is self-documenting and has no predefined paths, you don't need
OAS IMHO.

HATEOAS without a formal description what to expect might be enough for exploratory client development, but is nearly useless for programmers which want to create robust client programs. I like OpenAPI because it can define exactly the parameter and return types. I need that for a HATEOAS API too.

@darrelmiller:

I think I see what you are trying to achieve with the notion of interface. Personally, I'm quite happy to use a combination of link relations and media type in the content-type header do that description for me. I'm not really sure I am a fan of introducing another concept for clients and servers to couple on.

The problem is that the link relations and media types are not really represented in the API definition. There is nothing in OpenAPI linking a link relation type to a formal description of an operation, or a media type to a model schema (though this relates to #146).

@mpnally
mpnally commented Apr 9, 2016

@darrelmiller Perhaps you can point us to a concrete example of an API that is "really representative of the types of things [you] deal with". I think that would do more than anything to help us understand your POV.

@fehguy
Contributor
fehguy commented Apr 11, 2016

relinking to parent issues #574 #586

@ePaul ePaul changed the title from Allow documenting HATEOAS APIs (pathless operations) to Allow documenting HATEOAS APIs (pathless operation/Interface/URI class/OperationType) Apr 12, 2016
@ePaul ePaul changed the title from Allow documenting HATEOAS APIs (pathless operation/Interface/URI class/OperationType) to Allow documenting HATEOAS APIs (pathless operation / interface / URI class / operation type) Apr 12, 2016
@ePaul ePaul added a commit to ePaul/OpenAPI-Specification that referenced this issue Apr 12, 2016
@ePaul ePaul Issue #577: draft specification changes.
This simply reuses the $ref property of a path item – not sure if that is the right thing to do, or if we should use an `interface` property instead.
b3d688c
@ePaul
ePaul commented Apr 12, 2016

This would also allow documenting APIs which use HAL (Hypertext Application Language).

With HAL, the Person example from above could look like this:

{
   "_links": {
      "self" : { "href": "https://example.org/people/12345" },
      "siblings": [
          { "href": "https://example.org/people/98765" },
          { "href": "https://example.org/people/98764" }
      ],
      "partners" : [
         { "href": "https://example.org/people/56789" },
      ]
   },
   "name": "MP"
}

This could then be described in OpenAPI:

  definitions:
    Person:
      type: object
      properties:
         name:
           type: string
         _links:
           type: object
           properties:
             self:
               type: object
               properties:
                 href:
                   type: string
                   format: uri
                   interface:
                      $ref: "#/interfaces/Person"
             siblings:
               type: array
               items:
                 type: object
                 properties:
                   href:
                     type: string
                     format: uri
                     relation: sibling
                     interface:
                        $ref: "#/interfaces/Person"
             partners:
               type: array
               items:
                 type: object
                 properties:
                   href:
                     type: string
                     format: uri
                     relation: partner
                     interface:
                        $ref: "#/interfaces/Person"
  interfaces:
    Person:
      get:
        responses:
          200:
            schema:
              $ref: '#/definitions/Person'

(Of course, you could also describe the other properties of a HAL reference object here.)

@ePaul ePaul added a commit to ePaul/OpenAPI-Specification that referenced this issue Apr 19, 2016
@ePaul ePaul + Paŭlo Ebermann Issue #577: draft specification changes.
This simply reuses the $ref property of a path item – not sure if that is the right thing to do, or if we should use an `interface` property instead.
13fe6d9
@ePaul ePaul added a commit to ePaul/OpenAPI-Specification that referenced this issue Apr 20, 2016
@ePaul ePaul + Paŭlo Ebermann Issue #577: draft specification changes.
This simply reuses the $ref property of a path item – not sure if that is the right thing to do, or if we should use an `interface` property instead.
8e849d6
@ePaul ePaul pushed a commit to ePaul/OpenAPI-Specification that referenced this issue Apr 20, 2016
Paŭlo Ebermann Issue #577: put the interfaces object into the components object from #…
…633.

Also, rename it to "Interfaces Definitions Object".
19f8cfd
@ePaul
ePaul commented Apr 20, 2016

I guess each path item URI property can implement multiple interfaces, not just one. So instead of an interface property both should have an interfaces property containing a list of interfaces (or $refs to those).

@ePaul
ePaul commented May 10, 2016

I just found a different use case of interfaces: callbacks / web hooks, like the ones specified here.

The standard use case is subscribing to events. The client post a subscription to some subscription hook, which contains a callback URL. When the event occurs, the server will (acting as a client itself) send the event to that URL.

Currently there is no proper way of defining in OpenAPI what the event producer will send to the event consumer (and how the consumer is expected to answer).

This proposal would allow to define an interface (i.e. an operation not linked to any path), which can then be implemented by the consumer. The URI field in the subscription's parameter would be marked with a reference to this interface, so everything is "type-safe".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment