@fantasyni from Pomelo Netease
PayPal measured a 2x increase in developer productivity and high performance (2x)
require a constructor function
var B = require('b');
var b = new B();
b.run();
require a function
var B = require('b');
B();
require an object
var B = require('b');
B.run();
With simple require and exports, caller and callee are tightly coupled, developers have to deal with it everytime
Node is quite flexible, developers can write several styles of code, like callback, promise, generator with koa, function programming, OOP programming etc...
app.get('/', function(req, res){
res.send('Hello World');
});
Q.fcall(promisedStep1)
.then(promisedStep2)
.done();
var koa = require('koa');
var app = koa();
app.use(function *(){
this.body = 'Hello World';
});
app.listen(3000);
Inconsistent code stypes, makes it hard to maintain especially in large team work projects
var serverConfig = require('../../config/server');
var redis = require("redis");
var client = redis.createClient(serverConfig['redisPort'], serverConfig['redisHost']);
client.on("error", function(err) {
console.error("redis error " + err);
});
client.on("ready", function() {
console.log("redis is ready");
});
module.exports = client;
you require a config file, and set the redis connection
however, when you switch environments which means serverConfig may be changed, you can not do well with it
Bearcat is a POJOs based application framework which provides a lightweight container for writing simple, maintainable node.js, it is designed to solve all of these painful things.
Simple POJOs + Configuration metadatas = Elastic, maintainable system
var POJO = function() {
this.props = null;
}
POJO.prototype.method = function() {
}
module.exports = POJO;
var Engine = require('./engine');
var Wheel = require('./wheel');
var Car = function() {
this.engine = new Engine();
this.wheel = new Wheel();
}
Car.prototype.run = function() {
this.engine.run();
var res = this.wheel.run();
console.log('run car...');
return 'car ' + res;
}
module.exports = Car;
var Car = function($engine) {
this.$id = "car";
this.$engine = $engine;
this.$wheel = null;
}
Car.prototype.run = function() {
this.$engine.run();
var res = this.$wheel.run();
console.log('run car...');
return 'car ' + res;
}
module.exports = Car;
just add a simple configuration metadata file context.json
{
"name": "simple_inject",
"scan": ""
}
var Bearcat = require('bearcat');
var contextPath = require.resolve('./context.json');
var bearcat = Bearcat.createApp([contextPath]);
bearcat.start(function(){
var car = bearcat.getBean('car'); // get bean
car.run(); // call the method
});
[2014-05-04 18:50:41.996] [INFO] bearcat - [app] Bearcat startup in 6 ms
run engine...
run wheel...
run car...
By default, scope is singleton
var Car = function($engine) {
this.$id = "car";
this.$scope = "singleton";
this.$engine = $engine;
this.$wheel = null;
}
Car.prototype.run = function() {
this.$engine.run();
var res = this.$wheel.run();
console.log('run car...');
return 'car ' + res;
}
module.exports = Car;
var car1 = bearcat.getBean('car');
var car2 = bearcat.getBean('car');
// car2 is exactly the same instance as car1
you can set scope to prototype
var Car = function($engine) {
this.$id = "car";
this.$scope = "prototype";
this.$engine = $engine;
this.$wheel = null;
}
Car.prototype.run = function() {
this.$engine.run();
var res = this.$wheel.run();
console.log('run car...');
return 'car ' + res;
}
module.exports = Car;
var car1 = bearcat.getBean('car');
var car2 = bearcat.getBean('car');
// car2 is not the same instance as car1
Initialization method
var Car = function() {
this.$id = "car";
this.$init = "init";
this.num = 0;
this.$engine = null;
}
Car.prototype.init = function() {
console.log('init car...');
this.num = 1;
return 'init car';
}
Car.prototype.run = function() {
this.$engine.run();
console.log('run car...');
return 'car ' + this.num;
}
module.exports = Car;
Destruction method
var Car = function() {
this.$id = "car";
this.$destroy = "destroy";
}
Car.prototype.destroy = function() {
console.log('destroy car...');
return 'destroy car';
}
Car.prototype.run = function() {
console.log('run car...');
return 'car';
}
module.exports = Car;
Async Initialization method
var Car = function() {
this.$id = "car";
this.$init = "init";
this.$order = 2;
this.num = 0;
}
Car.prototype.init = function() {
console.log('init car...');
this.num = 1;
return 'init car';
}
Car.prototype.run = function() {
console.log('run car...');
return 'car ' + this.num;
}
module.exports = Car;
Async Initialization method
var Engine = function() {
this.$id = "engine";
this.$init = "init";
this.$async = true;
this.$order = 1;
}
Engine.prototype.init = function(cb) {
console.log('init engine...');
setTimeout(function() {
console.log('asyncInit setTimeout');
cb();
}, 1000);
}
Engine.prototype.run = function() {
console.log('run engine...');
return 'wheel';
}
module.exports = Engine;
Aspect-Oriented Programming (AOP) -- key unit -- aspect.
Object-Oriented Programming (OOP) -- key unit -- class.
Aspects enable the modularization of concerns such as transaction management that cut across multiple types and objects.
var Car = function() {
}
Car.prototype.run = function() {
// pointcut
// advice code ...
console.log('Car run...');
// pointcut
// advice code ...
}
var Car = function(engine) {
this.engine = engine;
this.wheel = null;
this.log = null;
}
Car.prototype.run = function() {
this.log.info('log...');
this.engine.run();
var res = this.wheel.run();
console.log('run car...');
return 'car ' + res;
}
module.exports = Car;
var Engine = function() {
this.log = null;
}
Engine.prototype.run = function() {
this.log.info('log...');
console.log('run engine...');
return 'engine';
}
module.exports = Engine;
var Wheel = function() {
this.log = null;
}
Wheel.prototype.run = function() {
this.log.info('log...');
console.log('run wheel...');
return 'wheel';
}
module.exports = Wheel;
var Aspect = function() {
this.$id = "aspect";
this.$aop = true;
}
Aspect.prototype.doBefore = function(next) {
var $pointcut = "before:.*?run";
console.log('log...');
next();
}
module.exports = Aspect;
[2014-05-04 18:50:41.996] [INFO] bearcat - [app] Bearcat startup in 6 ms
log...
run engine...
log...
run wheel...
log...
run car...
Transaction management is a good example of a AOP crosscutting concern in enterprise Node.js applications
node-mysql provides simple transaction support at the connection level:
connection.beginTransaction(function(err) {
connection.query('INSERT INTO posts SET title=?', title, function(err, result) {
if (err) {
return connection.rollback(function() {});
}
var log = 'Post ' + result.insertId + ' added';
connection.query('INSERT INTO log SET data=?', log, function(err, result) {
if (err) {
return connection.rollback(function() {});
}
connection.commit(function(err) {
if (err) {
return connection.rollback(function() {});
}
console.log('success!');
});
});
});
});
SimpleService.prototype.testMethodTransaction = function(cb, txStatus) {
var self = this;
this.simpleDao.transaction(txStatus).addPerson(['aaa'], function(err, results) {
if (err) {
return cb(err); // if err occur, rollback will be emited
}
self.simpleDao.transaction(txStatus).getList([1, 2], function(err, results) {
if (err) {
return cb(err); // if err occur, rollback will be emited
}
cb(null, results); // commit the operations
});
});
}
more details can be refered to bearcat-dao transaction
Configurations are actually properties in POJO, therefore, you can use DI to solve it
var Car = function() {
this.$id = "car";
this.$Vnum = "${car.num}";
}
Car.prototype.run = function() {
console.log('run car' + this.$Vnum);
return 'car' + this.$Vnum;
}
module.exports = Car;
placeHolder to be replaced by the specific envrioment value
${car.num}
then in config.json file you can define car.num with the specific value
{
"car.num": 100
}
Different environment configurations:
├─┬ placeholderSample/
│ ├─┬ config/
│ │ └─┬ dev/
│ │ │ └── car.json
│ │ └─┬ prod/
│ │ └── car.json
│ └── car.js
└── context.json
Setup env at the startup command, by default the env is dev
Run with env or --env args
node app.js env=prod
Run with NODE_ENV
NODE_ENV=prod node app.js
the whole repository is bearcat-todo, forked from fengmk2-todo
Project code structure -- MVC driven
├─┬ app/
│ ├─┬ controller/
│ │ └── todoController.js
│ ├─┬ dao/
│ │ └── todoDao.js
│ ├─┬ domain/
│ │ └── todoDomain.js
│ ├─┬ service/
│ │ └── todoService.js
├─┬ config/
│ ├─┬ dev/
│ │ └── mysql.json
│ ├─┬ prod/
│ │ └── mysql.json
├── views/
├── public/
├── server.js
├── package.json
├── context.json
└── RERAME.md
todoController.js -- simple POJO
var TodoController = function() {
this.$id = "todoController";
this.$todoService = null;
}
TodoController.prototype.index = function(req, res, next) {
res.render('index.html', {});
}
module.exports = TodoController;
Setup Bearcat and add route
var contextPath = require.resolve('./context.json');
var bearcat = Bearcat.createApp([contextPath]);
bearcat.start(function() {
/**
* Routing
*/
var router = urlrouter(function(app) {
app.get('/', bearcat.getRoute("todoController", "index"));
});
app.use(router);
// start app
app.listen(config.port);
console.log('Server start on ' + config.port);
});
Setup Bearcat and add route
context.json setup scan path to enable auto-scan POJOs
{
"name": "bearcat-todo",
"scan": "app"
}
Add todoService and todoDao
todoService.js
var TodoService = function() {
this.$id = "todoService";
this.$todoDao = null;
}
TodoService.prototype.getList = function(params, cb) {
return this.$todoDao.getList(params, cb);
}
module.exports = TodoService;
Add todoService and todoDao
todoDao.js
var TodoDomain = require('../domain/todoDomain');
var TodoDao = function() {
this.$id = "todoDao";
this.$init = "init";
this.$domainDaoSupport = null;
}
TodoDao.prototype.init = function() {
this.$domainDaoSupport.initConfig(TodoDomain);
}
TodoDao.prototype.getList = function(params, cb) {
var sql = ' 1=1 order by finished asc, id asc limit ?,?';
return this.$domainDaoSupport.getListByWhere(sql, params, null, cb);
}
module.exports = TodoDao;
Update todoController
todoController.js
TodoController.prototype.index = function(req, res, next) {
this.$todoService.getList([0, 50], function(err, results) {
if (err) {
console.log(err);
return;
}
res.render('index.html', {
todos: results
});
});
}
Just run it
node server.js
magic is also in context.json
{
"name": "bearcat-todo",
"dependencies": {
"bearcat-dao": "*"
},
"scan": "app",
"beans": [{
"id": "mysqlConnectionManager",
"func": "node_modules.bearcat-dao.lib.connection.sql.mysqlConnectionManager",
"props": [{
"name": "port",
"value": "${mysql.port}"
}, {
"name": "host",
"value": "${mysql.host}"
}, {
"name": "user",
"value": "${mysql.user}"
}, {
"name": "password",
"value": "${mysql.password}"
}, {
"name": "database",
"value": "${mysql.database}"
}]
}]
}
Set up mysql configuration
mysql.json in dev
{
"mysql.port": 3306,
"mysql.host": "localhost",
"mysql.user": "root",
"mysql.password": "test",
"mysql.database": "bearcat_test"
}
Swith to production env ?
mysql.json in prod
{
"mysql.port": 3306,
"mysql.host": "10.123.22.3",
"mysql.user": "todo_online",
"mysql.password": "todo_online_aqz",
"mysql.database": "bearcat_todo_online"
}
Just run it
node server.js
node server.js env=prod
Simple POJOs + Configuration metadatas = Elastic, maintainable system
/
#