8

I am trying to design a RESTful service that makes a good use of the Hypermedia.
Preferably, the user agent should only know the root URI in order to be able to explore all the functionality of the service - that is, I would like it to be in the 3rd level in the maturity model.

Now, the user agent should be able to create some resources and also edit them at a later time. At create / edit time, the user agent needs access to some other resource / enumerations.

foo resource:

{
    "category" : "category chosen from an enumeration of possible categories",
    "color" : "color chosen from an enumeration of possible colors",
    "aRelatedResource" : "resource identifier from chosen from a collection"
}


Given the previously mentioned requirements, I have come up with the following pattern:

Have a fooRoot resource:

{
    // no properties, only links
    "_links" : { 
        "foos" : { "href" : "URI-to-foos" },
        "fooCreator" : { "href" : "URI-to-plain-fooWriter" }
    }
}

Include a link to a fooWriter in the foo resource:

foo resource:

{
    "category" : "category chosen from an enumeration of possible categories",
    "color" : "color chosen from an enumeration of possible colors",
    "aRelatedResource" : "resource identifier from chosen from a collection",
    "_links" : {
        "self" : {...},
        "fooEditor" : { "href" : "URI-to-fooWriter-initialized-for-current-foo" }
    }
}

A fooWriter would look as follows:

{
    "fooPayload" : {
        "category" : "NULL or pre-initialized",
        "color" : "NULL or pre-initialized",
        "aRelatedResource" : "NULL or pre-initialized"
    },
    "_links" : {
        "fooPayloadDestination" : { "href" : "URI-to-foos-or-foo" },
        "categoryEnum" : { "href" : "URI-to-categories" },
        "colorEnum" : { "href" : "URI-to-colors" },
        "availableResourcesToRelateWith" : { "href" : "some-other-URI" },
        ....
        .... and even something useful for pre-validation etc.
        "validator" : { href : "URI-to-some-resource-or-service" }
    }
}

To sum up, any resource that can be created and edited may have an associated writer resource.
By GET-ting the writer, the user agent can create / edit the resource in a quite convenient manner.
The payload embedded in the writer gets POST-ed to its destination and voilà :)

Also, there should be a root container holding links to both the resource and its writer for new resources (see fooRoot in the example above).



The questions are...

...does the pattern described above have a well-known name?
...is there a better way to solve the create / edit problem, where adjacent resources are required at create / edit time and the 3rd level of maturity still "holds"?

Some references:

turdus-merula
  • 8,546
  • 8
  • 38
  • 50
  • 1
    Why wouldn't the `foo` resource have a self link contained within it, and why wouldn't you simply update the resource by executing a PUT to that self link? That seems like a lot less work than your method. – Jonathan W Aug 31 '14 at 04:13
  • @JonathanW, thanks :) Though, what about the links for "colorEnum", "categoryEnum" etc.? Should I add them on the _foo_ resource, too? What about creating a new _foo_, not editing an existing one? Where would those links be embedded in this case? – turdus-merula Aug 31 '14 at 09:01
  • 1
    So part of the reason you might not be getting a response is that your original question is very open ended. To directly answer your questions, this pattern does not have a name that I know of. As to whether the pattern is "okay," I'm not sure what you mean by that. A pattern is simply a template to approach designing something and, by definition, is generally applicable. So... is that really the question you want answered, or is the question whether *this* pattern is the best way to solve your problem? – Jonathan W Aug 31 '14 at 15:32
  • @JonathanW, thanks again :) I've updated the questions at the end of the post. I guess most of all I needed some community feedback regarding the described solution. It seems _reusable_ & _generally applicable_, it suits a range of _common problems_, so we may call it a _pattern_. But it has no name :) – turdus-merula Aug 31 '14 at 17:22
  • 1
    If an enum is a static set of possibilities, I would just document the possible values and validate on the server. I wouldn't try to bake it into the exchange as a link... but if there were a lot of information and the enum itself pointed to a different resource, you could do a similar thing like I did with the parsnip in my example. – Jonathan W Sep 01 '14 at 15:29

2 Answers2

1

What you describe reminds me a bit of create and edit form link relations. If you're building an API, however, its use is fairly limited, as you are going to need to someone to program to it regardless of how it's defined.

In my opinion, the easiest way to organize the example you gave above is to define a root menu like this:

GET / HTTP/1.1
Accept: application/hal+json
----
HTTP/1.1 200 OK
Content-Type:application/hal+json

{
    "_links" : { 
        "plants" : { "href" : "/plants" }
    }
}

The plants relation would hold a collection of plant resources defined by a given media type (let's say it's application/vnd.biology-example-org.plant):

GET /plants HTTP/1.1
Accept: application/hal+json
----
HTTP/1.1 200 OK
Content-Type:application/hal+json

{
    "_links" : { 
        "self" : { "href" : "/plants" },
        "plant": [
          {
            "href" : "/plants/parsnip",
            "title" : "The Parsnip",
            "type" : "application/vnd.biology-example-org.plant+json"
          }
        ]
    }
}

TO add a new plant to the collection that's related to the parsnip, POST to the plants collection resource and relate to the parnsip via its link:

POST /plants HTTP/1.1
Content-Type: application/vnd.biology-example-org.plant+json

{
    "title" : "The Carrot - a cousin of the Parsnip",
    "category" : "vegetable",
    "color" : "orange",
    "related" : [ "/plants/parsnip" ]
}
----
HTTP/1.1 201 Created
Location: http://biology.example.org/plants/carrot

To subsequently modify the carrot, issue a PUT to the URL that was returned:

PUT /plants/carrot HTTP/1.1
Content-Type: application/vnd.biology-example-org.plant+json

