agourlay / omnibus

An HTTP-friendly persistent message bus.
Apache License 2.0
70 stars 4 forks source link

Hateoas approach #13

Closed agourlay closed 10 years ago

agourlay commented 10 years ago

Right now the GET method on a topic is used to subscribe directly to the topic.

I believe we should offer a level of indirection to expose the topic in a more REST fashion way.

GET /topics/animals/furry "should" reply something like

{
  "name" : "animals/furry",
  "creationDate" : "aTimestamp",
   "_links": {
        "subscribe": { "href": "/stream/topics/animals/furry" },
        "stats":  { "href": "/stats/topics/animals/furry" },
        "parent": { "href": "/topic/animals/" },
        "children": [{ "href": "/topic/animals/furry/cats",
                          "href": "/topic/animals/furry/fox" }]
    }
}

That way the topic tree can be traversed in a clear REST way and subscribing becomes more clear with the action url.

Any suggestion/feedback on this?

edit : fix title

xogeny commented 10 years ago

This looks like how I would approach this. You could make the "children" embedded (using the _embedded) field. This would be logical since these are resources that are nested inside "animals/furry". That would also allow you to expose some information about those objects (although that isn't strictly required by the HAL spec).

I would use a different format for the "name". Perhaps ["animals","furry"]. What you have looks too much like a URL. The risk is that people will start doing things like "/stream/topics/"+name to construct a URL. The whole point of HATEOAS is that the client shouldn't have to know anything about how the links are constructed or where they might go to. It should get all of that from the _links and _embedded fields.

BTW, you might ultimately want to have subscribe links for both websocket and SSE so you'll have one more link. But the tricky part is that the websocket URL should follow the ws or wss scheme. So you either need to bring in the network location part of the URL (e.g., ws://hostname/stream/topics/animals/furry) or make sure it is documented in the API that /stream/topics/animals/fury is a websocket).

xogeny commented 10 years ago

One other comment...it is HATEOAS (note the A). :-)

agourlay commented 10 years ago

Made some progress regarding this topic. Here is the data returned by the current implementation.

curl - POST http://localhost:8080/topics/animals/furry/cats Topic animals/furry/cats created curl -X POST http://localhost:8080/topics/animals/furry/bears Topic animals/furry/bears created curl -X GET http://localhost:8080/topics/animals/furry

{
  "topic": "/animals/furry",
  "subTopicsNumber": 2,
  "viewDate": 1390066096,
  "_embedded": {
    "children": [{
      "/animals/furry/cats": {
        "href": "/topics/animals/furry/cats"
      }
    }, {
      "/animals/furry/bears": {
        "href": "/topics/animals/furry/bears"
      }
    }]
  },
  "_links": [{
    "self": {
      "href": "/topics/animals/furry"
    }
  }, {
    "subscribe": {
      "href": "/stream/topics/animals/furry"
    }
  }, {
    "stats": {
      "href": "/stats/topics/animals/furry"
    }
  }]
}

with the http header Content-Type →application/hal+json;

If you try to GET directly on /topics you will receive the list of topic roots formatted as well.

It is also possible to retrieve all the topic leaves formatted like that using the administration API "/admin/leaves".

WDYT?

xogeny commented 10 years ago

That looks pretty nice. I have a couple of comments. First, are you familiar with hal-browser. I'd suggest installing it and then using it to browser your API. It will be a good check of HAL compliance (and it is a pretty cool demonstration of what HAL allows you to do).

Second, one thing to be careful of is to make it clear for people consuming the API what is really a URL and what isn't. So I would change your _embedded contents to:

"_embedded": {
    "children": [{
      "cats": {
        "href": "/topics/animals/furry/cats"
      }
    }, {
      "bears": {
        "href": "/topics/animals/furry/bears"
      }
    }]
  }

Because we know that we are already looking at the "furry" topic inside the "animals" topic, we don't need to repeat all that in the specification of the children's names, only in the href for them. I'd also consider a different specification for topic, something like:

topic: ["animals", "furry"]

Again, the reason is that you need to be careful to make clear what is really a URL.

This is one of the big adjustments for HATEOAS is that most programmers come at APIs thinking they need to construct the URLs based on id fields and so on. So I try to avoid including id fields or URL fragments because people will think they need to use these to build their own URLs.

Also, when using POST you should return the proper status code. It should be either 201 or 202 depending on whether the POST actually actually caused the resource to be created on the server side and you should return a Location header with the URL of the resource (and not just as a self in the response).

I hope that helps.

agourlay commented 10 years ago

I did not know "hal-browser", I will check it out thx.

You are right about children naming and topic name representation, I will implement it this way.

Concerning the POST method we already have 201 or 202 with the proper location header ;)

The API starts to look good.

agourlay commented 10 years ago

Sadly I am running into strange javascript errors with hal-browser.

Anyway I have followed your last suggestions :+1:

I will release the version 0.1 rather soon and I am pretty sure I will receive more feedback on that topic.