mamund / hal-forms

backward-compatible extension for HAL to support dynamic forms at runtime.
MIT License
30 stars 18 forks source link

Open spec up to other templates - don't confine to "default" #24

Closed gregturn closed 3 years ago

gregturn commented 6 years ago
key - The unique identifier for this template object. This is a REQUIRED element. For this release, the only valid value for key is "default". If this element is missing, set to empty or is unparsable, this template object SHOULD be ignored.

Right now, the spec says that the ONLY template allowed is the default one. Can we revise the spec to read that "default" is the, so far, the only pre-defined one? This would support the ability to have multiple templates.

mamund commented 6 years ago

Yep. Good idea. This will clear up confusion in the docs. I'll make a good this week and pay for review.

JorgeFerrer commented 6 years ago

Hey Greg, Mike,

The main problem I see with allowing additional templates other than "default" is, what is the meaning of "default" then?

When there is only "default" and the whole document represents one relation type then it's clear. This is the case in all the examples from the HAL-FORMS Media Type specification.

However in the example of the Spring blog entry, since _templates is included in the HAL response of an employee resource, then the context is lost. It seems it has been assumed that "default" means an update operation on the resource where _templates is included. If this is correct, then the HAL-FORMS spec should clearly explain the meaning of "default" in this context.

Makes sense?

JorgeFerrer commented 6 years ago

To help understand my previous question, I've prepared some alternative ways to provide the representation of the Employee resource from Spring's blog entry. I'm keeping the original from the blog as Option 1. Our understanding of the HAL-FORMS spec wast that it wasn't correct to add _templates in the HAL response for the employee. I'm providing as Option 2 and Option 3, what we interpreted to be correct.

Option 1: HAL document containing HAL-FORMS

This is the option that is provided as an example in the Blog Post Building richer hypermedia with Spring HATEOAS.

The problem I see with this example is that it's not clearly defined what the template named "default" really means. Here it seems to be an update operation for the "self" resource. But that's a convention that is out of the scope of HAL or HAL-FORMS isn't it?

{
  "id" : 1,
  "firstName" : "Frodo",
  "lastName" : "Baggins",
  "role" : "ring bearer",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/employees/1"
    },
    "employees" : {
      "href" : "http://localhost:8080/employees"
    }
  },
  "_templates" : {
    "default" : {
      "title" : null,
      "method" : "put",
      "contentType" : "",
      "properties" : [ {
        "name" : "firstName",
        "required" : true
      }, {
        "name" : "id",
        "required" : true
      }, {
        "name" : "lastName",
        "required" : true
      }, {
        "name" : "role",
        "required" : true
      } ]
    },
    "deleteEmployee" : {
      "title" : null,
      "method" : "delete",
      "contentType" : "",
      "properties" : [ ]
    }
  }
}

Option 2: HAL document links to separate HAL-FORMS resource

This is an alternative implementation of the example provided in the Spring blog. In this case the affordances are both provided as regular _links, where the rel key can be dereferenced to obtain the HAL-FORMS resource associated to that operation.

HAL Resource

{
  "id" : 1,
  "firstName" : "Frodo",
  "lastName" : "Baggins",
  "role" : "ring bearer",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/employees/1"
    },
    "employees" : {
      "href" : "http://localhost:8080/employees"
    },
    "http://localhost:8080/rels/update": {
      "href" : "http://localhost:8080/employees",
      "type" : "application/prs.hal-forms+json"
    },
    "http://localhost:8080/rels/deleteEmployee" : {
      "href" : "http://localhost:8080/employees/1",
      "type" : "application/prs.hal-forms+json"
    }
  },
}

Note how in this case I've provided the HAL-FORMS Mime type in the Link Object. That's not mandatory, but can be helpful for the client.

HAL-FORMS Resource for Create

{
   "_links" : {
     "self" : {
       "href" : "http://api.example.org/rels/update"
     }
   },
   "_templates" : {
    "default" : {
      "title" : null,
      "method" : "put",
      "contentType" : "",
      "properties" : [ {
        "name" : "firstName",
        "required" : true
      }, {
        "name" : "id",
        "required" : true
      }, {
        "name" : "lastName",
        "required" : true
      }, {
        "name" : "role",
        "required" : true
      } ]
    },
  }
}

HAL FORMS Resource for Delete

{
   "_links" : {
     "self" : {
       "href" : "http://api.example.org/rels/deleteEmployee"
     }
   },
  "_templates" : {
    "default" : {
      "title" : null,
      "method" : "delete",
      "contentType" : "",
      "properties" : [ ]
    }
  }
}

Option 3: Same as 2 but form resources are embedded

This option is still a pure HAL resource, with no extensions. It embeds resources of type HAL-FORM.

HAL Resource

