Previously I wrote about easily
developing Node.js and Couchbase apps using Ottoman for ODM. That post was just a taste of what Ottoman can do and how you
can compare it to the things Mongoose can do with MongoDB.
This time we’re going to go a step further and see some use cases that makes Ottoman shine.
The Node.js SDK for Couchbase has many different ways to work with documents. CRUD operations, N1QL queries, Couchbase Views, these
are all great things to use, but what happens when your data gets really wild. By wild I mean you have documents with referred
relationships and complex structures. You could use Couchbase Views and CRUD operations, but if your relationships are complex, you’re
going to have a lot of Node.js code for data manipulations. You could use N1QL queries, but with complex relationships you’re going
to be left with huge complicated queries with a lot of joins. What do you do?
Some Modeling Examples
Let’s say we’re working with the following document models. We’re going to assume this application is some kind of project management
applicaiton.
A Project Model
Given that we are creating projects, we’ll not only need information about the project itself, but who is a part of the project. Take the
following Ottoman model as a sample:
1 2 3 4 5 6 7 8 9 10 |
var ProjectMdl = ottoman.model("Project", { name: "string", description: "string", owner: { ref: "User" }, users: [ { ref: "User" } ] }); |
In the above model, the owner
is a different Ottoman model called User, but the users
is an
array of that User model.
A User Model
With the project model in place we need to define the user model in Ottoman. It might look something like this:
1 2 3 4 5 6 7 8 9 |
var UserMdl = ottoman.model("User", { name: { first: "string", last: "string" }, email: "string" }); |
The above user model is just standard string data. There are no references to other models like we saw in the project model. Now we
have to worry about querying these two models in the best fashion.
Querying Deep With Ottoman
Let’s say we wanted to query for projects within our application. This query would typically look something like this:
1 2 3 4 5 6 7 8 |
ProjectMdl.getById("ID_HERE", function(error, project) { if(error) { return res.status(400).send(error); } res.send(project); }); |
The results to this query might look something like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
{ name: "My Project", description: "This is a description to my project", owner: { "_type": "user", "$ref": "1234" }, users: [ { "_type": "user", "$ref": "1234" }, { "_type": "user", "$ref": "4321" } ] } |
What’s wrong here? Well, the query only loaded a shallow amount of data. For example, none of the actual user information loaded,
only the references to those user documents.
So how can we deep query for the other documents? Take a look at this revision to our query:
1 2 3 4 5 6 7 8 |
ProjectMdl.getById("ID_HERE", {load: ["users"]}, function(error, project) { if(error) { return res.status(400).send(error); } res.send(project); }); |
Notice the use of {load: ["users"]}
in this case. This means that we want to load all the user models in the array
when we run our query. How about if we wanted to load the owner documents as well? We would do something like
{load: ["users", "owner"]}
to load them as well.
Complicating the Ottoman Model
Now let’s change our data model a bit. Let’s say our project Ottoman model now looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var ProjectMdl = ottoman.model("Project", { name: "string", description: "string", owner: { ref: "User" }, comments: [ { message: "string", user: { ref: "User" } } ] }); |
Instead of an array of Ottoman models we’re now working with an array of objects that happen to contain an Ottoman model.
Changing our Deep Ottoman Query
With the change to our data model comes a change to our query. It doesn’t make sense to do {load: ["comments"]}
because
comments
is not an Ottoman model. There is nothing to load. Instead we have to change our query to look like this:
1 2 3 4 5 6 7 8 |
ProjectMdl.getById("ID_HERE", {load: ["owner", "comments[*].user"]}, function(error, project) { if(error) { return res.status(400).send(error); } res.send(project); }); |
Notice the use of comments[*].user
above. We are loading the user model that exists in all object elements of the
array.
Conclusion
You just saw how to load child models all within a single query. While very possible using a N1QL query, it would take a few
JOIN
statements to get the job done. It comes down to your preference, but if you enjoy the ODM approach, Ottoman
and the load
option is a great choice.
A full working example of this can be seen in the compiance GitHub project as
well as in a webinar series that I did on full stack development.