Re: More Thoughts on Links and Operation Subclasses

On Jan 30, 2014, at 9:20 AM, Mark Baker <distobj@acm.org> wrote:

> Our last discussion failed, but this discussion on Ryan's excellent
> points seems to have drilled down to a bite sized point that I'll
> offer a quick observation on...
> 
> On Tue, Jan 28, 2014 at 7:45 AM, Markus Lanthaler
> <markus.lanthaler@gmx.net> wrote:
>> Yeah, DELETE is very specific and it might look as if it doesn't have any value. There exist, however, APIs that archive resources that are being deleted. In such a case, I think it does make sense to qualify a DELETE with an ArchiveOperation or something similar. Basically the operation tells the client what kind of consequences it can expect if it invokes an operation.
> 
> What you describe there is not REST. With REST, the client only knows
> that a DELETE was performed upon receiving a 200 response, not that a
> DELETE was performed along with some other stuff that is indicated
> separately (either in-band in the message, or out-of-band via
> information received in a prior interaction). *Just* DELETE, as
> defined in the HTTP spec. The "other stuff" is totally permissible in
> REST, but is an *implementation detail* hidden behind the uniform
> interface.


Yes! That’s where I was trying to go with this. What Hyrda seems to be doing is specializing HTTP methods rather than simply instructing clients as to how they should format messages and what HTTP method to send that message to the server. It’s a subtle but critical difference. HTTP methods have no concept of return types or expected status codes and adding them to hydra:Operations is effectively changing the semantics of HTTP. 

HTML is one of the best examples of REST/Hypermedia out there today. Ignoring the fact that HTML isn’t the greatest format for machine-to-machine interaction, but it satisfies the constraints Mark is referring to (I will cover the description stuff later). The HTML Forms mechanism is the closest analog to what Hydra’s Operation classes are trying to do. HTML forms work because they do the following:

* Identify a control for collecting information <form id=“foo-form” action=“http://example.com/foo” method=“GET”></form>
* Identify the data points to be collected via input elements, and how to format the message to send
* An optional enctype value to describe how the data should be sent to server, but defaults to application/x-www-form-urlencoded
* Defines what HTTP method will be used 
* The URL that will accept the data
* An optional “accepts” attribute to declare the acceptable media types
* Lastly, there is a control to activate the form, either a Submit button or Javascript trigger.

Beyond that, HTML forms are pretty simple, yet remarkably successfully. Forms are also not extensible either, and that hasn’t been much of a problem for developers. There’s only a been one type of form element and it’s worked well over the years. There’s also no sensible way of specializing the HTTP methods in HTML. The forms mechanism merely tells the browser how to format the request and what HTTP method to use. It makes no effort to add semantics to the HTTP method. If were took the following Hydra operation:

{
  "@context": "http://www.w3.org/ns/hydra/context.jsonld",
  "@id": "/an-issue/12",
  "title": "An exemplary issue representation",
  "operations": [
    {
      "@type": "DeleteResourceOperation",
      "method": "DELETE"
    }
  ]
}

And describe it using an HTML form, we might end up with something like this:

<form id=“delete-issue
           action=“/an-issue/12”
           method=“DELETE”>
	<input type=“submit” id=“submitDelete”/>
</form>

If HTML forms supported the DELETE method, both options should successfully delete the resource at /an-issue/12 . Now if it was archived, I don’t know this fact at submit time, but rather I learn this from the response.

Obviously, this is too simplistic/contrived of an example. Let’s consider the following Hydra operation:

{
  "@context": "http://www.w3.org/ns/hydra/context.jsonld",
  "@id": "http://api.example.com/doc/#comments",
  "@type": "Link",
  "title": "Comments",
  "description": "A link to comments with an operation to create a new comment.",
  "supportedOperations": [
    {
      "@type": "CreateResourceOperation",
      "title": "Creates a new comment",
      "method": "POST",
      "expects": "http://api.example.com/doc/#Comment",
      "returns": "http://api.example.com/doc/#Comment",
      "statusCodes": [
        ... optional information about status codes that might be returned ...
      ]
    }
  ]
}

Now let’s take a look this same operation using HTML forms:

