judofyr / tipi

1 stars 2 forks source link

Support links with JSON-HAL #3

Open judofyr opened 10 years ago

judofyr commented 10 years ago

After we have #2 in place we can introduce links:

article = Article.new(…)
article.links.content = router.link.articles[article.id]
article.to_json
{
  "title": "Hello world",
  "author_name": "Magnus Holm",
  "_links": {
    "content": { "href": "/articles/1/content" }
  }
}

A link is a reference to a resource (not an action) and contains the path in two possible ways: The internal chain of Ruby methods ([[:articles], [:find, 123], [:assets]]) and a URL path (/articles/123/assets).

The idea is that you can resolve a link and fetch more data. This will work regardless whether you talk to an HTTP backend or access the Ruby classes directly.

Some concerns:

Right now resources don't keep track of where in the tree they are. If you run root.articles[123].assets all you have is an Assets-instance with a state where the article key is set. This is suboptimal in two ways: (1) There's no way to generate a relative path and (2) if you're fetching links which shares parents you will have to redo a lot of queries.

I'm not really concerned about (2), but (1) is pretty important because it means we'll have to change the conventions for how the Resource-classes are structured. I really like how the current Resource requires little code to nest into another Resource:

# Currently:
def [](id)
  state[:user] = Models::User.find(id)
  User.new(state)
end

# If we wish to main paths we need to add a #parent field:
def users
  state[:user] = Models::User.find(id)
  User.new(state, self)
end

