openactive-archive / opportunity-api

Repository for the Opportunity API specification
0 stars 0 forks source link

Composite Response for Facet Search #5

Open nickevansuk opened 6 years ago

nickevansuk commented 6 years ago

Use Case

To reduce latency and improve user experience, a single search endpoint should be available that returns results of a query that include relevant activities, disabilities, other filtered resource collections as one response.

Straw Man Proposal

Combining the results of the /activities and /sessions responses into one composite response keeps it consistent with each of the resource collection endpoints, however this also leads to confusion in several areas:

Example

GET /search/sessions?latitude=51.5&longitude=-0.2&radius=5
->
{
  "@context": "https://www.openactive.io/ns/oa.jsonld",
  "activity": {
    "type": "Collection",
    "id": "https://example.com/api/activities?latitude=51.5&longitude=-0.2&radius=5",
    "totalItems": 32,
    "items": []
  },
  "sessions": {
    "@context": "https://www.openactive.io/ns/oa.jsonld",
    "id": "https://example.com/api/sessions?latitude=51.5&longitude=-0.2&radius=5",
    "type": "Collection",
    "totalItems": 145,
    "view": {
      "id": "https://example.com/api/sessions?latitude=51.5&longitude=-0.2&radius=5",
      "type": "PartialCollectionView",
      "first": "https://example.com/api/sessions?latitude=51.5&longitude=-0.2&radius=5&page=1",
      "next": "https://example.com/api/sessions?latitude=51.5&longitude=-0.2&radius=5&page=2",
      "last": "https://example.com/api/sessions?latitude=51.5&longitude=-0.2&radius=5&page=3"
    },
    "items": []
  }
}
lukehesluke commented 6 years ago

+1

This would be very useful for us in imin. We are building a search API which supports returning extra contextual information about a search. So, like in your example @nickevansuk , if you do a search for sessions within some set of filters (e.g. ?latitude=51.5&longitude=-0.2&radius=5) it can return not only sessions matching those filters but also activities, accessibility support info, gender restriction info, etc that can be found within that collection of sessions

Clients can then use this extra contextual information to, for example, populate a list of filters for the end-user to choose from, like the left-hand column in Amazon shopping search results (e.g. https://www.amazon.co.uk/s/ref=nb_sb_noss_2?field-keywords=cuckoo+clock)

We would ideally have this all returned by one endpoint as this means just one API call from the client and just one call to our search backend

nickevansuk commented 6 years ago

One potential route here is to use potentialAction, as below:

{
  "@context": "https://www.openactive.io/ns/oa.jsonld",
  "type": "Collection",
  "id": "https://example.com/api/sessions?latitude=0.1232334&longitude=0.23423423",
  "totalItems": 3,
  "potentialAction": {
    "type": "SearchAction",
    "target": "https://example.com/api/sessions?latitude=0.1232334&longitude=0.23423423&activity={activity}",
    "activity-input": {
      "type": "PropertyValueSpecification",
      "valueRequired": false,
      "multipleValues": true,
      "valueName": "activity",
      "name": "Activity",
      "possibleValue": {
        "type": "Collection",
        "id": "https://example.com/api/activities?latitude=0.1232334&longitude=0.23423423",
        "totalItems": 2,
        "items": [
          {
            "type": "Concept",
            "id": "http://openactive.io/activity-list/#d5f34cb1-35c0-46e5-ad6d-181f77274640",
            "identifier": "d5f34cb1-35c0-46e5-ad6d-181f77274640",
            "prefLabel": "Cardiovascular Classes",
            "count": 67
          }
        ]
      }
    }
  }
}

Note that identifier is used here to remove the need for API users to parse the "id" (as only the latter segment of the id needs to be passed as in for the parameter).

The above is consistent because the potentialActions are available on the object "https://example.com/api/sessions?latitude=0.1232334&longitude=0.23423423", which means that the potential actions are restricted to searchActions that are available on that object (i.e. faceted)

This also provides the API user with a URI template they can use to construct the API call to add that facet to the search (though in reality they would want a construct the API call programatically to allow users to add and remove filters via a UI). So there's a question about whether the URI template is likely to actually be used.

nickevansuk commented 6 years ago

Using the above the possibleValues could only be included if a parameter e.g. enumeratePossibleValue=activity,genderRestriction was present (to save bandwidth on mobile)

lukehesluke commented 6 years ago

@nickevansuk I do like that the root object is now the sessions collection as that makes sense conceptually. The facets are an accompaniment to the sessions, but the collection is still ostensibly the sessions. This also puts the view at the root level, which makes sense as the paging data encapsulated in that object is relevant to the collection as a whole, rather than some subset of the collection - if you move to page 2, you're moving to page 2 of the sessions, not the facets

However, it seems odd to have the facets enumerated in a potentialAction. The facets are an extra piece of data that augments the sessions collection

I wonder if it's worth changing the Collection spec to allow .facets, which is an object with keys equal to {facetName}-facet and values equal to Collections e.g.

{
  "@context": "https://www.openactive.io/ns/oa.jsonld",
  "type": "Collection",
  "id": "https://example.com/api/sessions?latitude=0.1232334&longitude=0.23423423",
  "totalItems": 3,
  "facets": {
    "activity-facet": {
      "type": "Collection",
      // Note that I haven't used an existing /api/activities endpoint for this as it's specifically the activities
      // that belong to sessions satisfying this query. Any facilities' activities wouldn't be included in this
      "id": "https://example.com/api/sessions?latitude=0.1232334&longitude=0.23423423#!/activites"
      "totalItems": 1,
      "items": [{
        "type": "Concept",
        "id": "http://openactive.io/activity-list/#d5f...",
        "identifier": "d5f...",
        "prefLabel": "Swimming",
        "count": 3
      }]
    },
    "genderRestriction-facet": {
      // ...etc
    }
  },
  // ...etc
}
lukehesluke commented 6 years ago

And @nickevansuk agreed that some logic similar to https://github.com/openactive/opportunity-api/issues/3 should be worked out for excluding facets from response when not wanted