the-hypermedia-project / charter

Overview of the project goals.
52 stars 3 forks source link

Hypermedia Resource Interface #6

Closed zdne closed 9 years ago

zdne commented 9 years ago

So I have drafted what could be a Swift interface of the Hypermedia Resource. The draft does not cover any sort of builder at the moment.

As I was working on the draft inspired (read: porting) by @fosrias Ruby interface following questions and remarks popped up:

NOTE: I will tick off the questions / remarks as we solve them

fosrias commented 9 years ago

One general comment to consider as we are working on all the interface and internal structure of the object is not so much a language specific or media type specific focus but a canonical classification of what is going on. That is what is the more abstract concept in play. That way we don't miss solving the general situation and then having specific media types etc map into that. That being said, media type specific questions will press finding those things.

Since some media types (HAL) can have more links under one relation type, shouldn't the transition class hold the relation type and an array of links instead?

Good question. Have been thinking about this a bit myself. You have two options as a consumer:

  1. See if the link relation is an array and respond accordingly.
  2. Always assume it is an array and check the count.

I suspect that the more common situation is that it is just a single transition object, but it is more problematic to have variations of the type you get. This same issue will show up in the treatment of "embedded" resources. Do we treat them all as arrays even if the resource relation is one-to-one vs. one-to-many.

It is sort of artificial to cast all to an array, but this would be how we would need to do it to avoid getting different types. It does then cause some issues in media type translation of these things if the thing should not be sent as an array if there is only one.

From a HAL standpoint, cast all as arrays. As a canonical object, that causes a loss of some information and may not be the most intuitive. Just musing pros/cons here. Would like to here some other thoughts on this.

shouldn't this be reflected by the resource attribute?

Per my initial comment, I believe even these media types send a "name" for these resources. It may happen to be the same as a link rel, but the more fundamental concept is to name the embedded resource and we should do that with no coupling to a media type implementation. It may be that a serializer/deserializer implements the translation that way, but the canonical object just has "resources" that are named and when you construct it you tell it those things. Should not couple these things.

This canonical object should be built not based on a media type but on what each of the building blocks are, IMO. Make sense?

NAMING.

Again ditto on the first comment, maybe we have a property called "name" and a property called "prompt" on a transition. HAL would map conceptually "title" to "prompt" and maybe siren something else, but "name" conceptually means the same thing. So, this is part of that challenge of all this which @smizell is going to put together something to talk about these canonical constructs. Ideally, we do all this in an intuitive way, but at some point you have to decide on what the common property is and then map media types into it. Some media type that has a concept or property that does not map may demand a new property on a transition for example, but that information would be lost in translating to a media type that does not have that concept. Too abstract ideas, or does this make sense. This object is a "rosetta stone" interface. It exposes access to all the information but in standard way that is not coupled to one media type (albeit it may use the exact same property since we are not trying to come up with new NAMING unless we absolutely have to).

What exactly is input property validator?

So, that is more throw in for conceptual consideration, but basically if a media type supports sending validation information associated with particular input properties for client side validation, this object structure should support that. Clarify?

As HAL has additional properties for transitions

I did not try to be complete. Again, waiting on @smizell to frame some of this when he gets a chance. So, we certainly may have some of these. But they will be something canonical. That may be hreflang etc, or something else. Again, if something in some media type is well named we can certainly use it, but we don't have to.

Hope that keeps you going. Not trying to be obtuse, but what we want is a standard interface that as much as possible exposes intuitive names for things. This may or may not map to the media type underneath that the message was transported in.

smizell commented 9 years ago

I am getting ready to work on that document of hypermedia elements. I hope to get to it this week.

Regarding some of these thoughts here, in what I've done with Verbose, Halpert, and other the maze example, I have had a Transitions class which stores one or many Transition objects. I then created methods for that Transitions class for filtering and lookups, such has has_rel, filter_rel, or get_rel. I even have a general filter method for filtering on multiple properties. These are some crude examples.

has_customer = hypermedia_resource.transitions.has_rel('customer')
many_customers = hypermedia_resource.transitions.filter_rel('customer')
first_customer = hypermedia_resource.transitions.get_rel('customer')
specific_customer = hypermedia_resource.transitions.get(rel="customer", name="John")