# However, then we can't change state in-place, so we need this:
def users
  state = state.merge(
    user: Models::User.find(id)
  end
  User.new(state, self)
end
judofyr commented 10 years ago

I kinda sidetracked there.

The API for resolving a link will look something like this:

resource = service.resolve(link)

It's crucial that links are just data. link.resolve will never happen; a link will never have access to the service it was fetched from.

judofyr commented 10 years ago

This is actually more tricky than I realized: Do you attach a link to a resource or to a type?

I'm going to postpone this task until I have a specific usecase.

blambeau commented 10 years ago

@judofyr IMO, the answer has to be found in the concept of 'representation' that seems to be missing in tipi so far.

Don't you attach URLs, hence links, to resources, even if various representations of them may lead to different 'types' ? (what do you mean by type anyway?)

judofyr commented 10 years ago

I'm unsure if I'm using all of these words correctly. Are /users and /users/1 two different resources, even though they return pretty much same data ([User] vs User)? I want a User returned either from /users or /users/1 to have the same links attached.

blambeau commented 10 years ago

As I understand RESTful, yes, these are different resources, because they have different URLs. Now, I've never seen anyone talking about resource types. Maybe /users/1 and /users/2 have same resource type, and /users/ a very similar one?

judofyr commented 10 years ago

I guess you could say they are two different resources that return the same representation type?

steveklabnik commented 10 years ago

/users and /users/1 are two different resources, which share a representation, and may contain similar entities.

Every URL points at a distinct resource.

An entity is the thing in your database.

A representation is a media type.

In Fielding, a resource is a relation over time between one or more entities. They can only be acceded through a representation.

Random Programmer tends to conflate 'resources' and entities, imagining those as 'end points' to the same 'resource'. Which is wrong.

Does that make sense?

judofyr commented 10 years ago

Thanks, 'entity' is the word I was missing!

So, where does one attach links? Eventually they end up in a representation, but I guess it makes sense that the same entity also have the same links regardless of the resources they come from.

steveklabnik commented 10 years ago

Links only appear in representations. But they're generated based on the state of your application.

Imagine your app as a state machine. Resources are the states, links are the transitions.

Sent from my iPhone

On Mar 30, 2014, at 12:37 PM, Magnus Holm notifications@github.com wrote:

Thanks, 'entity' is the word I was missing!

So, where does one attach links? Eventually they end up in a representation, but I guess it makes sense that the same entity also have the same links regardless of the resources they come from.

\ Reply to this email directly or view it on GitHub.

blambeau commented 10 years ago

@steveklabnik is there any kind of formal definition/model of all of this? Because I'm kind of lost here:

Entity, Resource, Representation, Thing, in a Database, State, Transition, Links, Resource contain entities, Links appear in representation, resource is relation over time between entities. Looks like random words to me.

I don't thing maths are always needed in programming, but sometimes, a formal model is the only stuff that is clear enough to build something IMO.

steveklabnik commented 10 years ago

fielding's thesis, chapter 5.— Sent from Mailbox for iPhone

On Sun, Mar 30, 2014 at 12:48 PM, Bernard Lambeau notifications@github.com wrote:

@steveklabnik is there any kind of formal definition/model of all of this? Because I'm kind of lost here: Entity, Resource, Representation, Thing, in a Database, State, Transition, Links, Resource contain entities, Links appear in representation, resource is relation over time between entities. Looks like random words to me.

I don't thing maths are always needed in programming, but sometimes, a formal model is the only stuff that is clear enough to build something IMO.

Reply to this email directly or view it on GitHub: https://github.com/judofyr/tipi/issues/3#issuecomment-39037104

judofyr commented 10 years ago

I guess there are different types of links. Some links are dependent on the state (next, prev) while others are directly connected to the entity (a link to a user's posts).

Some context here: I'm trying to figure out where links should be attached in this system. Right now you build up objects (that's your representation) that Tipi automatically converts to JSON. Should you insert links when you build up your objects, or should you be able to tell Tipi what links belongs to each object?

# Solution 1
User.new(name: …, links: {posts: router.users[id].posts})

# Solution 2
links_for User do |router|
  {posts: router.users[id].posts}
end
User.new(name: …)

The advantage of the second solution is that it means you can decouple your URL structure from your representation. I care about this mostly from a Ruby point-of-view (no need to have a global route object).

This obviously only works for links that are strictly connected to the entity (relationships). There still needs to be a way to add links that's strictly related to the resource.

steveklabnik commented 10 years ago

I would imagine links belong on resources, because links are always from a resource to a resource.— Sent from Mailbox for iPhone

On Sun, Mar 30, 2014 at 12:50 PM, Magnus Holm notifications@github.com wrote:

I guess there are different types of links. Some links are dependent on the state (next, prev) while others are directly connected to the entity (a link to a user's posts). Some context here: I'm trying to figure out where links should be attached in this system. Right now you build up objects (that's your representation) that Tipi automatically converts to JSON. Should you insert links when you build up your objects, or should you be able to tell Tipi what links belongs to each object?

# Solution 1
User.new(name: …, links: {posts: router.users[id].posts})
# Solution 2
links_for User do |router|
  {posts: router.users[id].posts}
end
User.new(name: …)

The advantage of the second solution is that it means you can decouple your URL structure from your representation. I care about this mostly from a Ruby point-of-view (no need to have a global route object).

This obviously only works for links that are strictly connected to the entity (relationships). There still needs to be a way to add links that's strictly related to the resource.

Reply to this email directly or view it on GitHub: https://github.com/judofyr/tipi/issues/3#issuecomment-39037182

judofyr commented 10 years ago

At the same time I'd like to avoid duplication. I don't want to write the same code for attaching links for both the /users and /users/:id resource.

Meh, I realize that maybe I don't have enough information/experience to solve this issue. I don't know how I want use links. Top-level next/prev links are easy. It's the moment when I need a link deep down into the representation tree that things become tricky.

Oh well, there's a reason this has been tagged as "Later".

Thanks for the clarifications though; very much appreciated.

marcusramberg commented 10 years ago

I agree with @steveklabnik - Putting the links into the model seems like tight coupling. And in some cases you might even want the same model objects to link differently based on the context in the api.

I like the idea of a resource type that can be attached to resources tho, and it might be the right place for links as well, while allowing reuse for both /users and /users/:id.