<form id=“comments-form" 
      action="http://api.example.com/comments"
      method="POST"
      enctype="application/x-www-form-urlencoded"
      returnMediaType=“application/ld+json"
      expectedReturnType="http://api.example.com/doc/#Comment"
      statusCodes=“201, 401, 400">
      
      <input type="text" id="name”/>
      ...
</form>

If we had to bake in the expectations about what the return types could be and what types of potential status code the form action might return, HTML forms would be no way near as successful as they are today. What would a browser do if the form return HTML? If the server responded with a 403? A 302 even? Baking the post conditions into the form, or operation, lends it self to all kinds of coupling and gets developers to avoid looking at the HTTP response headers to determine what action to determine if/how they should handle the response, but rather rely solely on what the ApiDocumentation asserts. Intermediaries can’t do anything with this information. At that point you’re in WSDL/WADL/Swagger territory. 

The fact that HTML doesn’t concern the browser with things like returns types and potential response codes is one of the things that makes the web work today. Beating the horse a little more, consider a checkout process whereby one of my payment options use PayPal and I’m sending data via POST to PayPal’s payment API and awaiting the response from PayPal. I’m going to send the client from my API in my domain, over to PayPal, where I don’t have control over PayPal’s API, more importantly their namespace or response codes. Using my ApiDocumentation to describe what I think is PayPal’s expected response types is recipe for failure. 

Now, without a doubt, HTML forms don’t do much to describe what the form does. HTML rely’s on the fact that a Human can parse the text on the page in order to determine the controls function. In Hydra, we’re trying to get at the machine parseable analog to descriptive text. I guess I was trying to get at is that Hyrda should put it’s effort in describing how to format the messages rather than through specialized Operations. 

Hyrda is close, but the two things that bug me the most are the declarations in the operation that tell the client what to expect. What could be better is taking something like this:

{
  "@context": "http://www.w3.org/ns/hydra/context.jsonld",
  "@id": "http://api.example.com/doc/#comments",
  "@type": "Link",
  "title": "Comments",
  "description": "A link to comments with an operation to create a new comment.",
  "supportedOperations": [
    {
      "@type": "CreateResourceOperation",
      "title": "Creates a new comment",
      "method": "POST",
      "expects": "http://api.example.com/doc/#Comment",
      "returns": "http://api.example.com/doc/#Comment",
      "statusCodes": [
        ... optional information about status codes that might be returned ...
      ]
    }
  ]
}

And turning it into this:

{
  "@context": "http://www.w3.org/ns/hydra/context.jsonld",
  "@id": "http://api.example.com/doc/#comments",
  "@type": "Link",
  "title": "Comments",
  "description": "A link to comments with an operation to create a new comment.",
  "supportedOperations": [
    {
      "@type": "CreateResourceOperation",
      "title": "Creates a new comment",
      "method": "POST",
      "expects": "http://api.example.com/doc/#Comment"
      ]
    }
  ]
}

We can give hints as to what’s in the response by leveraging Content-Type and Link headers [1]: 

HTTP/1.1 200 OK
...
Content-Type: application/ld+json
Link: <http://api.example.com/doc/#Comment>; rel="profile"; type="application/ld+json",
      <http://www.w3.org/ns/hydra/context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"
…

Or in the case that commenting went wrong and we have different response:

HTTP/1.1 400 Bad Request
...
Content-Type: application/ld+json
Link: <http://api.example.com/doc/#FormatError>; rel="profile"; type="application/ld+json",
      <http://www.w3.org/ns/hydra/context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"
…

Or, in the case I deal all too often with misconfigured Tomcat hosted APIs:

HTTP/1.1 400 Bad Request
...
Content-Type: text/html
...

Using Content-Type and Link headers, the client can determine if it can handle the response without having to parse the message body. You still have the same descriptive capabilities, but you’re now telling the client to look at the response rather than looking at the documentation. This is kind of the point that Stu Chartlon was getting at in his “The trouble with APIs” post [2].


Ryan-

[1] http://words.steveklabnik.com/the-profile-link-relation-and-you
[2] http://www.stucharlton.com/blog/archives/2011/06/the-trouble-with-apis.html



+-----------------------------------------------+
    Ryan J. McDonough
    http://damnhandy.com
    http://twitter.com/damnhandy

Received on Friday, 31 January 2014 15:50:08 UTC