{
  "id" : 1,
  "firstName" : "Frodo",
  "lastName" : "Baggins",
  "role" : "ring bearer",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/employees/1"
    },
    "employees" : {
      "href" : "http://localhost:8080/employees"
    },
    "http://localhost:8080/rels/update" : {
      "href" : "http://localhost:8080/employees"
    }
  },
  "_embedded": {
    "http://localhost:8080/rels/update": {
       "_links" : {
         "self" : {
           "href" : "http://api.example.org/rels/update"
         }
       },
       "_templates" : {
        "default" : {
          "title" : null,
          "method" : "put",
          "contentType" : "",
          "properties" : [ {
            "name" : "firstName",
            "required" : true
          }, {
            "name" : "id",
            "required" : true
          }, {
            "name" : "lastName",
            "required" : true
          }, {
            "name" : "role",
            "required" : true
          } ]
        },
      }
    },
    "http://localhost:8080/rels/deleteEmployee": {
      "_links" : {
        "self" : {
          "href" : "http://api.example.org/rels/deleteEmployee"
        }
      },
    "_templates" : {
      "default" : {
        "title" : null,
        "method" : "delete",
        "contentType" : "",
        "properties" : [ ]
      }
    }
  }
}
gregturn commented 6 years ago

To back up to the beginning of my example, the controller I designed for the Spring blog article has one resource that responds to three HTTP methods:

Single-item resource

There is also an aggregate resource at /employess that response to GET and POST as well.

So to me, fetching a single-item resource, i.e. GET /employees/1, should yield:

And this should be driven by the client's Accept header, as to whether the server produces HAL or HAL-FORMS (or whatever future mediatypes we support).

If we are confined to one template, a put forth by the existing HAL-FORMS spec, and the template is itself confined to operate on the self link's URI, then my REST controllers are confined to one HTTP method per route.

Assuming I picked PUT, such a restriction would demand I pick a different route to handle DELETE. That sounds quite awkward--having a mediatype impose limits on REST resource URIs, given that a REST URI should have the option to support any HTTP method.

There were other comments made earlier I want to offer clarity on:

However in the example of the Spring blog entry, since _templates is included in the HAL response of an employee resource, then the context is lost.

Just to be clear, the JSON fragments in my blog article are intended to be HAL-FORMS, not HAL. Right before the first fragment, you can read "Spring HATEOAS will generate HAL-FORMS hypermedia like this". Spring HATEOAS can generate different formats, with separate serializers/deserializers for each supported mediatype.

And to respond to "the context is lost", I submit that the context, like any REST interaction, is given by the "self" link and the fact that this was a GET call. Hence, the client was fetching a specific item resource (compared to an aggregate). The client can then choose to display links to the user using corresponding rels, as well as transform any HAL-FORMS template into a clickable action with the necessary fields. "Default" templates may get preferential placement, while others are, perhaps, tucked away before some folding widget, with just the "name" field for brevity. Who knows? But it's an option in how the client shows these affordances to the user.

The main problem I see with allowing additional templates other than "default" is, what is the meaning of "default" then?

The same question can be posed about any link and its rel, no matter the mediatype. Based on what I've read in this spec, "default" comes across as either the most important action provided in the document, or at least the most likely one to be used. In this example, I would clearly favor PUT over DELETE. For an aggregate resource, I would put POST.

So far, Spring HATEOAS implements by putting the first affordance into that slot. In the future, we may offer some annotation or other means to override this, but for now it seems adequate.

Either way, it doesn't matter. Just show the form to the user on the web page, and let him or her consume it as desired.

When there is only "default" and the whole document represents one relation type then it's clear.

By that same logic, then HAL documents should only have one link, or any other mediatype. But we seem to operate just fine with multiple links. Why not say the same for multiple templates? Part of their purpose can be derived by the HTTP method.

One thing I thought I had misread a few months when implementing HAL-FORMS support, but clearly did not, is that HAL-FORMS can only have one link, the "self" link. Hence I opened #25.

JorgeFerrer commented 6 years ago

Hey Greg,

Thanks for the detailed answer. First of all I wanted to apologize if my previous comment has come out as criticism. It was not my intention. We are developing RESTful APIs in several projects now and Spring Boot (and thus Spring REST) is one of the technologies we are using, so we are very excited with the amazing improvements you guys are making to make it easier.

The reason for my comments is because our interpretation of HAL-FORMS had been different from what you propose in the blog example. What we were doing looked more like options 2 and 3 in my previous comment.

We are just trying to find out from Mike if both approaches are valid and if so what are the pros and cons of each.

"default" comes across as either the most important action provided in the document, or at least the most likely one to be used. In this example, I would clearly favor PUT over DELETE. For an aggregate resource, I would put POST.

The problem I see with this definition of "default" as the most important action is that it varies per resource. So that takes us back to documenting the meaning for each resource, instead of being able to just define the types of relations (or actions) independently of where they are used (which is what we understand Mike advocates).

Makes sense?

gregturn commented 6 years ago

First of all I wanted to apologize if my previous comment has come out as criticism.

