Shaun Xu

The Sheep-Pen of the Shaun


News

logo

Shaun, the author of this blog is a semi-geek, clumsy developer, passionate speaker and incapable architect with about 10 years’ experience in .NET and JavaScript. He hopes to prove that software development is art rather than manufacturing. He's into cloud computing platform and technologies (Windows Azure, Amazon and Aliyun) and right now, Shaun is being attracted by JavaScript (Angular.js and Node.js) and he likes it.

Shaun is working at Worktile Inc. as the chief architect for overall design and develop worktile, a web-based collaboration and task management tool, and lesschat, a real-time communication aggregation tool.

MVP

My Stats

  • Posts - 122
  • Comments - 576
  • Trackbacks - 0

Tag Cloud


Recent Comments


Recent Posts


Archives


Post Categories


Image Galleries


.NET



When I was developing Worktile Enterprise we are using MongoDB with an ODM framework called Mongoose. It provides sachems-based validation in top of native MongoDB driver, and for each collection we also need to define schema.

When the business became more complex we found it needs to share part of schema between collections. For example, in calendar module there are two collection one for event series the other for each individual event instance. Both of them contains properties such as event name, description, location, start and end date and so on. One solution is to have them defined in both schemas. But this introduces duplication in our codebase. And if we need to tweak the definition we need to remember to modify code in, at least, two places. With more business requirements came we know there will be more cases needs to share schema. This solution is easy to do, but will harm our source code very quickly.

 

Shared Schema as Sub-Document

Another solution is to move the shared definition in a separate file and export it. Then we can require and put it as a sub-document when we need. Below is the shared calendar event details definition in our Worktile Enterprise.

 

1
2
3
4
5
6
7
8
9
name: {type: String},
start: {type: Number},
end: {type: Number},
is_all_day: {type: Number, default: core.constant.calendar.isAllDayEvent.no},
location: {type: String},
video_link: {type: String},
description: {type: String},
reminders: [ReminderShared.Reminder],
participants: [Participant]

 

Now when we defined event series schema we required the code we created previously and put it into the schema property named "details".

 

1
2
3
4
5
6
var Shared = require('./calendar.shared');

var CalendarEventSchema = new Schema({
    calendar: {type: Schema.ObjectId, ref: 'calendar', index: true},
    details: Shared.Details
});

 

Similarly, in event instance schema there is a property named "details" and referring our shared schema as well.

 

1
2
3
4
5
6
7
8
var Shared = require('./calendar.shared');

var CalendarEventInstanceSchema = new Schema({
    calendar: {type: Schema.ObjectId, ref: 'calendar', index: true},
    event: {type: Schema.Types.ObjectId, ref: 'calendar_event'},
    seqno: {type: Number},
    details: Shared.Details
});

 

In this solution we defined the shared schema in one place and reference in any places we need. And if we changed the content of the shared schema all collections referring it will be changed automatically. I was using it in several collections and it helped me a lot, until I began to implement the reminder module.

Worktile Enterprise contains a dedicate reminder component which responsible for notify users when an event is going to start, or a task is reaching the deadline, through vary channels such as email, SMS and voice call.

In reminder component we have two collections one for outstanding reminder records while the other stores failed reminders (we called reminder-poison) for future diagnostic.

Reminder-poison collection has almost same information as reminder collection, with three additional properties: poisoned_at, poison_reason and poison_reason_description. Same reason, we don't want to refine shared schema twice. But in this case it may not be a good choice to put shared schema in a property because reminder collection contains the entire share schema, which will make the document looks strange.

 

1
2
3
var ReminderSchema = new Schema({
    details: Reminder.Shared
});

 

In order to make the shared properties in the same level (not in a sub document) we introduced another solution.

 

Shared Schema as Assigned Properties

Basically Mongoose schema definition is a normal JSON object, each property represents a MongodDB document property with additional information such as type, index and reference. It we can load the shared schema JSON, copy its properties and combine to the targeting schema then we can implement inherience.

For example, assuming we have shared schema as below.

 

1
2
3
4
5
var PersonSchemaRaw = {
    first_name: {type: String},
    last_name: {type: String},
    age: {type: Number}
};

 

Then we can inherits schema from it and append additional properties like this.

 

1
2
3
4
5
6
7
8
9
var UserSchemaRaw = (function () {
    var schema = {};
    Object.keys(PersonSchemaRaw).forEach(function (key) {
        schema[key] = PersonSchemaRaw[key];
    });
    schema.email = {type: String},
    schema.avatar = {type: String}
    return schema;
})();

 

If have "lodash" required in your system it could be more easy and simple as below.

 

1
2
3
4
var UserSchemaRaw = _.assign({}, PersonSchemaRaw, {
    email: {type: String},
    avatar: {type: String}
});

 

Related Topics

There's a Mongoose plugin named mongoose-schema-extend implements the schema inherence. You need to request Monggose and this plugin, then define your schema with base schema through "extend" method as below.

 

1
2
3
4
5
6
7
var PersonSchema = new Schema({
  name : String
}, { collection : 'users' });

var EmployeeSchema = PersonSchema.extend({
  department : String
});

 

If your project is fine with ES6, there's a new MongoDB ES6 Node.js client named Mongorito. Although it said it's not necessary to define a model before operating against a collection, you can even do that with the new ES6 Class syntax as below.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var Mongorito = require('mongorito');
var Model = Mongorito.Model;

class Person extends Model {
    // ... ...
}

class User extends Person {
    // ... ...
}

 

Since all models in Mongorito are inherits from "Model", you can define model which inherits from another model you defined.

 

Summary

MongoDB is schema-free which means we don't need to define which properties should be defined in a collection. It should be admitted that this looks like attempting to use MongoDB as a relational database. But schema-predefinition provides some additional benefit such as validation, population, etc.

When we need some common schema across multiple collections we have several solutions. We can repeat them in each schemas, or put then shared properties into a sub document. Or if we want them to be in the same level as other properties we can leverage "lodash" to combine.

We can, of course use mongoose-schema-extend and Mongorito if we can use ES6.

 

Hope this helps,

Shaun

All documents and related graphics, codes are provided "AS IS" without warranty of any kind.
Copyright © Shaun Xu. This work is licensed under the Creative Commons License.

Comments

Gravatar # re: Implement Schema Inherence in Mongoose
Posted by Kun Ran on 6/20/2016 9:33 AM
There is Discriminator would also be a good candidate.

http://mongoosejs.com/docs/discriminators.html

Evil deeplink: http://blog.rankun.org
Gravatar # re: Implement Schema Inherence in Mongoose
Posted by Shaun Xu on 6/20/2016 10:11 AM
@Kun Ran, I didn't noticed about "mongoose.discriminator" which looks good. But looks like we must store entities in the same collection for vary discriminators. In my case the model can be in the same collection, or not. It just inherits the schema definition and doesn't care where to save.
Gravatar # re: Implement Schema Inherence in Mongoose
Posted by doasync on 7/29/2017 2:55 AM
Here is the new module which just extends the parent Schema#obj:

https://www.npmjs.com/package/mongoose-extend-schema
Post A Comment
Title:
Name:
Email:
Comment:
Verification: