Re: More Thoughts on Links and Operation Subclasses

On Feb 3, 2014, at 3:19 PM, Markus Lanthaler <markus.lanthaler@gmx.net> wrote:

> On Friday, January 31, 2014 4:50 PM, Ryan J. McDonough wrote:
>> 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 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.
> 
> Right. That's the contract. But AFAICT that doesn't mean that it is a
> violation of any of REST's constraints to give a client more information
> that it can use to choose which of the potential requests it might wanna
> make.
> 
> Do you disagree?


Yes. The draw of HTTP is the uniform interface. In between you and the origin server (i.e. the Hydra application) there could be multiple intermediaries that return to you something other than the specified hydra:statusCode or hydra:returns. The documentation is nice, but people tend to take it too literally. 

Look at some of the comments on the old Facebook API. You have people whining because they expected a 200 (Ok) and get an image but instead they got a 303 or 302 instead and they’re all perplexed. A good number of devs will take the documentation as fact rather than look at what’s coming back in the response. Intermediaries on the other hand will never look at your documentation and will always look at the headers and message body. Some would argue that the client is also an intermediary. 


> 
> IMO it doesn't matter whether a media type or a vocabulary a defines that.

Sure it does. If it makes the developer make false assumptions about what is being returned, it absolutely matters. 

> 
> 
>>> The "other stuff" is totally permissible in
>>> REST, but is an *implementation detail* hidden behind the uniform
>>> interface.
> 
> Agreed and intermediaries won't see this at all. And they don't have to..
> that's one of RESTs main strengths.
> 
> 
>> 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.
> 
> As I've already said a number of times, those are only hints. Even
> describing "how [clients] should format messages and what HTTP method to
> send that message to the server" is only a hint in the end, especially for
> POSTs which have very little semantics on their own.
> 
> 
>> 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.
> 
> You forget the prose that's usually around those forms which tell the
> incredible sophisticated agent (i.e., you) why he would want to submit that
> form. Have you ever tried to fill out a form on a Chinese website? Were the
> things you described above enough to know which form to choose? Sometimes
> you can infer that from the "data points it collects" but in most cases that
> won't work.

Please don’t complicate the argument with rich HTML5/DHTML/AJAX-y functionality. As you know, forms don’t require JavaScript. 20 Years ago, all we really had to deal with vanilla, JavaScript-less forms. At best, JavaScript did light-weight validation and nothing more. They worked fine and got us to where we are now. HTML forms function because they don’t make assumptions about what might be next. There’s no way Web Browsers could deal with expectations like Hydra has.This is where WADL failed and where Swagger also fails. 

> 
> 
>> 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
> 
> Because they typically rely on JavaScript and JSON if they need more
> functionality.

I’m talking at a base level. I’m trying really hard avoid the HTML5/DHTML/AJAX-y stuff that is an utter mess. You can whip up an HTML form sans JavaScript and it will collect the requested data. This is fact.

> Furthermore, things like nested structures etc. are
> represented visually instead of doing so at the data level.

True dat. And yes, this is where the HTML forms model falls down and something more expressive needs to happen. Like Hyrda.

> 
>> 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.
> 
> I completely agree with these points. Yet, developers usually want at least
> to know what they can expect to get back on success. If you don't know that
> it's incredible difficult to program against. Again, on the human Web that's
> comparable simple as there's a very smart agent processing the responses.
> 
> 
>> 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.
> 
> Then just leave it out :-) It's not required to specify these things.

You could, but there will be those who are expecting the response types because it’s in some Hyrda descriptors but not others. 

> 
> 
>> 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
> 
> Exactly. The solution I chose was to type operations as I felt that's the
> simplest solution that a lot of people will understand instinctively. How
> would you describe it instead?

I agree that people will instinctively get this, but HTTP doesn’t work this way. HTTP requests and have variable response types and Hydra is doing what WADL and Swagger do and suggest that there’s a single, fixed response types. 

Instead, I would recommend that developers work in an asynchronous, even-driven fashion and specify handlers to sense and react to the data in the response by looking at the headers to determine if the client can in fact respond to it. Without a doubt, the service needs to work within the constraints of the client. That is, response should likely be in JSON-LD and ideally confirm to some type hierarchy defined in the service descriptor. 

I guess it would help to create an example, huh? :)


> 
> 
>> 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"
>>      ]
>>    }
>>  ]
>> }
> 
> That's completely valid and in most cases you will achieve exactly the same.
> It doesn't, however, offer enough information to create a documentation
> similar to those of most current Web APIs. I found that an important use
> case to support.
> 
> 
>> 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].
> 
> I think the biggest problem is how this stuff is documented and the fact
> that people won't read it :-) It's not a contract. It's a hint. Clients have
> to interpret the response in any case. Some will be more tolerant, some will
> break when they don't get back what they expected. That's similar to how
> people often run into troubles when parts of a website get redesigned and
> "nothing works anymore" because it looks different or they have to take
> different paths.

What I was trying to get at with the profile link header is that if the data model is expressed up front and there’s sufficient documentation about what the types mean, a client can create a number of handlers that could react to different responses. If the model is good enough, the client could react better to responses that they didn’t expect at build time. 

Some developers will read documentation and create code generators that the illiterate ones will use. It is here that I feel Hydra will get into trouble.


> 
> I have troubles extracting something actionable from your mails. Would
> removing returns/statusCodes address your concerns?

Absolutely!

> If so, what does it really change?

It'll force developers to look at at what’s coming back in the response headers rather than what’s defined in the Hyrda description. One is a hint and the other is fact (i.e. what the server is sending back). By removing returns and status, you are now forcing developers to look in the right place: the HTTP response headers. 

I have development teams messing things up on a fairly frequent basic with WADL and Swagger (seeing a pattern here? :) ) due to the fact that they are expecting the server to return exactly what is specified in the descriptor and not taking into account they may have to deal with both a forward and reverse proxy in the mix. 

It could also be the wording. If this is just a hint, then perhaps instead of hydra:returns, which sounds a bit more committed than say perhaps something more like hyrdra:intimation or perhaps even hydra:anticipatedResponseType? 

I’m still not sold on status codes. For the most part, everyone is going to expect something in the 200 range. There’s too many exceptional codes to deal with in a format like Hydra to be practical. 

> IMO it will be just a matter of time till someone else mints
> a URL for these things in order to, again, be able to transform a Hydra
> description into a nicely formatted HTML documentation.

Sure, and that’s fine. AAA principle working at it’s finest! At least no one can point to Hydra Core and blame it for suggesting fixed response types :)

But seriously, I have to find some time to demo these ideas to better illustrate what I’m talking about. That might go a ways in clarifying these points.

> 
> 
>> [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
> 
> 
> --
> Markus Lanthaler
> @markuslanthaler
> 

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

Received on Tuesday, 4 February 2014 01:12:02 UTC