Re: Querying collections

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.
- 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).

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 08:15:21 UTC