HAL and others that extend HAL are the only media types I've seen that use a JSON object with the rels as keys. In most other media types, you have an array of anonymous objects that are links with rel properties, such as:

{
  "links": [{ "rel": "customer", "href": "/customer/1" }]
}

If you do it the HAL way, you'll have to convert these over to its link pattern, then actually convert back if you want to filter on additional things. In my experience (I can be persuaded though!), I think using object keys as part of the internal object makes adding functionality like filtering and lookups harder.

I'll get to these other things soon. For transitions, Verbose has:

I've considered going with "class" instead of name, and actually started there. This is modeled after HTML.A tags.

<a href="/customers" title="List of Customers" id="customers" class="customer" rel="customers">Customer List</a>

I'll get more to this later. Would be interested in hearing your alls thoughts.

fosrias commented 9 years ago

objects that are links with rel properties

So, this is the more canonical concept. That a transition has a relation type that it stores in a "rel" property. We can use that as a key on a transition as it is an obvious choice, IMO. So, if you not in my example transition it stores the rel on the transition, but it is also used as a key on the transitions hash. We could just have transitions be an array and then filter on rel, but I think a hash is a more performant way to do it.

So, this is a good example of what I am talking about. Get at the canonical concepts and that is the interface. Then these media types internally map to that. Know you are thinking that way.

On Mon, Aug 18, 2014 at 10:48 PM, Stephen Mizell notifications@github.com wrote:

I am getting ready to work on that document of hypermedia elements. I hope to get to it this week.

Regarding some of these thoughts here, in what I've done with Verbose, Halpert, and other the maze example, I have had a Transitions class which stores one or many Transition objects. I then created methods for that Transitions class for filtering and lookups, such has has_rel, filter_rel, or get_rel. I even have a general filter method for filtering on multiple properties. These are some crude examples.

has_customer = hypermedia_resource.transitions.has_rel('customer')many_customers = hypermedia_resource.transitions.filter_rel('customer')first_customer = hypermedia_resource.transitions.get_rel('customer')specific_customer = hypermedia_resource.transitions.get(rel="customer", name="John")

HAL and others that extend HAL are the only media types I've seen that use a JSON object with the rels as keys. In most other media types, you have an array of anonymous objects that are links with rel properties, such as:

{ "links": [{ "rel": "customer", "href": "/customer/1" }]}

If you do it the HAL way, you'll have to convert these over to its link pattern, then actually convert back if you want to filter on additional things. In my experience (I can be persuaded though!), I think using object keys as part of the internal object makes adding functionality like filtering and lookups harder.

I'll get to these other things soon. For transitions, Verbose has:

  • id - Unique identifier throughout the document
  • name - The name of the actual transition (such as customer)
  • title - Human-readable title of the transition
  • description - Human-readable description
  • label - Human-readable label or prompt

I've considered going with "class" instead of name, and actually started there. This is modeled after HTML.A tags.

Customer List

I'll get more to this later. Would be interested in hearing your alls thoughts.

— Reply to this email directly or view it on GitHub https://github.com/the-hypermedia-project/charter/issues/6#issuecomment-52511296 .

smizell commented 9 years ago

What if we had an object and interface for each of these components of the resource object rather than accessing it directly with a hash? Right now, to access a transition, you do:

resource.transitions[:self]

...which is a Ruby hash and not an object we own. Couldn't we hide all of this hash-versus-array stuff behind an interface and deal with it internally? If we find the performance of our lookups are slow, we can change the internals without changing the API?

resource.transitions.get_rel(:self) # or something clearer

Of course, you can define the [] method on this interface in Ruby, too, to do what you do above, but you are still hiding this functionality behind this object. Either way, you have a Transitions object that handles this rather than the normal hash and you can implement it in the class however you like, fixing performance in the future.

This is what I was doing with Halpert, but I can always be persuaded. It just feels like you give up having control over that part of the API if you have it accessed primarily through a hash. Just my thoughts! What do you think?

fosrias commented 9 years ago

I think have some form of an enumerable for the transitions object is the way to go, which does not preclude your interface, but would mean that we would have to extend array or hash with a 'get_ref' method and turn "transitions" into a custom enumerable object. Not the end of the world. Kind of non-standard from a ruby perspective. May be more common in other languages.

