Example application
As a context for this blog post, we'll create a simplified data schema for an issue tracking application.
An issue tracking application typically lets customers reports issues, and someone in the organization is selected as a responsible contact for the follow-up and ultimately resolving the reported issue.
The illustration below shows the three main objects of our application.
This simplified model has two explicit relations from the issues object:
- An issue is linked to one customer
- An issue has one responsible
The model also has two implicit relations:
- A customer can have many issues
- A responsible can have many issues
Our simple data model reveals that there are four object/relations that must be maintained in our issue tracking application.
Creating a schema with relations
With the example above in mind, lets go ahead and show how we can create data consistency and flexibility by using relations in our JSON documents.
Just as in a traditional relational database we'll start by creating one collection (table) for each of our main objects.
In restdb.io development mode, we add the three collections by cliking on the "Add Collection" button. The screen shot below shows the result:
For this example we'll also add some random data using the random data generator.
After running the random data generator on each collection, our database now has these populated data collections.
The customers collection:
[
{
"_id": "58feea7ebcb53e0d000009ca",
"customer-name": "Von Group"
},
{
"_id": "58feea7ebcb53e0d000009cb",
"customer-name": "Tromp Inc"
}
...
]
And the issues collection:
[
{
"_id": "58feeab2bcb53e0d000009cc",
"title": "Printer ink",
"description": "It's empty in 2. floor. Please fix asap!"
},
{
"_id": "58feeae1bcb53e0d000009d0",
"title": "Sloow network",
"description": "Everything just hangs before log in."
}
...
]
And the responsible collection:
[
{
"_id": "58feea60bcb53e0d000009b8",
"name": "Hane.Dante"
},
{
"_id": "58feea60bcb53e0d000009c1",
"name": "Modesta_Grady"
},
...
]
Creating a relation with the REST API
Adding relations to objects are done with a REST API call.
In this example we'll use the PUT verb to add a relation to a customer and a responsible on a specific issue. We do this by providing the ObjectID's for the particular customer and responsible documents:
PUT /rest/issues/58feeab2bcb53e0d000009cc
{
"title": "Printer ink",
"description": "It's empty in 2. floor. Please fix asap!",
"customer": ["58feea7ebcb53e0d000009ca"],
"responsible": ["58feea60bcb53e0d000009c1"]
}
After validation the document, the database will produce a result for the modified issue document with an embedded copy of the customer and the responsible documents:
{
"_id": "58feeab2bcb53e0d000009cc",
"title": "Printer ink",
"description": "It's empty in 2. floor. Please fix asap!",
"customer": [
{
"_id": "58feea7ebcb53e0d000009ca",
"customer-name": "Von Group",
"_created": "2017-04-25T06:19:42.875Z"
}
],
"responsible": [
{
"_id": "58feea60bcb53e0d000009c1",
"name": "Modesta_Grady"
"_created": "2017-04-25T06:19:12.182Z"
}
]
}
Note that a relation is always represented as an array of objects.
What happened here?
For each reference to an object (i.e. relation), the database will keep track of any changes in the original document.
On a change in the original document, e.g. the customer name, the database will also update all copies of that instance. Hence keeping data consistency and enabling fast atomic queries.
Who's looking at me?
The examples above has shown how issues can have embedded copies (relations) of both customers and responsibles. This is great for speed and consistency.
But how do we find the (implicit) issues that belongs to a specific customer, or which issues a specific responsible person has?
Well, since the database keeps track of all relations, it can also tell us where a specific document is used in a relation.
The example queries below shows how you can track the implicit references to a document by using a special query parameter - referencedby=true
.
By using this parameter in a query, the database will return the full graph for all other documents that has a relation to the document in the query.
E.g. Find all issues for the customer 58feea7ebcb53e0d000009ca
:
/rest/customers/58feea7ebcb53e0d000009ca?referencedby=true
Result is a full graph of the issues for this customer.
{
"_id": "58feea7ebcb53e0d000009ca",
"customer-name": "Von Group"
"_referencedby": [
{
"collection": "issues",
"references": [
{
"_id": "58feeab2bcb53e0d000009cc",
"title": "Printer ink",
"description": "It's empty in 2. floor. Please fix asap!",
"customer": [
{
"_id": "58feea7ebcb53e0d000009ca",
"customer-name": "Von Group",
"_created": "2017-04-25T06:19:42.875Z"
}
],
"responsible": [
{
"_id": "58feea60bcb53e0d000009c1",
"name": "Modesta_Grady",
"email": "Arno.King@gmail.com",
"_created": "2017-04-25T06:19:12.182Z"
}
],
"_created": "2017-04-25T06:20:34.441Z",
"_changed": "2017-04-25T06:20:34.441Z",
"_version": 0
},
{
"_id": "58feeae1bcb53e0d000009d0",
"title": "Sloow network",
"description": "Everything just hangs before log in.",
"customer": [
{
"_id": "58feea7ebcb53e0d000009ca",
"customer-name": "Von Group",
"_created": "2017-04-25T06:19:42.875Z"
}
],
"responsible": [
{
"_id": "58feea60bcb53e0d000009c1",
"name": "Modesta_Grady",
"email": "Arno.King@gmail.com",
"_created": "2017-04-25T06:19:12.182Z"
}
],
"_created": "2017-04-25T06:21:21.390Z",
"_changed": "2017-04-25T06:21:21.390Z",
"_version": 0
}
]
}
]
}
Find all issues for a specific responsible. The same format is returned here.
/rest/responsible/58feea60bcb53e0d000009c1?referencedby=true
{
"_id": "58feea60bcb53e0d000009c1",
"name": "Modesta_Grady"
"_referencedby": [
{
"collection": "issues",
"references": [
...
]
}
]
}
Summary
Objects and relation are a crucial part of any application. By letting the database do the job of maintaining this logic, you will save yourself a ton of work.
At the same time you will keep the speed and flexibility of a document database, combined with the consistency and integrity of a relational database.
Further reading: