Description: This tutorial discusses relationships between linked data entities and how the concepts of JSON-LD and NGSI-LD can be used to interrogate entities and navigate from one entity to another. The tutorial discusses a series of simple linked-data data models based around the supermarket chain’s store finder application, and demonstrates how to design models holding one-to-one, one-to-many and many-to-many relationships. This NGSI-LD tutorial is a direct analogue to the earlier Understanding Entities and Relationships tutorial (which was based on the NGSI v2 interface). The differences in relationships created using NGSI v2 and NGSI-LD are highlighted and discussed in detail.
The tutorial uses cUrl commands throughout, but is also available as Postman documentation.
Relationships in Linked Data
“It’s hard to communicate anything exactly and that’s why perfect relationships between people are difficult to find.”
― Gustave Flaubert, L'Éducation sentimentale
All NGSI data entity attributes can be divided into one of two types.
- Property attributes.
- Relationship attributes.
For each entity, the Property attributes (including various subtypes such as GeoProperty , TemporalProperty and
time values) define the current state something in the real world. As the state of the entity changes the value
of
each Property is updated to align with the last real world reading of the attribute. All Property attributes relate
to the state of a single entity.
Relationship attributes correspond to the interactions between entities (which are expected to change over time).
They effectively provide the graph linking the nodes of the data entities together. Each Relationship attribute holds
an object
in the form of a URN - effectively a pointer to another object. Relationship attributes do not hold data
themselves.
Both properties and relationships may in turn have a linked embedded structure (of properties-of-properties or properties-of-relationships or relationships-of-properties or relationships-of-relationships etc.) which lead a full complex knowledge graph.
Designing Data Models using JSON-LD
In order for computers to be able to navigate linked data structures, proper ontologically correct data models must be
created and a full @context
must be defined and made accessible. We can do this by reviewing and updating the existing
data models from the NGSI v2 Entity Relationships tutorial.
Revision: Data Models for a Stock management system as defined using NGSI-v2
As a reminder, four types of entity were created in the NGSI v2 stock management system. The relationship between the four NGSI v2 entity models was defined as shown below:
More details can be found in the NGSI v2 Entity Relationships tutorial.
In NGSI v2 relationship attributes are just standard properties attributes. By convention NGSI v2 relationship
attributes are given names starting ref
and are defined using the type="Relationship"
. However, this is merely
convention and may not be followed in all cases. There is no infallible mechanism for detecting which attributes are
associative relationships between entities.
Data Models for a Stock management system defined using NGSI-LD
The richer JSON-LD description language is able to define NGSI-LD entities by linking entities directly as shown below.
The complete data model must be understandable by both developers and machines.
- A full Human readable definition of this data model can be found online.
- The machine-readable JSON-LD definition can be found at
http://context/user-context.jsonld
- this file will be used to provide the@context
to power our NGSI-LD data entities.
Four data models have been created for this NGSI-LD stock management system. The relationships between the models are described below:
- The Store model is now based on and extends the
FIWARE Building model. This ensures that it offers
standard properties for
name
,address
and category:- A Building will hold
furniture
this is a 1-many relationship.- Building ➡ Shelf.
- A Building will hold
- The Shelf model is a custom data model defined
for the tutorial:
- Each Shelf is
locatedIn
a Building. This is a 1-1 relationship. It is the reciprocal relationship tofurniture
defined above:- Shelf ➡ Building.
- A Shelf is
installedBy
a Person - this is a 1-1 relationship. A shelf knows who installed it, but it is this knowledge is not part of the Person entity itself:- Shelf ➡ Person.
- A Shelf
stocks
a given Product. This is another 1-1 relationship, and again it is not reciprocated. A Product does not know which Shelf it is to be found on:- Shelf ➡ Product.
- Each Shelf is
- A StockOrder model replaces the
Inventory Item bridge table defined for NGSI v2:
- A StockOrder is
requestedBy
a Person - this is a 1-1 relationship.- StockOrder ➡ Person.
- A StockOrder is
requestedFor
a Building - this is a 1-1 relationship.- StockOrder ➡ Building.
- A StockOrder is a request for a specific
orderedProduct
- this is a 1-1 relationship.- StockOrder ➡ Product.
- A StockOrder is
- The Product model remains unchanged. It has no relationships of its own.
Additionally, some relationships have been defined to be linked to https://schema.org/Person
entities. This could be
outlinks to a separate HR system for example.
Comparison between Linked and Non-Linked Data Systems
Obviously within a single isolated Smart System itself, it makes no difference whether a rich, complex linked-data architecture is used or a simpler, non-linked-data system is created. However, if the data is designed to be shared, then linked data is a requirement to avoid data silos. An external system is unable to "know" what relationships are unless they have been provided in a machine-readable form.
Video: Rich Snippets: Product Search
A simple example of an external system interrogating for structured data can be found in online product search. Machines from third parties such as Google are able to read product information (encoded using a standard Product data model) and display a rich snippet of product information with a standard star rating.
Click on the image above to watch an introductory video on rich snippets for product search.
Further machine-readable data model examples can be found on the Steal Our JSON-LD site.
Traversing relationships
Example: Imagine the scenario where a pallet of Products are moved from stock in the warehouse (
stockCount
) onto the shelves of the store (storeCount
) . How would NGSI v2 and NGSI-LD computations differ?
Relationships without Linked Data
Without linked data, there is no machine-readable way to connect entities together. Every data relationship must be known in advanced somehow. Within an isolated Smart System this is not an issue, since the architect of the system will know in advance what-connects-to-what.
For example in the simple NGSI v2 Entity Relationships tutorial, a convenience bridge table InventoryItem entity had
been created specifically to hold both count on the shelf and count in the warehouse in a single entity. In any
computation only the InventoryItem entity would be involved. The stockCount
value would be decremented and the
shelfCount
value would increment. In the NGSI v2 model both the storeCount
and the shelfCount
have been placed
into the conceptual InventoryItem Entity. This is a necessary workaround for NGSI v2, and it allows for simpler data
reading and data manipulation. However technically it is ontologically incorrect, as there is no such thing as an
InventoryItem in the real world, it is really two separate ledgers, products bought for the store and products sold
on the shelf, which in turn have an indirect relationship.
Since the entity data is not yet machine-readable externally, the programmer is free to design models as she sees fit and can decide to update two attributes of one InventoryItem Entity or two separate attributes on two separate Shelf and StockOrder entities without regards whether these really are real concrete items in the real world. However, this means external systems cannot discover information for themselves and must be pre-programmed to know where information is held.
Relationships with Linked Data
With a well-defined data model using linked data, every relationship can be predefined in advance and is discoverable.
Using JSON-LD concepts (specifically @graph
and @context
) it is
much easier for computers to understand indirect relationships and navigate between linked entities. Due to these
additional annotations it is possible to create usable models which are ontologically correct and therefore Shelf
can now be directly assigned a numberOfItems
attribute and bridge table concept is no longer required. This is
necessary as other systems may be interrogating Shelf directly.
Similarly, a real StockOrder Entity can be created which holds a entry of which items are currently on order for
each store. This is a proper context data entity as stockCount
describes the current state of a product in the
warehouse. Once again this describes a single, real world entity and is ontologically correct.
Unlike the NGSI v2 scenario, with linked data, it would be possible for an external system to discover relationships
and interrogate our Supermarket. Imagine for example, an
Autonomous Mobile Robot
system which is used to move a pallet of products onto a shelf it would be possible for this external system to
"know" about our supermarket by navigating the relationships in the linked data the @graph
from StockOrder to
Shelf as shown:
- Some
product:XXX
items have been removed fromstockOrder:0001
- decrementstockCount
. - Interrogating the StockOrder is discovered that the Product is
requestedFor
for a specific URI e.g.store:002
.
{
"@graph": [
{
"@id": "tutorial:orderedProduct",
"@type": "https://uri.etsi.org/ngsi-ld/Relationship",
"schema:domainIncludes": [
{
"@id": "tutorial:StockOrder"
}
],
"schema:rangeIncludes": [
{
"@id": "tutorial:Product"
}
],
"rdfs:comment": "The Product ordered for a store",
"rdfs:label": "orderedProduct"
},
...etc
]
}
- It is also discovered from the StockOrder model that the
requestedFor
URI defines a Building.
{
"@graph": [
{
"@id": "tutorial:requestedFor",
"@type": "https://uri.etsi.org/ngsi-ld/Relationship",
"schema:domainIncludes": [
{
"@id": "tutorial:StockOrder"
}
],
"schema:rangeIncludes": [
{
"@id": "fiware:Building"
}
],
"rdfs:comment": "Store for which an item is requested",
"rdfs:label": "requestedFor"
},
...etc
]
}
- It is discovered from the Building model that every Building contains
furniture
as an array of URIs. - It is discovered from the Building model that these URIs represent Shelf units.
{
"@graph": [
{
"@id": "tutorial:furniture",
"@type": "https://uri.etsi.org/ngsi-ld/Relationship",
"schema:domainIncludes": [
{
"@id": "fiware:Building"
}
],
"schema:rangeIncludes": [
{
"@id": "tutorial:Shelf"
}
],
"rdfs:comment": "Units found within a Building",
"rdfs:label": "furniture"
},
...etc
]
}
- It is discovered from the Shelf model that the
stocks
attribute holds a URI representing Product items.
{
"@graph": [
{
"@id": "tutorial:stocks",
"@type": "https://uri.etsi.org/ngsi-ld/Relationship",
"schema:domainIncludes": [
{
"@id": "tutorial:Shelf"
}
],
"schema:rangeIncludes": [
{
"@id": "tutorial:Product"
}
],
"rdfs:comment": "The product found on a shelf",
"rdfs:label": "stocks"
},
...etc
]
}
- A request the Shelf unit which holds the correct Product for the
stocks
attribute is made and the ShelfnumberOfItems
attribute can be incremented.
Through creating and using standard data models and describing the linked data properly, it would not matter to the
robot if the underlying system were to change, provided that the Properties and Relationships resolve to fully qualified
names (FQNs) and a complete @graph
. For example the JSON short name attributes could be amended or the relationships
redesigned but their real intent (which resolves to a fixed FQN) could still be discovered and used.
Architecture
The demo application will send and receive NGSI-LD calls to a compliant context broker. Since both NGSI v2 and NGSI-LD interfaces are available to an experimental version fo the Orion Context Broker, our demo application will only make use of one FIWARE component.
Currently, the Orion Context Broker relies on open source MongoDB technology to keep persistence of the context data it holds. Therefore, the architecture will consist of two elements:
- The Orion Context Broker which will receive requests using NGSI-LD.
- The underlying MongoDB database :
- Used by the Orion Context Broker to hold context data information such as data entities, subscriptions and registrations.
Since all interactions between the two elements are initiated by HTTP requests, the elements can be containerized and run from exposed ports.
The necessary configuration information can be seen in the services' section of the associated docker-compose.yml
file:
orion:
image: quay.io/fiware/orion-ld
hostname: orion
container_name: fiware-orion
depends_on:
- mongo-db
networks:
- default
ports:
- '1026:1026'
command: -dbhost mongo-db -logLevel DEBUG
healthcheck:
test: curl --fail -s http://orion:1026/version || exit 1
mongo-db:
image: mongo:4.2
hostname: mongo-db
container_name: db-mongo
expose:
- '27017'
ports:
- '27017:27017'
networks:
- default
command: --nojournal
Both containers are residing on the same network - the Orion Context Broker is listening on Port 1026
and MongoDB is
listening on the default port 27017
. Both containers are also exposing the same ports externally - this is purely for
the tutorial access - so that cUrl or Postman can access them without being part of the same network. The command-line
initialization should be self-explanatory.
The only notable difference to the introductory tutorials is that the required image name is currently
fiware/orion-ld
.
Start Up
All services can be initialised from the command-line by running the services Bash script provided within the repository. Please clone the repository and create the necessary images by running the commands as shown:
#!/bin/bash
git clone https://github.com/FIWARE/tutorials.Relationships-Linked-Data.git
cd tutorials.Relationships-Linked-Data
git checkout NGSI-v2
./services orion|scorpio|stellio
Note: If you want to clean up and start over again you can do so with the following command:
./services stop
Creating and Associating Data Entities
Reviewing existing entities
On start up, the system is brought up with a series of Building, Product and Shelf entities already present. You can query for them using the requests below. In each case only the Properties of the entities have been created.
To avoid ambiguity, computers prefer to use unique IDs when referring to well-defined concepts. For each of the NGSI-LD
entities returned, the names of the attributes received can be defined as either as a fully qualified name (FQN) or as
simple JSON attributes dependent upon whether the associated Link
header connecting the NGSI-LD Data Entity to the
computer readable JSON-LD @context
Data Models is included in the request.
Display all Buildings
The Stores of the supermarket have been created using the Smart data
Building model and the enumerated value of this type is
fiware:Building
which expands to https://uri.fiware.org/ns/datamodels%23Building
. It is therefore possible to
request all building entities without supplying a known context.
1 Request:
curl -G -X GET \
'http://localhost:1026/ngsi-ld/v1/entities' \
-H 'Accept: application/ld+json' \
-d 'type=https%3A%2F%2Furi.fiware.org%2Fns%2Fdata-models%23Building' \
-d 'options=keyValues'
Response:
Tip: Use jq to format the JSON responses in this tutorial. Pipe the result by appending
| jq '.'
The response returns all the existing Building entities, with the attributes expanded as fully qualified names (FQNs).
[
{
"@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.6.jsonld",
"id": "urn:ngsi-ld:Building:store001",
"type": "https://uri.fiware.org/ns/datamodels#Building",
"https://schema.org/name": "Bösebrücke Einkauf",
"https://schema.org/address": {
"streetAddress": "Bornholmer Straße 65",
"addressRegion": "Berlin",
"addressLocality": "Prenzlauer Berg",
"postalCode": "10439"
},
"https://uri.fiware.org/ns/data-models#category": {
"vocab": "https://wiki.openstreetmap.org/wiki/Tag:building%3Dcommercial"
},
"location": {
"type": "Point", "coordinates": [13.3986, 52.5547]
}
},
{
"@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.6.jsonld",
"id": "urn:ngsi-ld:Building:store002",
"type": "https://uri.fiware.org/ns/datamodels#Building",
"https://schema.org/name": "Checkpoint Markt",
"https://schema.org/address": {
"streetAddress": "Friedrichstraße 44",
"addressRegion": "Berlin",
"addressLocality": "Kreuzberg",
"postalCode": "10969"
},
"https://uri.fiware.org/ns/data-models#category": {
"vocab": "https://wiki.openstreetmap.org/wiki/Tag:building%3Dcommercial"
},
"location": {
"type": "Point", "coordinates": [13.3903, 52.5075]
}
},
... etc
]
According to the defined data model:
- The
type
attribute has the FQNhttps://uri.etsi.org/ngsi-ld/type
. - The
name
attribute has the FQNhttps://uri.etsi.org/ngsi-ld/name
. - The
location
attribute has the FQNhttps://uri.etsi.org/ngsi-ld/location
. - The
address
attribute has the FQNhttp://schema.org/address
. - The
category
attribute has the FQNhttps://uri.fiware.org/ns/datamodels#category
.
type
, name
and location
are defined in the NGSI-LD Core Context:
https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.6.jsonld
.
The other attributes are defined using the Tutorial's own Context:
http://context/user-context.jsonld
.
Both category
and address
are common attributes the definitions of which are brought in from the FIWARE data
models and schema.org
respectively.
Display all Products
Requesting the Product entities can be done by supplying the FQN of the entity type
in the request as well.
2 Request:
curl -G -X GET \
'http://localhost:1026/ngsi-ld/v1/entities' \
-d 'type=https://fiware.github.io/tutorials.Step-by-Step/schema/Product' \
-d 'options=keyValues' \
-H 'Link: <http://context/user-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"'
Response:
However since the full context has been supplied in the Link
header, the short names are returned.
[
{
"id": "urn:ngsi-ld:Product:001",
"type": "Product",
"name": "Apples",
"price": 0.99,
"size": "S"
},
{
"id": "urn:ngsi-ld:Product:002",
"type": "Product",
"name": "Bananas",
"price": 10.99,
"size": "M"
},
.. etc
]
According to the defined data model:
- The
type
attribute has the FQNhttps://uri.etsi.org/ngsi-ld/type
. - The
name
attribute has the FQNhttps://uri.etsi.org/ngsi-ld/name
. - The
price
attribute has the FQNhttps://fiware.github.io/tutorials.Step-by-Step/schema/price
. - The
size
attribute has the FQNhttps://fiware.github.io/tutorials.Step-by-Step/schema/size
. - The
currency
attribute has the FQNhttps://fiware.github.io/tutorials.Step-by-Step/schema/currency
.
The programmatic implementation of the Product model and its attributes are fully described in the
http://context/user-context.jsonld
.
Display all Shelves
Requesting the Product entities can be done by supplying the short of the entity type
in the request as well,
provided the full context has been supplied in the Link
header.
3 Request:
curl -G -X GET \
'http://localhost:1026/ngsi-ld/v1/entities' \
-d 'type=Shelf' \
-d 'options=keyValues' \
-H 'Link: <http://context/user-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"'
Response:
Once again the short names are returned.
[
{
"id": "urn:ngsi-ld:Shelf:unit001",
"type": "Shelf",
"name": "Corner Unit",
"maxCapacity": 50,
"location": {
"type": "Point", "coordinates": [13.398611, 52.554699]
}
},
{
"id": "urn:ngsi-ld:Shelf:unit002",
"type": "Shelf",
"name": "Wall Unit 1",
"maxCapacity": 100,
"location": {
"type": "Point", "coordinates": [13.398722, 52.554664]
}
},
... etc
]
According to the defined data model:
- The
type
attribute has the FQNhttps://uri.etsi.org/ngsi-ld/type
. - The
name
attribute has the FQNhttps://uri.etsi.org/ngsi-ld/name
. - The
location
attribute has the FQNhttps://uri.etsi.org/ngsi-ld/location
. - The
maxCapacity
attribute has the FQNhttps://fiware.github.io/tutorials.Step-by-Step/schema/maxCapacity
. - The
numberOfItems
attribute has the FQNhttps://fiware.github.io/tutorials.Step-by-Step/schema/numberOfItems
.
The programmatic implementation of the Shelf model and its attributes are fully described in the
http://context/user-context.jsonld
.
Obtain Shelf Information
Initially each shelf is created with name
, maxCapacity
and location
Properties only. A sample shelf is requested
below.
4 Request:
curl -G -X GET \
'http://localhost:1026/ngsi-ld/v1/entities/urn:ngsi-ld:Shelf:unit001/' \
-d 'options=keyValues' \
-H 'Link: <http://context/user-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"'
Response:
The short names have been returned since the @context
has been supplied in the Link
header.
{
"@context": "http://context/user-context.jsonld",
"id": "urn:ngsi-ld:Shelf:unit001",
"type": "Shelf",
"name": "Corner Unit",
"maxCapacity": 50,
"location": {
"type": "Point",
"coordinates": [13.398611, 52.554699]
}
}
Creating Relationships
To complete the data model within the data model, various additional Properties and Relationships need to be added to the entity.
A Shelf holds a numberOfItems
- this is a Property
of the Shelf and contains a value
representing the
number of Items. The value
of this Property (i.e. the number of Items will change over time). Properties have been
covered in a previous tutorial and will not be covered in detail here.
A Shelf stocks
a given Product - this is a Relationship
of the Shelf Only the URN of the product is
known by the Shelf entity - effectively it points to further information held elsewhere.
To distinguish Relationships, they must be given type="Relationship"
and each Relationship has must have an
object
sub-attribute, this contrasts with Properties which must a type="Property"
have a value
attribute. The
object
sub-attribute holds the reference to the related entity in the form of a URN.
A Shelf is locatedIn
a given Building. Once again this is a Relationship
of the Shelf. The URN of the
Building is known by the Shelf entity, but further information is also available:
locatedIn[requestedBy]
is a Relationship-of-a-Relationship, this sub-attribute in turn holds anobject
attribute of its own pointing to a Person.locatedIn[installedBy]
is a Relationship-of-a-Relationship, this sub-attribute in turn holds anobject
attribute of its own pointing to a Person.locatedIn[statusOfWork]
is a Property-of-a-Relationship, this sub-attribute in turn holds anvalue
attribute holding the current status of thelocatedIn
action.
As you can see, it is possible to embed further Properties (with a corresponding value
) or Relationships (with a
corresponding object
) inside the entity structure to provide a rich graph of information.
Adding 1-1 Relationships
Within the @context
a Shelf has been predefined with two relationships. (stocks
and locatedIn
).
To create a relationship add a new attribute with type=Relationship
and an associated object attribute. Metadat about
the relationships (e.g. requestedBy
, installedBy
)can be created by adding subattributes to the relationship. The
value of object is the URN corresponding to the linked data entity.
Note that the relationship is currently unidirectional. Shelf ➡ Building.
5 Request:
curl -iX POST \
http://localhost:1026/ngsi-ld/v1/entities/urn:ngsi-ld:Shelf:unit001/attrs \
-H 'Content-Type: application/ld+json' \
-d '{
"numberOfItems": {"type": "Property","value": 50},
"stocks": {
"type": "Relationship",
"object": "urn:ngsi-ld:Product:001"
},
"locatedIn" : {
"type": "Relationship", "object": "urn:ngsi-ld:Building:store001",
"requestedBy": {
"type": "Relationship",
"object": "urn:ngsi-ld:Person:bob-the-manager"
},
"installedBy": {
"type": "Relationship",
"object": "urn:ngsi-ld:Person:employee001"
},
"statusOfWork": {
"type": "Property",
"value": "completed"
}
},
"@context": [
"http://context/user-context.jsonld",
"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.6.jsonld"
]
}'
Obtain the Updated Shelf
Having added the additional attributes, it is possible to query for the amended entity.
This example returns the context data of the Shelf entity with the id=urn:ngsi-ld:Shelf:unit001
.
6 Request:
curl -X GET \
http://localhost:1026/ngsi-ld/v1/entities/urn:ngsi-ld:Shelf:unit001
Response:
There are now two additional relationship attributes present stocks
and locatedIn
. Both entries have been expanded
as fully qualified names (FQNs), as defined in the
Shelf Data Model as the Link
header was not
passed in the previous request.
{
"@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.6.jsonld",
"id": "urn:ngsi-ld:Shelf:unit001",
"type": "https://fiware.github.io/tutorials.Step-by-Step/schema/Shelf",
"http://schema.org/name": {
"type": "Property",
"value": "Corner Unit"
},
"https://fiware.github.io/tutorials.Step-by-Step/schema/locatedIn": {
"type": "Relationship",
"object": "urn:ngsi-ld:Building:store001",
"https://fiware.github.io/tutorials.Step-by-Step/schema/installedBy": {
"type": "Relationship",
"object": "urn:ngsi-ld:Person:employee001"
},
"https://fiware.github.io/tutorials.Step-by-Step/schema/requestedBy": {
"type": "Relationship",
"object": "urn:ngsi-ld:Person:bob-the-manager"
},
"https://fiware.github.io/tutorials.Step-by-Step/schema/statusOfWork": {
"type": "Property",
"value": "completed"
}
},
"https://fiware.github.io/tutorials.Step-by-Step/schema/maxCapacity": {
"type": "Property",
"value": 50
},
"https://fiware.github.io/tutorials.Step-by-Step/schema/numberOfItems": {
"type": "Property",
"value": 50
},
"https://fiware.github.io/tutorials.Step-by-Step/schema/stocks": {
"type": "Relationship",
"object": "urn:ngsi-ld:Product:001"
},
"location": {
"type": "GeoProperty",
"value": {
"type": "Point",
"coordinates": [13.398611, 52.554699]
}
}
}
For example, this means that https://fiware.github.io/tutorials.Step-by-Step/schema/locatedIn
is a well-defined
relationship within our linked data JSON-LD schema.
How is the relationship's Fully Qualified Name created ?
One of the central motivations of JSON-LD is making it easy to translate between different representations of what are
fundamentally the same data types. In this case, the shorthand locatedIn
refers to the unique and computer readable
https://fiware.github.io/tutorials.Step-by-Step/schema/locatedIn
.
To do this NGSI-LD uses the two core expansion and compaction algorithms of the underlying JSON-LD model.
Looking at the relevant lines in the JSON-LD @context
:
"tutorial": "https://fiware.github.io/tutorials.Step-by-Step/schema/",
"Shelf": "tutorial:Shelf",
"locatedIn": {
"@id": "tutorial:locatedIn",
"@type": "@id"
},
You can see that tutorial
has been mapped to the string https://fiware.github.io/tutorials.Step-by-Step/schema/
and
locatedIn
has been mapped to tutorial:locatedIn
which using.
Furthermore, locatedIn
has an @type="@id"
which indicates to a computer that its underlying value is a URN.
Video: JSON-LD Compaction & Expansion
Click on the image above to watch a video JSON-LD expansion and compaction with reference to the @context
.
What other relationship information can be obtained from the data model?
More information about Relationships
can be obtained from the @graph
of the linked data model. For locatedIn
the
relevant section definition is as follows:
{
"@id": "tutorial:locatedIn",
"@type": "https://uri.etsi.org/ngsi-ld/Relationship",
"schema:domainIncludes": [{ "@id": "tutorial:Shelf" }],
"schema:rangeIncludes": [{ "@id": "fiware:Building" }],
"rdfs:comment": "Building in which an item is found",
"rdfs:label": "located In"
}
This indicates a lot of additional information about the locatedIn
Relationship in a computer readable fashion:
locatedIn
is really an NGSI-LD relationship (i.e. it has the FQNhttps://uri.etsi.org/ngsi-ld/Relationship
).locatedIn
is only used on Shelf entities.locatedIn
only points to Building entities.locatedIn
can be defined for humans as "Building in which an item is found".locatedIn
can be labelled as "located In" when labelling the Relationship.
Through reading the NGSI-LD data entity and its associated data model, a computer can obtain as much information as a human can from reading the human-readable equivalent data specification:
Find the store in which a specific shelf is located
This example returns the locatedIn
value associated with a given Shelf
unit.
If the id
and type
of a data entity are known, a specific field can be requested by using the attrs
parameter.
7 Request:
curl -G -X GET \
'http://localhost:1026/ngsi-ld/v1/entities/urn:ngsi-ld:Shelf:unit001/' \
-d 'attrs=locatedIn' \
-d 'options=keyValues' \
-H 'Link: <http://context/user-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"'
Response:
{
"@context": "http://context/user-context.jsonld",
"id": "urn:ngsi-ld:Shelf:unit001",
"type": "Shelf",
"locatedIn": "urn:ngsi-ld:Building:store001"
}
Find the IDs of all Shelf Units in a Store
This example returns the locatedIn
URNs of all Shelf entities found within urn:ngsi-ld:Building:store001
. This
is purely an instance of using the q
parameter to filter on attribute value:
8 Request:
curl -G -X GET \
'http://localhost:1026/ngsi-ld/v1/entities/' \
-d 'type=Shelf' \
-d 'options=keyValues' \
-d 'attrs=locatedIn' \
-H 'Accept: application/json' \
-H 'Link: <http://context/user-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"'
Response:
The response contains an array displaying:
[
{
"id": "urn:ngsi-ld:Shelf:unit001",
"type": "Shelf",
"locatedIn": "urn:ngsi-ld:Building:store001"
}
]
Adding a 1-many relationship
To add a 1-many relationship, add an array of Relationship items as the attribute. This can be used for simple links
without additional data. This method is used to add Shelf entities as furniture
in the Store.
This is the reciprocal relationship to the locatedIn
attribute on Shelf:
9 Request:
curl -L -X POST 'http://localhost:1026/ngsi-ld/v1/entities/urn:ngsi-ld:Building:store001/attrs' \
-H 'Content-Type: application/ld+json' \
--data-raw '{
"furniture": [
{
"type": "Relationship",
"datasetId": "urn:ngsi-ld:Relationship:1",
"object": "urn:ngsi-ld:Shelf:001"
},
{
"type": "Relationship",
"datasetId": "urn:ngsi-ld:Relationship:2",
"object": "urn:ngsi-ld:Shelf:002"
}
],
"@context": "http://context/user-context.jsonld"
}'
Finding all shelf units found within a Store
To find all the furniture
within a Building, simply make a request to retrieve the furniture
attribute.
Because the reciprocal relationship already exists, Additional information can be obtained from the Shelf entities themselves.
10 Request:
curl -G -X GET \
'http://localhost:1026/ngsi-ld/v1/entities/urn:ngsi-ld:Building:store001' \
-d 'options=keyValues' \
-d 'attrs=furniture' \
-H 'Accept: application/json' \
-H 'Link: <http://context/user-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"'
Response:
{
"id": "urn:ngsi-ld:Building:store001",
"type": "Building",
"furniture": ["urn:ngsi-ld:Shelf:001", "urn:ngsi-ld:Shelf:002"]
}
Creating Complex Relationships
To create a more complex relationship, and additional data entity must be created which holds the current state of the
links between real world items. In the case of the NGSI-LD data model we have already created, a StockOrder can be
used to link Product, Building and Person entities and the state of the relationships between them. As well
as Relationship attributes, a StockOrder can hold Property attributes (such as the stockCount
) and other more
complex metadata such as Properties-of-Properties or Properties-of-Relationships.
The StockOrder is created as a standard NGSI-LD data entity.
11 Request:
curl -X POST \
http://localhost:1026/ngsi-ld/v1/entities/ \
-H 'Content-Type: application/ld+json' \
-d '{
"id": "urn:ngsi-ld:StockOrder:001",
"type": "StockOrder",
"requestedFor": {
"type": "Relationship",
"object": "urn:ngsi-ld:Building:store001"
},
"requestedBy": {
"type": "Relationship",
"object": "urn:ngsi-ld:Person:bob-the-manager"
},
"orderedProduct": {
"type": "Relationship",
"object": "urn:ngsi-ld:Product:001"
},
"stockCount": {
"type": "Property",
"value": 10000
},
"orderDate": {
"type": "Property",
"value": {
"@type": "DateTime",
"@value": "2018-08-07T12:00:00Z"
}
},
"@context": [
"http://context/user-context.jsonld"
]
}'
Find all stores in which a product is sold
Since Relationship attributes are just like any other attribute, standard q
parameter queries can be made on the
StockOrder to obtain which entity relates to it. For example the query below returns an array of stores in which a
given product is sold.
The query q==orderedProduct="urn:ngsi-ld:Product:001"
is used to filter the entities.
12 Request:
curl -G -X GET \
'http://localhost:1026/ngsi-ld/v1/entities/' \
-d 'type=StockOrder' \
-d 'q=orderedProduct==%22urn:ngsi-ld:Product:001%22' \
-d 'attrs=requestedFor' \
-d 'options=keyValues' \
-H 'Accept: application/json' \
-H 'Link: <http://context/user-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"'
Response:
The response returns an array of requestedFor
attributes in the response.
[
{
"id": "urn:ngsi-ld:StockOrder:001",
"type": "StockOrder",
"requestedFor": "urn:ngsi-ld:Building:store001"
}
]
Find all products sold in a store
The query below returns an array of produts sold in a given store.
The query q==requestedFor="urn:ngsi-ld:Building:store001"
is used to filter the entities.
13 Request:
curl -G -X GET \
'http://localhost:1026/ngsi-ld/v1/entities/' \
-d 'type=StockOrder' \
-d 'q=requestedFor==%22urn:ngsi-ld:Building:store001%22' \
-d 'options=keyValues' \
-d 'attrs=orderedProduct' \
-H 'Accept: application/json' \
-H 'Link: <http://context/user-context.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"'
Response:
The response returns an array of orderedProduct
attributes in the response. This is the reciprocal of the previous
request.
[
{
"id": "urn:ngsi-ld:StockOrder:001",
"type": "StockOrder",
"orderedProduct": "urn:ngsi-ld:Product:001"
}
]
Obtain Stock Order
A complete stock order can be obtained by making a standard GET request to the /ngsi-ld/v1/entities/
endpoint and
adding the appropriate URN.
14 Request:
curl -G -X GET \
'http://localhost:1026/ngsi-ld/v1/entities/urn:ngsi-ld:StockOrder:001' \
-d 'options=keyValues'
Response:
The response returns the fully expanded entity.
{
"@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.6.jsonld",
"id": "urn:ngsi-ld:StockOrder:001",
"type": "https://fiware.github.io/tutorials.Step-by-Step/schema/StockOrder",
"https://fiware.github.io/tutorials.Step-by-Step/schema/orderDate": {
"@type": "DateTime",
"@value": "2018-08-07T12:00:00Z"
},
"https://fiware.github.io/tutorials.Step-by-Step/schema/orderedProduct": "urn:ngsi-ld:Product:001",
"https://fiware.github.io/tutorials.Step-by-Step/schema/requestedBy": "urn:ngsi-ld:Person:bob-the-manager",
"https://fiware.github.io/tutorials.Step-by-Step/schema/requestedFor": "urn:ngsi-ld:Building:store001",
"https://fiware.github.io/tutorials.Step-by-Step/schema/stockCount": 10000
}