I think an alternative would be to have transitions be some enumerable and have a helper method on a resource object to find a transition, like

self_transition = resource.get_transition(:self) # returns nil if no self-transition

# or

self_transition = resource.transitions[:self] # returns nil if no self-transition

or something. The more we do that type of thing the more we complicate the interface, but I suspect some subset of helper methods that support a paranoid introspection of the resource and its transitions to see what is there may ultimately make sense.

Not sure what the best answer here is, but arrays and hashes also have some filtering interfaces we could use as well like:

 self_transition = resource.transitions.detect { |transition| transition.rel == :self } # Array
 self_transition = resource.transitions.values.detect { |transition| transition.rel == :self } # Hash

Anyhow, not making any definitive conclusion, but I think we could pick one or the other and use native filtering approaches as one option. Back to you on thoughts?

On Tue, Aug 19, 2014 at 1:41 AM, Stephen Mizell notifications@github.com wrote:

What if we had an object and interface for each of these components of the resource object rather than accessing it directly with a hash? Right now, to access a transition, you do:

resource.transitions[:self]

...which is a Ruby hash and not an object we own. Couldn't we hide all of this hash-versus-array stuff behind an interface and deal with it internally? If we find the performance of our lookups are slow, we can change the internals without changing the API?

resource.transitions.get_rel(:self) # or something clearer

Of course, you can define the [] method on this interface in Ruby, too, to do what you do above, but you are still hiding this functionality behind this object. Either way, you have a Transitions object that handles this rather than the normal hash and you can implement it in the class however you like, fixing performance in the future.

This is what I was doing with Halpert, but I can always be persuaded. It just feels like you give up having control over that part of the API if you have it accessed primarily through a hash. Just my thoughts! What do you think?

— Reply to this email directly or view it on GitHub https://github.com/the-hypermedia-project/charter/issues/6#issuecomment-52536089 .

smizell commented 9 years ago

This is good. I like your idea to focus on the interface first, and I think this will be very helpful.

Regarding the helper idea, I do agree as time goes on it could grow in complexity as you add all of the helpers, especially if the builder helpers are there, too. An enumerable class seems like a cool way to solve this. What about moving these builder methods there as well?

resource.transitions.add(:self, "/customers")

In Halpert and in my Python stuff, I created a special class for this transition collection that includes the building methods. I would also add methods for removing items.

class Transitions:
    """
    Transitions for a given representation
    """

    def __init__(self):
        # This could be a hash to increase performance per our
        # conversations about hash versus array. How it is stored
        # and accessed can then be up to the designer.
        self.items = []

    def add(self, rel, href):
        link = Transition(rel, href)
        self.items.append(link)
        return link

    def filter_by_rel(self, rel):
        return [item for item in self.items if item.rel == rel]

    def get_by_rel(self, rel):
        return self.filter_by_rel(rel)[0]

    def has_rel(self, rel):
        return len(self.filter_by_rel(rel)) > 0

    def all_rels(self):
        return [item.rel for item in self.items]

class Transition:

    def __init__(self, rel, href):
        self.rel = rel
        self.href = href
smizell commented 9 years ago

I meant to add, something like:

resource.transitions.all

...could return an array of all transitions (no matter if you use hash/array) and give you all the filtering methods.

Just throwing out some ideas here. I'm all for doing what is best!

fosrias commented 9 years ago

W/r to builder methods, they are on a separate builder class and not on the "HypermediaResource" object at all. That is the constructor yields a builder but the methods for building the object are not on it at all, which was the point of the builder class. You may have missed that or I may have misunderstood your comment. There is a side of me that would like to keep the builder methods completely separate, but something like add may make sense.

Let me mull on this custom enumerable idea. I think that could keep the overall object interface cleaner and we could also look at a similar approach to embedded resources in the 'resources' attribute.

It is probably a pattern that would translate to other languages as well.

On Tue, Aug 19, 2014 at 9:26 PM, Stephen Mizell notifications@github.com wrote:

This is good. I like your idea to focus on the interface first, and I think this will be very helpful.

Regarding the helper idea, I do agree as time goes on it could grow in complexity as you add all of the helpers, especially if the builder helpers are there, too. An enumerable class seems like a cool way to solve this. What about moving these builder methods there as well?

