Object Relations in a NoSQL Database
Objects and relations 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.
Join the DZone community and get the full member experience.
Join For FreeCompared to a traditional relational database (SQL), a document-oriented (NoSQL) database has poor or non-existent support for relations between objects (data schema). A NoSQL datastore persists and retrieves documents (often in JSON format) and any relationship between your documents is something you must implement yourself.
Without any direct support for relations, you’re stuck with the logic and maintenance of objects/relations in your application layer.
This blog post shows how you can leave the job of maintaining object/relations to a database, effectively giving you time to pick from your backlog instead of adding.
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 for 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, let's 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 clicking on the Add Collection button. The screenshot 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, i.e. 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, i.e. 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 relations 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:
Published at DZone with permission of Jon Erik Solheim, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments