Skip to content

Database core module

veljkopopovic edited this page Nov 20, 2014 · 6 revisions

DB core module introduces interfaces and agreements which allows developers to create one or more database connections in order to store data to permanent storage required by their packages. Core will load (using nodejs require function) each and every file located in packages /server/model/ directory. Order of file loading is defined by the operating system, and function used to read directory is documented in nodejs documentation more thoroughly. Requiring a file using nodejs require function gives developer an option to export some functions or variables of interest to external user of required file. In other words, file will be executed and if developer wants something back from that require call, module.exports variable is used. This very convenient mechanism which allows package developer two possibilities. Let's check them out.

Execute model file and give nothing back

This approach gives developer an opportunity to do almost whatever he wants. Default Mongo db connection is created at bare beginning of DB core module creation (details about default db configuration will be explained later) and, due to mongoose node module package properties, available by default after each and every require('mongoose') call at the model file loading time. Developer can simply implement schemata and create models using mongoose API and those will be available to any piece of software via require('mongoose') call. In order to learn more about how to use mongoose, please read the mongoose guide. So, in this regime, developer is one who is responsible for creating schemata and baking models from them, simply load and forget.

There are several notes one should take into consideration with this regime. Most important draw back of this approach is lack of schemata extension possibilities. Once model is created (please, consult mongoose documentation regarding this issue) there is no way for developer to extend that schema used for model with one more field or method or static or any other property. If a extension option is required for your package, this is not a approach that will suite requirements. Here is a simple example: let's thing about articles schema which should store some article information within. So, once model is created within some package, there is no way for developer to install a package which will insert some field in articles schema, for example, longitude and latitude where this article was recorded or tags or some other related data.

On the other side, since developer has full control over model file and nothing is given to MEANIO system to take care of, some other database systems can be introduced as well, fully independent of MEANIO platform using nodejs module system which should provide an API to create schemata, models and DB connections and fetch data from appropriate resources later in package execution time.

A typical model file should look like this:

'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
  Schema = mongoose.Schema;


/**
 * Article Schema
 */
var ArticleSchema = new Schema({
  created: {
    type: Date,
    default: Date.now
  },
  title: {
    type: String,
    required: true,
    trim: true
  },
  content: {
    type: String,
    required: true,
    trim: true
  },
  user: {
    type: Schema.ObjectId,
    ref: 'User'
  }
});

/**
 * Validations
 */
ArticleSchema.path('title').validate(function(title) {
  return !!title;
}, 'Title cannot be blank');

ArticleSchema.path('content').validate(function(content) {
  return !!content;
}, 'Content cannot be blank');


/**
 * methods
 */

ArticleSchema.methods.spacesLessTitle = function () {
  return this.title.replace(/\s/g, '');
};

/**
 * Statics
 */
ArticleSchema.statics.load = function(id, cb) {
  this.findOne({
    _id: id
  }).populate('user', 'name username').exec(cb);
};

mongoose.model('Article', ArticleSchema);

Example is a bit modified version of meanio articles package model implementation. If you're familiar with mongoose, this example is very clear to you.

Execute model file and give structure back

This approach is bit more flexible regarding schemata extension and DB connections on one side, but more restrictive regarding of database system itself on the other: mongodb is the only solution (for now).

Previous approach does not set any module.exports variable so nothing is returned to require function caller. That way, MEANIO core has no info about what your file did within it and if developer wanted something from core. Since there is no info, nothing can be done. So what should be returned?

At this point, this feature is not fully stabilized and there can be some changes in future. We will work hard to maintain backward compatibility or to fully elaborate once backward compatibility is broken why it was done that way.

Speaking of previous, articles example, let's walk through this model file example:

'use strict';

function nonBlank(s){
  return !!s;
}

var ArticleSchema = {
  fields: {
    created: {
      type: Date,
      default: Date.now
    },
    title: {
      type: String,
      required: true,
      trim: true,
      validate: [nonBlank, 'Title cannot be blank']
    },
    content: {
      type: String,
      required: true,
      trim: true,
      validate: [nonBlank, 'Content cannot be blank']
    },
    user: {
      type: Schema.ObjectId,
      ref: 'User'
    }
  },
  methods: {
    spacesLessTitle : function () {
      return this.title.replace(/\s/g, '');
    }
  },
  statics: {
    load : function(id, cb) {
      this.findOne({ _id: id }).populate('user', 'name username').exec(cb);
    }
  }
};

module.exports = {
  register : [
    { schema: ArticleSchema, model: 'articles', dbs:['articles'] }
  ]
};

There are few small differences, small but important. First, and probably most important is the fact we're creating a hash here, so exported 'schema' (it's not a real schema, it's just a descriptor which will be used to create a mongoose schema) is a hash with several recognizable keys: fields, methods, statics ... One can use virtual (for mongoose virtual fields), pre (for 'pre' hooks) and post (for 'post' hooks) as well or indices (to specify indices). Node module exports a register field which is array (developer can specify more than one schema to be created) of hashes. schema key is interpreted as schema descriptor, model key as model name and dbs is interpreted as db alias array which will create this same model. Yes, given structure allows you to store same model onto multiple database instances. Aliases will be explained later.

And that's about it. This way, core will take structure, remember it and expand with any other structure with same model name on given db alias. So, if developer wants to expand existing schema, his expansion should be exported to core via this construct and merged once model creating time comes along. Important thing is that model name and db alias of original schema and extension match. Note that if you specify more than one DB alias in dbs field (let's say there is n aliases given), n separate copies of this structure will be created, one for each database.

After core has loaded all packages (package loading time) and bit before it opens listening ports making system actually public (package execution time), all stored schemata will be compiled to mongoose models making them available to packages.

Nice, but how should I get model?

First approach is an easy situation, use require('mongoose').model for it. If you have decided to take second approach, how you should get model and execute some queries on them? Take look at the code bellow:

var mongoose = require('mongoose');
var db;
if (mongoose.alias_MEANIODB_exists ('your_db_alias')){
  db = mongoose.get_MEANIODB_connection ('your_db_alias');
}
var model = db.model('my_model_name');

There are two important functions which we injected into mongoose instance: mongoose.alias_MEANIODB_exists(alias) will provide info to developer if given alias has been registered to system. mongoose.get_MEANIODB_connection(alias) will return mongoose db connection suitable for further queries.

Finally db aliases?

DB aliases are shorthand, human friendly name for database connection. Developer can configure as many connections as he requires, for now statically, from configuration file only. So if you open config file, there is config for default db connection, simple db:connection_string field. To create other connections developer should configure dbs field as well (db field remains, and that connection is considered as default one) to something like this:

  dbs: {
     alias_name: connection_string
  }

DB core module will take this config and create appropriate connections. At this point, there is no check if there are duplicate connection strings or not. Once connections are created, alias map is memorized as well so functions mentioned above can be called to resolve connections.

Clone this wiki locally