None taken!

I enjoy finding someone else trying to implement RESTful concepts (especially if they are using our stuff) and being to hammer out gaps in a spec quite exciting. That was we can build stronger APIs.

πŸ‘

JorgeFerrer commented 6 years ago

Thanks Greg!

@mamund if you have any input or recommendations on the examples above they would be very appreciated.

mamund commented 6 years ago

yes, hello @gregturn and @JorgeFerrer

1) at this stage, whatever change we make needs to be backward compatible. 2) first stage is to define behavior that allows extending the collection 3) an additional effort would be to define details of an extension on how to define/use extended values in the collection.

we can accomplish #1 and #2 with just small changes in the wording of the doc.

doing #3 will take more time/agreement/effort.

let's start w/ #2. i'm open to language.

gregturn commented 6 years ago

Regarding (2):

Replace (section in _templates):

The unique identifier for this template object. This is a REQUIRED element. For this release, the only valid value for key is "default". If this element is missing, set to empty or is unparsable, this template object SHOULD be ignored.

with...

The unique identifier for this template object. This is a REQUIRED element. For this release, the only pre-defined value for key is "default". If this element is missing, set to empty or is unparsable, this template object SHOULD be ignored. If the key is something other, clients may choose to ignore the template entry.

That would let existing clients that assume only the existence of "default" to continue with the option to ignore any other templates, presuming they flat out ignore any other templates. However, clients can also proceed to accept ALL templates.

This wording DOES imply that a HAL-FORMS document could contain multiple links, with none of them being "default".

wdyt?

mamund commented 6 years ago

i think we'll need to keep default (in order to be backward-compatible to any existing implementations)

"if there is only one element in the collection the key MUST/SHOULD be set to "default"

wdyt?

alejandrohdezma commented 6 years ago

Hi all, I'm very interested in this issue and I've been following it from the beginning but I still have some doubts.

In the spec, it is said that:

The HAL-FORMS media type design follows many of the HAL media type conventions. For this reason, HAL-FORMS "looks like HAL." However, it is important to keep in mind that HAL-FORMS is not the same as HAL — the two should not be thought of as interchangeable in any way.

Also, at the last part, where a "Process Flow for HAL-FORMS Documents" is suggested, it says that: first servers should emit HAL responses with links to the HAL-FORMS documents. And then, HAL clients should parse the HAL response and request the HAL-FORMS documents.

Is not this the suggested workflow anymore?

That's my first doubt.

Second (and last), taking a look to the _"The _templates Element"_ section it is said that:

The _templates element describes the available state transition details including the HTTP method, message content-type, and arguments for the transition

What I get from this sentence (and also from the whole spec) is that the _templates will serve the purpose of serving different "forms" for the same action (in the spec examples we can find HAL-FORMS documents for a filter and a create request, both of them in separate HAL-FORMS and, therefore, with their own _templates object, but never both of them inside the same _templates). However what I get from reading this issue (and looking at the examples from the SpringBoot blog post) is that the _templates object could be also used to provide information about two (or more) totally different operations (update and delete in the example).

So... which of the two uses for _templates is correct?

Thank you all :)

mamund commented 6 years ago

the spec is designed to support HAL-FORMS as a separate document. however, the HAL-FORMS spec is not going to be written to prevent people from doing other things w/ HAL-FORMS.

IOW, the spec will focus on the proper use and not on the improper use.

does this help?

alejandrohdezma commented 6 years ago

Ok, yeah, makes sense. Thanks, @mamund!

mamund commented 6 years ago

@JorgeFerrer

i think we'll need to keep default (in order to be backward-compatible to any existing implementations)

"if there is only one element in the collection the key MUST/SHOULD be set to "default"

wdyt?

JorgeFerrer commented 6 years ago

@mamund Yeah, I think that's a very good way to keep backwards compatibility while still leaving the door open to not having to include "default" as one of the _template keys when there are several.

And I think the sentence should be with MUST, not with SHOULD: "if there is only one element in the collection the key MUST be set to "default"

@gregturn I think that with this change it would be more clear if you guys used a more meaningful _template key like "update" or "updateEmployee" instead of "default" in the example of the blog post.

mamund commented 6 years ago

I'm ok with MUST here. Anyone object?

gregturn commented 6 years ago

MUST is perfect. Sounds like we have consensus.

mamund commented 6 years ago

Ok. I'll write up a branch and post for final review.

mamund commented 6 years ago

@gregturn @JorgeFerrer and others...

i created the PR for review. turns out it's a bit messy since i haven't been grooming the sync'd repos well, but hopefully this won't get in the way of this issue.

i'll let this rest a day or so to give us a chance to review/comment before i resolve the merge.

cheers.

gregturn commented 5 years ago

BUMP

mamund commented 5 years ago

after the dust settles on the contentType changes, we can revisit this and close it out next.

thanks for your help on making HAL-FORMS better.