2

I have a few HTTP resources, and i'm wondering if including the models of one in the other is acceptable or if there is a cleaner method.

In this example, we have a ton of "news" resources, and users can subscribe via a resource called "subscription":

==>/news-items/{id}/
{
    "id": 987,
    "url": "http://example.com/foo/bar",
    "title": "Wow! A Headline",
}

And

==> /users/{user-id}/subscriptions/
[
    {
         "id" : 123
         "keywords": "Mr Celebrity"
    },
    ...
]

And then the endpoint that includes the models of both resources ...

==> /users/{user-id}/my-news/
[
    {
        "id": 987,
        "url": "http://example.com/foo/bar",
        "title": "Wow! A Headline",
        "matched_by": [
            {
                "id": 123,
                "keywords": "Wow",
            },
            ...
        ]
    },
    ...
]

The usecase is fairly self explanatory for this: A feed of news with 'matched by "Mr Celebrity"' next to it. It feels a bit weird to have an endpoint that consists only of nested/composite models from other endpoints ... Is this acceptable practice or problematic?

The endpoint will be read-only, and the other options seem to be:

  1. HATEOAS-style: "matched_by" and the news item are URLs pointing to a news item and subscription. Would require more HTTP requests.
  2. Some kind of multi-payload response with a HATEOAS primary payload then 'related' payload of the referenced resources. Not seen this done before.
  3. ??? some other options?

If my approach is sound, great, but I want to hear what others would do if there is a better alternative.

Thanks :)

3
  • Shouldn't the /users/{user-id}/subscriptions/ be a collection? In your example it obviously isn't one. And what exactly would be contained in the "matched_by" collection? Currently my recommendation would be option 1. with resource expansion functionality. Kinda like described here.
    – mefisto
    May 5, 2014 at 22:24
  • @teresko yes you are right re, the collection. Edited the question. Your link seems to describe a good option in expansion. "matched_by" is a set of subscription resources that "matched" the news item (therefore putting it in the user's feed).
    – Aiden Bell
    May 5, 2014 at 22:30
  • Also, part of being uneasy is the "matched_by" attribute only existing in the latter resource and not the canonical resource, so I was considering a "matched_by":[] in the first resource for the news item for completeness even though it will never contain anything.
    – Aiden Bell
    May 5, 2014 at 22:39

1 Answer 1

2

A feed of news with 'matched by "Mr Celebrity"' next to it. It feels a bit weird to have an endpoint that consists only of nested/composite models from other endpoints ... Is this acceptable practice or problematic?

Having embedded resource representations is perfectly valid, since you describe a resource graph in where every resource can be connected to another resources...

If I were you I'd check some standard hypermedia type instead of creating an own. For example: JSON-LD, HAL, Collection+JSON, etc...

HATEOAS-style: "matched_by" and the news item are URLs pointing to a news item and subscription. Would require more HTTP requests.

HATEOAS is about using hyperlinks, not plain URLs. Don't confuse these terms... A hyperlink consists of at least a HTTP method (calling an operation) and an URL (on a resource). By REST you have to add some meta-data to it, for example link relation or Hydra:Operation (if you prefer RDF).

Just a copy-paste HAL example from one of the linked sites:

{
    "_links": {
        "self": { "href": "https://api.example.com/player/1234567890/friends" },
        "next": { "href": "https://api.example.com/player/1234567890/friends?page=2" }
    },
    "size": "2",
    "_embedded": { 
        "player": [
            { 
                "_links": { 
                    "self": { "href": "https://api.example.com/player/1895638109" },
                    "friends": { "href": "https://api.example.com/player/1895638109/friends" }
                },
                "playerId": "1895638109",
                "name": "Sheldon Dong",
                "alternateName": "sdong",
                "image": "https://api.example.com/player/1895638109/avatar.png"
            },
            { 
                "_links": { 
                    "self": { "href": "https://api.example.com/player/8371023509" },
                    "friends": { "href": "https://api.example.com/player/8371023509/friends" }
                },
                "playerId": "8371023509",
                "name": "Martin Liu",
                "alternateName": "mliu",
                "image": "https://api.example.com/player/8371023509/avatar.png"
            }
        ]
    }
}

As you can see this defines one size property and two embedded players for a collection resource. Every representation contains links. You can find the main resource identifier (URI) under the links having self link relation. This is just one example. Most of the generic hypermedia types use similar approaches. Btw. this is just the GET part of the story. (HAL does not support POST, PUT, DELETE links, you have to use an extension to describe them.)

Understanding a better solution, like RDF with JSON-LD and Hydra would be a longer story...

3
  • 1
    Really enjoyed reading this, consider the questions answered. Thank you for taking the time to answer. RDF is lovely, so I might look in to JSON-LD. Thanks again!
    – Aiden Bell
    Sep 18, 2014 at 23:08
  • You're welcome! Just to mention the Hydra vocab (for REST APIs) is not a standard yet. The Hydra group is currently working on it. If you have any questions or suggestions you can join to the group and write to the public-hydra mailing list...
    – inf3rno
    Sep 18, 2014 at 23:42
  • You might be interested in the dissertation of Markus, it is about how they standardized JSON-LD and created the Hydra vocab. (I am on the 93/214 page now, hard stuff.) I guess you already know the Fielding dissertation.
    – inf3rno
    Sep 18, 2014 at 23:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.