resource.transitions.add(:self, "/customers")

In Halpert and in my Python stuff, I created a special class for this transition collection that includes the building methods. I would also add methods for removing items.

class Transitions: """ Transitions for a given representation """

def __init__(self):
    # This could be a hash to increase performance per our
    # conversations about hash versus array. How it is stored
    # and accessed can then be up to the designer.
    self.items = []

def add(self, rel, href):
    link = Transition(rel, href)
    self.items.append(link)
    return link

def filter_by_rel(self, rel):
    return [item for item in self.items if item.rel == rel]

def get_by_rel(self, rel):
    return self.filter_by_rel(rel)[0]

def has_rel(self, rel):
    return len(self.filter_by_rel(rel)) > 0

def all_rels(self):
    return [item.rel for item in self.items]

class Transition:

def __init__(self, rel, href):
    self.rel = rel
    self.href = href

— Reply to this email directly or view it on GitHub https://github.com/the-hypermedia-project/charter/issues/6#issuecomment-52641757 .

smizell commented 9 years ago

I did see the builder class, but think I did cross my wires. In your interface, the HypermediaResource object does not have these helpers directly on it. Do you like having these separate for organization purposes or for interface design purposes?

smizell commented 9 years ago

Sorry for the confusion there, btw!

fosrias commented 9 years ago

By using a builder class we get two things:

  1. You encapsulate how to build the complex object that would basically be impractical to build in a constructor.
  2. You keep the API of the HypermediaResource centered around interacting with the message it wraps.

To me, this is preferable.

NP on any confusion. All reasonable questions.

On Tue, Aug 19, 2014 at 9:42 PM, Stephen Mizell notifications@github.com wrote:

Sorry for the confusion there, btw!

— Reply to this email directly or view it on GitHub https://github.com/the-hypermedia-project/charter/issues/6#issuecomment-52644240 .

smizell commented 9 years ago

A builder using these blocks does seem nice, especially as you start needing to nest these. How do you foresee that aspect of the interface working with these builders? For example, say you want to add URI parameters to a transition. Would you have another builder that you'd use to build the fields within the main resource builder?

Python doesn't really have this ability for builders, and though it does have higher order functions, passing them around requires they already be defined. I suppose it would need a little different approach.

fosrias commented 9 years ago

I think in my example I have a transition builder that I yield that has an interface for that if you look a little closer. In Python or other libraries that don't yield for construction, you can have the builder have a method to return the built object and it encapsulates how it does that. In my ruby example you can do it that way too. The builder creates a complex internal struct and passes that into the constructor of an instance to do that. Whether that is how other libraries do it or not is not important. The fact that the builder returns an instance of the built object transcends language here.

On Tue, Aug 19, 2014 at 11:26 PM, Stephen Mizell notifications@github.com wrote:

A builder using these blocks does seem nice, especially as you start needing to nest these. How do you foresee that aspect of the interface working with these builders? For example, say you want to add URI parameters to a transition. Would you have another builder that you'd use to build the fields within the main resource builder?

Python doesn't really have this ability for builders, and though it does have higher order functions, passing them around requires they already be defined. I suppose it would need a little different approach.

— Reply to this email directly or view it on GitHub https://github.com/the-hypermedia-project/charter/issues/6#issuecomment-52660201 .

smizell commented 9 years ago

I'm following you then. I was thinking you were recommending with your design to primarily use blocks or callbacks. You're just suggesting a builder class used in whatever way makes the most sense in the language.

I was also asking because I was curious if you were going to create more builders than you have. It seems like a URI/body param will have quite a few available attributes (such as default values, a list of options, etc.). Are you suggesting a builder only for the resource and transition, with hashes for everything else (such as your has that includes validator), or is that up to the designer?

smizell commented 9 years ago

Regarding this:

As HAL has additional properties for transitions like hreflang, profile or deprecation

My thought was that hreflang is a hint, such as what the resource accepts and sends. Profile and deprecation seem like link relations (and therefore links), so I put them in a different category.

With that said, I think our transition object should support some of this hinting. Once you start including some of meta data, transitions, and properties of the resource, though, I would consider it to be a partially embedded resource. If you include all of this information, I'd consider it to be a fully embedded resource.

