Hydra and SHACL - a Perfect Couple - Part 1
Hydra Core is a community-driven specification for describing hypermedia APIs in a machine readable form so that client applications can discover the resources at runtime. On its own, however, it is not expressible enough to describe any arbitrary resource representation. SHACL, or Shapes Constraint Language, on the other hand is a beautifully extensible schema-like language which offers great power and flexibility in describing graph data structures. Combined, they provide a complete solution for building hypermedia applications driven by RDF.
TL;DR; I want some action!
Click the image to open Shaperone Playground, which demonstrates a working example of a form generated from a SHACL shape which dynamically loads Wikidata resources using SPARQL.
At the bottom of this post you will see how to configure shaperone this way.
Hydra HTTP request descriptions
The Hydra vocabulary defines a term
hydra:Operation which represents a HTTP request which a server advertises as being supported by specific resources, either by a specific instance or entire class of resources.
For the sake of this blog post, let’s consider a hypothetical API which describes a registration request:
The above snippet, excerpt from the API’s Documentation resource, declares that the clients will come across a collection of users (
rdf:type <UserCollection>) against which a POST request will be possible to create a new resource. That operation will require a representation of the
While Hydra Core vocabulary does have a basic set of terms which can describe the user class, it may not be enough to cater for rich client-server interactions as well as a UI building block. Neither will be RDFS, and OWL, although quite powerful, is a little complex and seriously lacks tooling support and widespread recognition.
Using SHACL to describe API payloads
SHACL is another RDF vocabulary, which describes data graphs by constraining properties and values of precisely targeted nodes in an RDF graph. It could be used to complement the API Documentation graph above by providing the required shape of instances of the
<User> class. This is easiest done by turning it into an implicitly targeted
In this example let’s require users to provide exactly one name (using
schema:name) and exactly one country of citizenship (using said Wikidata property P27)
Hopefully this is quite self-explanatory so far.
- The objects of
sh:propertyrequire that any instance of
<User>have exactly one of each property, declared using
sh:path. That is achieved using
- Name must be at least 3 characters long string
- Country must be an instance of Wikidata Country class
- Exactly one country is allowed
sh:orderis a UI hint for organising inputs in a form
dash:singleLineis a form builder hint which ensures that the text field does not allow line breaks (ie. no
dash:editorinstructs the form builder to create an input component with a selection of instances of the desired RDF type
SHACL is quite wonderful in that shapes are useful for many purposes. Check the SHACL Use Cases and Requirements note for a host of examples. In the presented scenario, a rich client can use to dynamically produce a form to have users input the data, and the server will run validations to check that requests payloads satisfy the SHACL constraints.
There is one piece missing however: where do the Country instances come from? 🤨
Circling back to Hydra
Out of the box, a SHACL processor would assume that any instances would be part the Data Graph. While this works for validation inside of TopBraid it is not feasible to build a browser application that way. For example, at the time of writing there are 171 instances of Country in Wikidata. Combined with a multitude of labels in various languages that is total of over 40 thousand triples. It’s hardly a good idea to push that proactively to the client up front.
Instead, I propose to connect the Shape back with the API using Hydra Core term
hydra:collection. It is defined modestly:
Collections somehow related to this resource.
It also does not have and
rdfs:domain making it a good candidate for linking a property shape directly with its data source:
1 2 3 4 5 6 7 8 9 10 11 12 13
By adding this property a UI component can load the countries by dereferencing a
hydra:Collection whose representation would look somewhat like this:
APIs are dead; Long live (Linked Data) APIs!
So far the subject was APIs, but the web is more than just servers returning data, even if that data is RDF. You see, the hypothetical registration form above actually references a third party dataset, which is Wikidata. All of this data is already on the web and use standard formats. By using a simple SPARQL query the countries can be fetched directly from their source; without even adding the
/countries resource to your API. Heck, the client appication would not need a dedicated API at all!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
This query can be directly encoded in a URL to GET the countries and populate a dropdown component. You can see that in the playground, mentioned in the beginning.
All possible thanks to web standards 🤘
Shaperone makes building a Hydra-aware form like this easy:
1 2 3 4 5 6 7 8 9 10 11 12 13
@hydrofoil/shaperone-hydra package extends the default behaviour to have
hydra:collection dereferenced rather than looking for the instance data locally.
In future posts I will present how to:
- use Hydra descriptions to find collections without
hydra:searchURI Templates can be used to:
- create forms with dependent fields, so that users first select a country which is then used to narrow down a selection of country’s secondary administrative division and so on POST
- improve performance by filtering resources on the data source