The Sheep-Pen of the Shaun

News

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.

.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.

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

varReminderSchema=newSchema({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.

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.

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.

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.

Comments

#re: Implement Schema Inherence in MongoosePosted 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

#re: Implement Schema Inherence in MongoosePosted 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.

#re: Implement Schema Inherence in MongoosePosted by doasync
on 7/29/2017 2:55 AM
Here is the new module which just extends the parent Schema#obj: