- From: Gregg Kellogg <gregg@greggkellogg.net>
- Date: Thu, 24 Apr 2014 10:57:35 -0700
- To: Thomas Hoppe <thomas.hoppe@n-fuse.de>
- Cc: public-hydra@w3.org
Hi Thomas,
On Apr 24, 2014, at 1:14 AM, Thomas Hoppe <thomas.hoppe@n-fuse.de> wrote:
> Hello Gregg,
>
> just my 50 Cent on this:
> - I don't understand why the {collection} is part of the templated link, I would expect that a client knows this from its current IRI.
As I mentioned elsewhere, depending on the type of IRI you want to mint, joining a template to the resource base can be a problem. For example, if I have a resource I've retrieved from <http://example/giants> and I want to construct a template "interests{?likeKind}", if I do basic URI joining, I would get <http://example/interests?like=fan> , where I probably want it to be relative to the giants.
The problem is easier if all resources are returned with a trailing "/", such as <http://example/giants/>, but this may be too big of an assumption.
> - Apart from that I would model the likeKind filter as you did.
> - The SPARQL queries are just the implementation to perform the actual query behind the API (if you have a SPARQL query at hand) but I don't see how they are related to the API. Of course you could introduce an operation "SPARQLQuery" that exepects a somehow JSON-LD serialized sparql query (what I quite like but as we know is not realistic).
I was using the SPARQL queries to illustrate the kinds of searches we might want to perform, not to bind them that tightly. I suspect this will be out-of-scope at this point, but you could imagine a search with an IRI template such as "interests?group-by=likeKind&count" that might return multiple results with the count of each kind of UserLike.
Gregg
> Greets, Thomas
>
>
> On 04/12/2014 11:49 PM, Gregg Kellogg wrote:
>> I've been discussing some issues with Markus off-list related to API issues we're working through in defining the YourSports API using Hydra. One of them deals with how to query a collection.
>>
>> For an example, I may have a schema:SportsTeam with a collection expressing interests on the team (such as fan, like, dislike, neutral). Some data may look like the following (sorry for the long setup):
>>
>> </giants> a :SportsTeam;
>> :name "SF Giants";
>> :interest [
>> a :UserLikes;
>> :linkKind "fan";
>> :performer </gregg>
>> ], [
>> a :UserLikes;
>> :likKind "foe";
>> :performer </markus>
>> ] .
>>
>> As expressions of interest on a particular team may be quite large, I want to separate these into a PagedCollection. As an interim solution to avoid breaking a specific "interest" property on SportsTeam, I'm using a separate collection property, and referencing it collection property from the SupportedProperty on interest. A part of the API description looks like the following:
>>
>> ysapi: a hydra:ApiDocumentation;
>> hydra:supportedClass :SportsTeam .
>>
>> :SportsTeam a hydra:Class;
>> hydra:supportedProperty [
>> a hydra:SupportedProperty;
>> hydra:description """
>> The interest property does not appear directly in an entity description; interests are asserted in a Collection identified as the value of the interestCollection property.
>>
>> Entries are managed through the associated Collection.
>> """@en;
>> hydra:property :interest;
>> hydra:collectionProperty :interestCollection;
>> hydra:writeOnly true;
>> hydra:required false
>> ], [
>> a hydra:SupportedProperty;
>> hydra:description """
>> Identifies a Collection containing member references to all UserLikes along with the interest relationships from the Thing to each member of that Collection.
>> """@en;
>> hydra:property :interestCollection;
>> hydra:writeOnly true;
>> hydra:required false
>> ] .
>>
>> :interestCollection a hydra:Link .
>>
>> :InterestCollection a hydra:Class;
>> hydra:supportedOperation [
>> a hydra:CreateResourceOperation;
>> hydra:description "Indicate if you are a fan, like, neutral, dislike or a foe of something."@en;
>> hydra:expects :UserLikes;
>> hydra:method "POST";
>> hydra:returns :UserLikes;
>> hydra:title "Express interest in something"@en
>> ];
>> hydra:supportedProperty [
>> a hydra:SupportedProperty;
>> hydra:multiple false;
>> hydra:property hydra:member;
>> hydra:required false
>> ] .
>>
>> As :interest is not defined as a hydra:Link, but :interestCollection is, an API client will know to retrieve the collection value to get an InterestCollection (defined using rangeIncludes). The two resources that might enable this are the following:
>>
>> # Giants resource
>> </giants> a :SportsTeam;
>> :name "SF Giants";
>> :interestCollection </giants/interests> .
>>
>> # InterestCollection resource
>> </giants/interests> a :InterestCollection, hydra:Collection;
>> hydra:member </giants/interests/gregg>, </giants/interests/markus> .
>> </giants> :interest </giants/interests/gregg>, </giants/interests/markus> .
>>
>> # Interest resources
>> </giants/interests/gregg> a :UserLikes; :likeKind "fan"; :performer </gregg> .
>>
>> </giants/interests/markus> a :UserLikes :likeKind "foe"; :performer </markus> .
>>
>> Now, for the question about how to query such a collection.
>>
>> So, how might I go about finding just the fans of the Giants? Certainly, one way would be to create a different collection property for each variation of the :likeKind property value, and implement server-side logic to perform an appropriate query. However, given the combinatorics of this, it probably makes sense to defer this logic to the API definition. As I understand it, I could create a SupportedProperty in :InterestCollection which is associated with some property with a a domain of :InterestCollection which would allow me to specify such a query. For example:
>>
>> :InterestCollection a hydra:Class;
>> hydra:supportedProperty [
>> a hydra:SupportedProperty;
>> hydra:multiple false;
>> hydra:property hydra:member;
>> hydra:required false
>> ], [
>> a hydra:SupportedProperty;
>> hydra:property :searchInterestCollection
>> ], .
>>
>> :searchInterestCollection a hydra:TemplatedLink;
>> hydra:search [
>> a hydra:IriTemplate;
>> hydra:template "{?collection}?likeKind={?kind}";
>> hydra:mapping [
>> a hydra:IriTemplateMapping;
>> hydra:variable "collection";
>> hydra:property :interestCollection;
>> hydra:required true
>> ], [
>> a hydra:IriTemplateMapping;
>> hydra:variable "kind";
>> hydra:property :likeKind
>> hydra:required true
>> ] .
>>
>> The documentation's a bit scant on TemplatedLinks, but I think this says that :searchInterestCollection is a property of an :InterestCollection. To search the collection, you construct an IRI using the template "{?collection}?likeKind={?kind}". The "collection" variable is bound to the property :interestCollection, although the value of that would be in SportsTeam, not the InterestCollection itself. Perhaps there's a standin for the IRI of the subject. The "kind" variable is bound to the property :likeKind, although that is a property of a :UserLikes (in YS, anyway). Anyway, that property is defined with a range restriction being one of "fan", "foe", etc., so an API client might have some way of knowing what kind of value to place here.
>>
>> Another way to look at search is that a Collection represents a Graph composed of the collection resource, and the referenced member resources. Performing a search, could be thought of as defining a SPARQL query against this graph, which returns a CONSTRUCT based on the members of that graph. So, the searchInterestCollection could be thought of as being like the following query:
>>
>> CONSTRUCT ?member
>> FROM </giants/interests>
>> WHERE {
>> </giants/interests> hydra:member ?member .
>> ?member :likeKind ?kind
>> FILTER ?kind = "fan"
>> }
>>
>> If we think of it this way, then it might be nice to consider doing aggregations as well, for example:
>>
>> SELECT ?kind, COUNT(?member) AS ?count
>> FROM </giants/interests>
>> WHERE {
>> </giants/interests> hydra:member ?member .
>> ?member :likeKind ?kind
>> }
>> GROUP BY ?kind
>>
>> Gregg Kellogg
>> gregg@greggkellogg.net
>>
>>
>
>
Received on Thursday, 24 April 2014 17:58:05 UTC