{
    "title" : "The Carrot - the orange cousin of the Parsnip",
    "category" : "vegetable",
    "color" : "orange",
    "related" : [ "/plants/parsnip" ]
}
----
HTTP/1.1 200 OK

The above example uses the Hypertext Application Language (HAL) to communicate "Level 3" REST semantics using JSON. HAL is simple yet very powerful. One of the conventions I really like is using the relation name as a URI which, when dereferenced, points directly to the documentation about that relation and the resources it can return.

If you want to play around with a live API like this, I'd strongly suggest looking at HALtalk, which is a live demo API of HAL.

Community
  • 1
  • 1
Jonathan W
  • 3,759
  • 19
  • 20
  • Thank you a lot for the answer :) For "future" generations, perhaps this link may also be referenced in the response: http://stackoverflow.com/questions/5104997/rest-url-for-create-and-edit-forms – turdus-merula Sep 01 '14 at 17:55
0

Let's ask a question. What do you need in order to edit a resource?

  • With HTML you need a FORM tag, and INPUT tags in them.

What do they describe?

  • 1.) It is a form.
  • 2.) It has an action IRI.
  • 3.) It has a method.
  • 4.) It has a content-type.
  • 5.) It has multiple fields, which should be filled by the user. Maybe with validation data.

In the form you got:

  • 6.) title for the entire form
  • 7.) label for the input fields

This is almost the same that a REST client needs.

  • 8.) It should contain a link-relation for the REST client, which it already knows, so the client will use it to put the form into the right context. (You can do this with RDFa by HTML.)

That's all, not less, not more.

Now let's see your current options by my 2 favourite JSON media types (there are a lot other JSON hypermedia types, like collection+json, shiren, etc..).

HAL+JSON

By HAL you can define embedded resources, and add links to them.

{
  "_links": {
    .. *snip* ..
  },
  "_embedded": {
    "manufacturer": {
      "_links": {
        "self": { "href": "/manufacturers/328764" },
        "homepage": { "href": "http://hoverdonkey.com" }
      },
      "name": "Manufacturer Inc."
    },
    "review": [
      {
        "_links": {
          "self": { "href": "/review/126" },
          "customer": { "href": "/customer/fred", "title": "Fred Wilson" }
        },
        "title": "Love it!",
        "content": "I love this product. I also bought a Hover Donkey with it.",
        "rating": 10
      },
      ...
    ]
  },
  "name": "A product",
  "weight": 400,
  .. *snip* ..
}

The _link describes here the 1.), the href describes here the 2.), the link title describes the 6.), the link-relation describes the 3.) and the 8.). We need the content-type 4.), and the meta-data about the fields required: validation, labels 5.) and 7.).

Now what choices do we have here?

  • We can somewhat extend HAL+JSON, and add a fields property to each link, or
  • we can add vendor specific content-types to describe the fields.

JSON-LD + hydra vocab

By the hydra vocab you can add operations to your hydra:Links in your RDF - usually JSON-LD - document.

{
    "@context": {
        "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
        "hydra": "http://www.w3.org/ns/hydra/core#",
        "vocab": "/vocab#",
        ...
        "title": {
            "@id": "vocab:Issue/title",
            "@type": "hydra:property",
            "rdfs:range": "xsd:string",
            "rdfs:label": "Issue title"
        },
        "comments": {
            "@id": "vocab:comments",
            "@type": "hydra:Link",
            "hydra:supportedOperation": [
                {
                    "@id": "vocab:create-comment",
                    "@type": "hydra:CreateResourceOperation",
                    "rdfs:label": "Creates a new comment",
                    "hydra:method": "POST",
                    "hydra:expects": "vocab:Comment",
                    "hydra:returns": "vocab:Comment"
                }
            ]
        }
    },
    "@id": "/issues/cso29ax",
    "id": "cso29ax",
    "created_at": "2012-12-10 12:45",
    ...
    "title": "Some random issue",
    "comments": {
        "@id": "/issues/cso29ax/comments/"
    }
}

Ofc. in practice you'd move the entire @context part into a separate file under the IRI /vocab, but it is easier to check this way what it does. By RDF documents you have subject, predicate, target triplets. For example in here: /issues/cso29ax, /ctx#Issue/title, "Some random issue" can be a triplet. So the "id", "created_at", "title", "comments" are just alternative names which you can describe with the @context.

So in here the @context describes the context of the representation and the property name like /ctx#create-comment and the operation name, like hydra:CreateResourceOperation describe the link-relation 8.). Ofc you can use the iana vocab by the description of the /ctx#create-comment if you want. It depends on you and the capabilities of your REST client. The hydra:Link describes that it is a form 1.). The @id: "/issues/cso29ax/comments/" describes the action IRI 2.). The rdfs:label describes the form title and the input field labels 6.) and 7.) (it can be multi-language). The content-type is always JSON+LD 4.). The HTTP method is described by the hydra:method 3.), the validation data, etc... are described by the hydra:expects and hxdra:returns of each property 5.) so it is part of the "@context" (I left it out in the example). So by RDF + hydra vocab you don't need anything else to describe your forms.

What you probably need is defining multiple representations of the same resource.

For example you need a representation with creation link, edit link, delete link, etc... and you need a representations with data only. You can do that multiple ways:

  • You can define a vendor specific content-type, for each of the representations.
  • You can add a new IRI for your resource, for example /issues/?data-only=1. (The query is for the non-hierarchical part of the IRIs, so it is part of the resource identifiers as well. A single resource can have multiple identifiers, so the /issues/ and the /issues/?data-only=1 can have the same resource. A single identifier cannot belong to multiple resources, but I think this is evident.) So in your case you don't have to create writer and editor resources, it is enough to have a single resource with multiple identifiers (IRIs).
inf3rno
  • 24,976
  • 11
  • 115
  • 197