Effective mongodb + mongoose. Schema design

database, mongodb, mongoose, node.js

Solution

Disclaimer: I've embarked on this simply as a challenge to myself. See the `<rant>` below for an explanation as to why I disagree with your approach.

First step to getting a solid grasp on No-SQL is throwing out terms like "denormalize" – they simply do not apply in a document based data store. Another important concept to understand is there are no `JOINS` in MongoDB, so you have to change the way you think about your data completely to adjust.

The best way to solve your problem with `mongoose` is to setup collections for Quotes and Items separately. Then we can set up references between these collections to "link" the documents together.

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var quoteSchema = new Schema({
  items: [{ type: Schema.Types.ObjectId, ref: 'Item' }]
});

var itemSchema = new Schema({});

That handles your Quotes -> Items "relationship". To get the Orders setup, you could use an array of embedded documents like you've indicated, but if you ever decided to start querying/indexing Orders, you'd be up a certain creek without a paddle. Again, we can solve this with references:

var itemSchema = new Schema({
  orders: [{ type: Schema.Types.ObjectId, ref: 'Order' }]
});

var orderSchema = new Schema({
  quarter: String
});

Now you can use population to get what you need:

Item
  .findById(id)
  .populate({
    path: 'orders',
    match: { quarter: 'Q1-14' }
  })
  .exec(function (err, item) {

    console.log(item.orders); // logs an array of orders from Q1-14

  });

Trouble with references is that you are actually hitting the DB with a read instruction twice, once to find the parent document, and then once to populate its references.

You can read more about refs and population here: http://mongoosejs.com/docs/populate.html

<rant>

I could go on for hours why you should stick to an RDBMS for this kind of data. Especially when the defense for the choice is a lack of an ORM and Mongo being "all the rage." Engineers pick the best tech for the solution, not because a certain tech is trending. Its the difference between weekend hackery and creating Enterprise level products. Don't get me wrong, this is not to trash No-SQL – the largest codebase I maintain is built on NodeJS and MongoDB. However, I chose those technologies because they were the right technologies for my document based problem. If my data had been a relational ordering system like yours, I'd ditch Mongo in a heartbeat.

</rant>

Problem

I'm new to mongodb and nosql databases. I would really appreciate some input/help with my schema design so I don't shoot myself in the foot. Data: I need to model `Quote`s. A `Quote` contains many `Ttem`s. Each `Item` contains many `Order`s. Each `Order` is tied to a specific fiscal quarter. Ex. I have a `Quote` containing an `Item` which has `Order`s in Q3-14, Q4-14, Q1-15. `Order`s only go max 12 quarters (3 years) into the future. Specifically, I'm having trouble with modelling the `Order`-quarter binding. I'm trying to denormalize the data and embed `Quote` <- `Item`s <- `Order`s for performance. Attempts/Ideas: - Have an `Order` schema containing `year` and `qNum` fields. Embed an array of `Orders` in every `Item`. Could also create virtual `qKey` field for setting/getting via string like `Q1-14` - Create a hash that embeds a `Order`s into an `Item` using keys like `Q1-14`. This would be nice, but isn't supported natively in Mongoose. - Store the current (base) quarter in each `Quote`, and have each `Item` contain an array of `Order`s, but have them indexed by #quarters offset from the base quarter. I.e. if It's currently Q1-14, and an order comes in for Q4-14, store it in array position 2. Am I totally off the marker? Any advice is appreciated as I struggle to use Mongo effectively. Thank you

Original source