Entity
An Entity is a business unit. Entities are supersets of models, resources and contain the business logic. They are persistent storage agnostic and provide a normalized API with which your consuming services can perform business logic actions.
The Entity Object on itself is nothing but an extension of EventEmitter
. It can be easily extended to create the Interfaces and base classes from where your business logic entities can inherit.
Currently the CRUD interface has been implemented and two ORM packages are supported in the form of Adaptors, Mongoose and Sequelize.
Install
npm install node-entity --save
Entity Static Methods
Entity uses the Inher package for inheritance, it implements the pseudo-classical inheritance pattern packed in a convenient and easy to use API. Extend will create a new Constructor that can be invoked with the new
keyword or itself extended using the same static method. All the static methods related to inheritance are from Inher.
entity.extend()
The extend()
method is the basic tool for extending, it accepts a constructor.
var entity = require('entity');
var EntityChild = entity.extend(function() {
this.a = 1;
});
var EntityGrandChild = EntityChild.extend();
var EntityGreatGrandChild = EntityGrandChild.extend(function() {
this.b = 2;
});
var greatGrandChild = new EntityGreatGrandChild();
greatGrandChild.a === 1;
greatGrandChild.b === 2;
Read more about extend() at Inher's documentation.
entity.getInstance()
The getInstance()
method will return a singleton instance. This means that you will get the same exact instance every time you invoke this static function.
var entityChild = require('../entities/child.ent').getInstance();
While the use of singletons has been fairly criticized, it is our view that in modern day web applications, the instance role has moved up to the node process. Your web application will naturally run on multiple cores (instances) and thus each instance is a single unit in the whole that comprises your web service. Entities need to emit and listen to local events, PubSub, create Jobs, send email and generally have bindings to other services (RPC, AWS, whatnot).
Those events, PubSub etc have a lifetime equal to the runtime of the core the application is running on. This requires for a single entity to exist and manage all those bindings, applying the business logic and performing the required high level operations.
Entity CRUD Interface
The current implementation offers a normalized CRUD Interface with a selected set of operations. The purpose of having a normalized interface is to decouple the business logic from storage, models. ORM Adaptors implement the CRUD Interface giving you a vast array of options for the underlying persistent storage. As mentioned above, currently two ORM packages are supported, Mongoose and Sequelize. This pretty much covers all the popular databases like mongodb, Postgres, MySQL, Redis, SQLite and MariaDB.
CRUD Primitive Operations
The CRUD Interface offers the following primitive operations:
- create(data)
- read(query=)
- readOne(query)
- readLimit(?query, offset, limit)
- update(query, updateValues)
- delete(query)
- count(query=)
These primitives will transparently adapt to the most optimized operations on the ORM of your choosing and guarantee the outcome will always be the same no matter the underlying ORM.
All primitive methods offer Before/After hooks and return a Promise to determine resolution.
entity.create(data)
- data
Object
The Data Object representing the Entity you wish created. - Returns
Object
The newly created item, in the type of the underlying ORM.
The create()
method will create a new item, you need to provide an Object containing key/value pairs.
entity.create({name: 'thanasis'})
.then(function(document) {
document.name === 'thanasis';
}, then(function(error) {
});
Check out the entity.create()
tests
entity.read(query=)
- query=
Object|string
Optional A query or an id. - Returns
Array.<Object>
An array of documents.
The read()
method will query for items. If the query argument is omitted, all the items will be returned. If the query argument is an Object, it will be passed as is to the underlying ORM. Entity guarantees that key/value type queries will work and will also transport any idiomatic ORM query types.
entity.read().then(function(documents) {
});
entity.read({networkId: '47'}).then(function(documents) {
});
Any additional key/value pairs you add to your query will be added with the AND
operator.
Check out the entity.read()
tests
entity.readOne(query)
- query
Object|string
A query or an id, required. - Returns
Object
A single Document.
The readOne()
method guarantees that you will get one and only one item. It is the method intended to be used by single item views. The query argument has the same attributes as read()
.
entity.read({name: 'thanasis'}).then(function(document) {
document.name === 'thanasis';
});
entity.read('42').then(function(document) {
document.id === '42';
});
entity.readLimit(?query, offset, limit)
- query
Object|string|null
A query or an id, if null
will fetch all. - offset
number
Starting position. - limit
number
Number of items to fetch. - Returns
Array.<Object>
An array of Documents.
Will fetch the items based on query, limiting the results by the offset and limit defined. The query argument shares the same attributes as read()
, if null
all the items will be fetched.
entity.readLimit(null, 0, 10).then(function(documents) {
});
entity.readLimit({networkId: '42'}, 10, 10).then(function(documents) {
});
entity.update(query, updateValues)
- query
Object|string
A query or an id, required. - updateValues
Object
An Object with key/value pairs to update. - Returns
Object=
The updated document of Mongoose ORM is used or nothing if Sequelize.
Will perform an update operation on an item or set of item as defined by the query. The query argument can be a single id or an Object with key/value pairs.
entity.update('99', {name: 'John').then(function(document) {
document.name === 'John';
});
entity.update({networkId: '42'}, {isActive: false}).then(function(documents) {
});
Check out the entity.update()
tests
entity.delete(query)
- query
Object|string
A query or an id, required. - Returns nothing.
Will perform an delete operation as defined by the query. The query argument can be a single id or an Object with key/value pairs.
entity.delete('99').then(function() {
});
entity.delete({networkId: '42'}).then(function() {
});
Check out the entity.delete()
tests
entity.count(query=)
- query=
Object|string
Optional A query or an id, if omitted all items will be count. - Returns
number
The count.
Will perform a count operation as defined by the query. The query argument can be a single id or an Object with key/value pairs, if empty it will count all the items.
entity.count().then(function(count) {
typeof count === 'number';
});
entity.count({networkId: '42'}).then(function() {
typeof count === 'number';
});
Check out the entity.count()
tests
Before / After Hooks
Every CRUD operation offers Before/After hooks courtesy of Middlewarify. Each middleware will receive the same exact arguments. To pass control to the next middleware you need to return a promise that conforms to the Promises/A+ spec.
entity.create.before(function(data){
if (!data.name) {
throw new TypeError('No go my friend');
}
});
entity.create({}).then(function(document) {
}, function(err) {
err instanceof Error;
err.message === 'No go my friend';
});
An Asynchronous middleware
entity.create.before(function(data){
return somepackage.promise(function(resolve, reject) {
readTheStars(function(err, omen) {
if (err) { return reject(err); }
if (omen === 'thou shall not pass') {
reject('Meh');
} else {
resolve();
}
});
});
});
Setting up a CRUD Entity
The two currently available adaptors are available through these properties of the entity module
All adaptors expose the setModel()
method. Before that method is successfully invoked all CRUD operations will fail, the Model required is an instantiated model of each respective ORM.
Initializing Mongoose
models/user.model.js
var mongoose = require('mongoose');
var userModel = module.exports = {};
userModel.schema = new mongoose.Schema({
name: {type: String, trim: true, required: true},
_isActive: {type: Boolean, required: true, default: true},
});
userModel.Model = mongoose.model('user', userModel.schema);
entities/user.ent.js
var EntityMongoose = require('entity').Mongoose;
var UserModel = require('../models/user.model');
var UserEntity = module.exports = Entity.Mongoose.extend(function(){
this.setModel(UserModel.Model);
});
Initializing Sequelize
models/user.model.js
var Sequelize = require('sequelize');
var seqInstance = new Sequelize(
'database',
'postgres',
'',
{
host: '127.0.0.1',
port: '5432',
dialect: 'postgres',
logging: false,
}
);
var userModel = module.exports = {};
userModel.Model = seqInstance.define('user', {
name: {type: Sequelize.STRING, allowNull: false},
_isActive: {type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true},
});
entities/user.ent.js
var EntitySequelize = require('entity').Sequelize;
var UserModel = require('../models/user.model');
var UserEntity = module.exports = Entity.Sequelize.extend(function(){
this.setModel(UserModel.Model);
});
That was it, from here on, irrespective of adaptor and ORM, you can instantiate a new entity or use the singleton to perform CRUD operations.
controllers/user.ctrl.js
var BaseController = require('./base-controller.js');
var userEnt = require('../entities/user.ent').getInstance();
var UserCtrl = module.exports = BaseController.extend();
UserCtrl.prototype.createNew = function(req, res) {
userEnt.create(req.body)
.then(function(udo){
res.json(udo);
}, function(error) {
res.json(501, error);
});
};
Entity Schema
Entities offer a schema API. CRUD Adaptors will automatically translate the underlying ORM's schema to a normalized mschema compliant schema.
entity.getSchema()
- Returns
mschema
The normalized schema.
getSchema()
is synchronous, it will return a key/value dictionary where value will always contain the following properties
- name
string
The name of the attribute. - path
string
The full path of the attribute (used by document stores) - canShow
boolean
A boolean that determines View visibility. - type
string
An mschema type, possible types are:
string
number
object
boolean
any
var userModel = require('../entities/user.ent').getInstance();
userModel.getSchema();
Outputs:
{
"name": {
"name": "name",
"path": "name",
"canShow": true,
"type": "string"
},
"_isActive": {
"name": "_isActive",
"path": "_isActive",
"canShow": false,
"type": "boolean"
}
}
The path
property will be useful when you use nested Objects in your Schema, something only possible with the Mongoose adaptor. So if the address is an object that contains the attribute city
here's how that would look like:
{
"address.city": {
"name": "city",
"path": "address.city",
"canShow": true,
"type": "string"
}
}
Check out the CRUD Schema method tests
entity.addSchema(attributeName, type)
- attributeName
Object|string
The name of the attribute to add or an Object with key/value pairs. - type
Object|string
The type or an Object with key/value pairs representing the attributes values. - Returns self, can be chained.
The addSchema()
method allows you to add attributes to the entity's schema. Any of the following combinations are acceptable.
entity.addSchema('name', 'string');
entity.addSchema('name', {type: 'string', cahShow: false});
entity.addSchema({
name: 'string'
});
entity.addSchema({
name: {
type: 'string'
}
});
entity.remSchema(attributeName)
- attributeName
string
The name of the attribute to remove. - Returns self, can be chained.
Remove an attribute from the schema.
entity.remSchema('name');
Check out the Schema basic methods tests
Release History
- v0.1.3, 15 Feb 2014
- Upgrade Middlewarify to 0.3.3.
- v0.1.2, 12 Feb 2014
- Now casting to Bluebird promises all returned Objects from Sequelize adaptor.
- v0.1.1, 11 Feb 2014
- Added "main" key in package.json
- v0.1.0, 11 Feb 2014
- New API, documentation, Inher integration.
- v0.0.1, 08 Oct 2013
License
Copyright 2014 Thanasis Polychronakis
Licensed under the MIT License