Once you consider it a partially embedded resource, it's no longer this simple transition, but has all of the methods of a resource, which includes meta data for profile and deprecation links.

The hard part here is defining where to draw this line. I've mentioned this struggle with seeing a difference between link and embedded resource. I have moved away from that, but there may be some merit to that in regards to situations like this. What if we don't separate "transition" from "hypermedia resource" and combine them? We already do this with embedded.

Just a thought at this point. Proceed with caution!

smizell commented 9 years ago

To add some clarity, I am not saying all transitions should be considered a Hypermedia Resource because different data is needed for each of these. I am saying to consider an embedded resource to be a transition and not something separate.

zdne commented 9 years ago

Gentlemen, while we have avoided being too media type specific I think we have slipped into programming language details. In the spirit of @fosrias "canonical constructs" we should let language specific details and habits (e.g. accessing via hash or method, function, callback, I-do-not-know-what) to the respective language and do not impose it.

In the attempt to do draw attention away from programming languages I will use my own (well, it's not a programming language anyway) – MSON to paint the canonical structure irrespective of implementation language:

Hypermedia Resource (object)

Transition (object)

Input Property (object)

Embedded Resource (Hypermedia Resource)

zdne commented 9 years ago

Note in the MSON example above I have skipped validator as I do not feel it is needed at the moment – I do not feel we should cover all possible possible canonical aspect (where is code on demand if so) in the first minimum viable product. Let's deliver an MVP validators etc. can be than easily added.

Also I have added meta data and meta links to the transition object.

Thoughts?

smizell commented 9 years ago

Z, good stuff! I struggle to find the balance here as we design this interface and have these discussions. I'll try to be a little more specific in this here than I have been with my questioning.

If we do move away from language-specific discussion, I feel as though we should also move away from the builder aspect we've been talking about. How things are built feels like implementation. While I definitely agree that the interface to these objects should provide the methods necessary to build a representation, bringing the builder in the mix immediately brings up questions on how I should build and organize that. Is this simply a hash at its core with builder classes and functions? If not, what parts are basic hashes and what parts are objects? I think those are important things to nail down for all languages.

I also feel the MSON example comes a little short for me, as I still see implementation in it. When I see array and other types throughout the example, I am instantly thinking about the way I would implement it. I have been asking a lot of questions about having an array as a primary means to accessing transitions and other objects because I think we're we're limiting the interface by just sticking everything in arrays and hashes.

In my thinking, rather than storing these objects in arrays and hashes, we create objects for everything. This means instead of an array of Transitions, you have a Transition Collection object that handles a collection of Transition Item objects. The collection provides methods for adding, removing, and filtering the internal items. It also leaves all of the implementation up to the developers in that we no longer care if the internals of these objects are in a hash, array, callback, etc. We just design the interface, which should be a really simple interface.

Also, see the Halpert Representer for what I'm talking about here.

Hope that makes sense! Feel free to fire back!

fosrias commented 9 years ago

Concur w/r to keeping the focus. Only comment w/r to builder is that if we are talking about the interface of the HypermediaResource, I think considering what we do NOT put in it may be more than an implementation question. Builder pattern suits this well so that we don't have to pollute the HypermediaResource interface. What the interface of the builder is can certainly be tabled but frankly the moment we start writing code with media type fixtures we are trying to serialize/deserialize this will come up.

Per validators, can certainly be left off for MVP as not many media types support that specification other than HTML and Hale ATM probably.

On Thu, Aug 21, 2014 at 12:25 PM, Stephen Mizell notifications@github.com wrote:

Z, good stuff! I struggle to find the balance here as we design this interface and have these discussions. I'll try to be a little more specific in this here than I have been with my questioning.

If we do move away from language-specific discussion, I feel as though we should also move away from the builder aspect we've been talking about. How things are built feels like implementation. While I definitely agree that the interface to these objects should provide the methods necessary to build a representation, bringing the builder in the mix immediately brings up questions on how I should build and organize that. Is this simply a hash at its core with builder classes and functions? If not, what parts are basic hashes and what parts are objects? I think those are important things to nail down for all languages.

I also feel the MSON example comes a little short for me, as I still see implementation in it. When I see array and other types throughout the example, I am instantly thinking about the way I would implement it. I have been asking a lot of questions about having an array as a primary means to accessing transitions and other objects because I think we're we're limiting the interface by just sticking everything in arrays and hashes.

In my thinking, rather than storing these objects in arrays and hashes, we create objects for everything. This means instead of an array of Transitions, you have a Transition Collection object that handles a collection of Transition Item objects. The collection provides methods for adding, removing, and filtering the internal items. It also leaves all of the implementation up to the developers in that we no longer care if the internals of these objects are in a hash, array, callback, etc. We just design the interface, which should be a really simple interface.

Also, see the Halpert Representer https://github.com/smizell/halpert-representer/blob/master/src/representer.coffee for what I'm talking about here.

Hope that makes sense! Feel free to fire back!

— Reply to this email directly or view it on GitHub https://github.com/the-hypermedia-project/charter/issues/6#issuecomment-52879834 .

smizell commented 9 years ago

Mark, thanks for working through this with me, even past my hard-headedness! I'll get this soon, I promise! I don't want it to seem like I'm against the builder idea. I think it's great! What I'm saying is that it feels more like an implementation aspect to me.

If I think of the interface itself from the user's perspective (the developers that use these libraries), I don't personally see much difference between using builders and using this collection object.

# Builder
my_resource = HypermediaResource.new do |builder|
  builder.add_transition(:self, 'http://example.com/something')
end
all_transitions = my_resource.transitions
self_transition = my_resource.transitions[:self]

# Collection
my_resource = HypermediaResource.new
my_resource.transitions.add(:self, 'http://example.com/something')
all_transitions = my_resource.transitions.all
self_transition = my_resource.transitions.get(:self)

The collection idea also fulfills both of your benefits that the builder provides, in that it does not pollute the HypermediaResource interface and handles the complexity of creating transition objects. In the example above, the transitions attribute on my_resource is this special collection object and not a primitive array.

I also think as people start using this, we will find that people find certain methods that could simplify their lives, such as:

# Select all safe transitions
my_resource.transitions.filter_safe

This functionality would go in the transition collection object, which would keep a separation of this complexity too. Basically, instead of having a builder that has methods for building each type of object possible, you have these objects to which you delegate that responsibility. And instead of builder.add_transition you have transitions.add. Just as a note, I'm not arguing the interface should be implemented this way, just saying it seems the same from a user's perspective.

All of this makes it feel more implementation to me, though I could very well be missing something. I will say, if the transitions attribute is merely an array, this builder is a necessity, but if you use these collection objects and move the building methods to their respective collections and not to the HypermediaResource interface, you essentially have the same thing in both cases. I personally believes it future-proofs some things, too, but the future is a ways off.

To your comment about the serializers/deserializers, I do think we will need something separate from the HypermediaResource interface (which I called a Translator in one of my examples). I have some more thoughts on this, but I'll leave that until later.

I feel it would be wrong to bring up issues and not propose a solution, so here's a thought. I'm taking what Z has done and removed things like objects, arrays, strings, etc. I've also added the idea of collections/items. A collection MAY be an array of items, it MAY be an object, or it MAY be whatever other way you might want to implement it. Whether it's an array or not, I think that's implementation details.

Feel free to change this up in any way. I think something along these lines allows us to build the same kind of interface while allowing for different implementations. Thoughts?

Edit: Fixed embedded resources.

fosrias commented 9 years ago

@smizell No hard headness even considered on my side. These are all really great ideas and worth the discussion. I think we need to find a way to separate the "canonical" interface discussion from the implementation discussion lest @zdne not use nice words like "gentlemen" the next time he calls us on this.

smizell commented 9 years ago

@fosrias, haha, I agree! I think we're moving that way. I sometimes have to take the long way to get there.

zdne commented 9 years ago

I like it! Good point on all sides. Simplified, still MSON (sorry have to practice it :) version:

Hypermedia Resource

Attribute

Transition

Input

Meta

Embedded Hypermedia Resource

Collection: T

Generic collection, possibly with language specific implementation, array, linked list, hash etc.

zdne commented 9 years ago

I feel my questions are answered. I have updated the original post accordingly. Closing the issue. Lets discuss the canonical form elsewhere its needed.