企业级Node.js应用开发中存在比较大的问题是缺乏基础的框架来处理应用层的逻辑对象,开发者不得不重复手动编写诸如单例、工厂方法、不一致的配置文件等。使得很难分辨应用层的逻辑,缺乏一致性,提高了维护成本。Bearcat提供了一个基础的管理业务层的逻辑对象用于解决Node.js开发中存在的问题。
Bearcat是网易基于POJOs(Plain Ordinary Java Object/Pure Old Java Object,简单的Java对象或普通的JavaBeans)开发的应用层框架。Bearcat提供轻量级容器用来编写简单可维护的Node.js应用,提供基础的底层来管理应用逻辑对象,使开发者将精力放在应用层的逻辑上。
POJO是Plain Ordinary Java Object/Pure Old Java Object简单的Java对象或普通的JavaBeans,被广泛应用于Java语言中,用来表示一个对象是一个普通对象而非特殊的对象。
使用POJO名称是为了避免和EJB混淆,由于其中一些属性和存取器方法的类没有业务逻辑,因此有时也可以作为VO(Value-object)或DTO(Data Transform Object)来使用。
POJO通常指没有使用Entity Beans的普通Java对象,实质上可以理解为简单的实体类。POJO类的作用是为了方便使用数据库的表。
Node.js中什么是POJO呢?简单来说它必须是Plain Old JavaScript Object,即简单的、普通的而非匿名的,由于不能是匿名的,所以POJO必须要有构造函数。
let POJO = function(){
this.props = null;
};
POJO.prototype.method = function(){
}
module.exports = POJO;
Bearcat由两部分组成分别是核心容器、AOP面向切面编程
Bearcat的核心容器包括三部分,分别是核心、Beans模块、Context模块。核心和Beans模块提供了容器的基础部分,包括IoC容器和DI依赖注入。
Bearcat基于核心容器提供了AOP面向切面编程的支持,允许开发者定义方法拦截器,并使用切面来解耦需要被分离的业务逻辑。
核心容器 | 描述 | 实现 |
---|---|---|
核心 | 核心和Beans模块提供了IoC容器和DI依赖注入 | |
Beans | Bearcat管理的对象 | beanFactory |
Context | 上下文模块 | applicationContext |
IoC控制反转是用于解决组件之间的依赖关系配置和生命周期的设计模式,Bearcat使用DI依赖注入的方式实现IoC,依赖注入是指容器内的组件无需自己去查找,组件只需要提供简单的元数据配置,容器可以根据配置将组件所依赖的注入到组件中。
依赖注入可以通过构造函数或对象属性的方式实现,Bearcat底层的beanFactory和高层的applicationContext中实现了IoC容器。
$ npm init ./project_name
$ cd project_name
$ npm i -S bearcat
context
context.json
文件中进行定义上下文配置属性 | 描述 |
---|---|
name | 标识项目或库的名称 |
beans | 标识需要在IoC容器中管理的beans元数据定义 |
scan | 自动扫描路径,扫描基于代码的元数据配置的beans。 |
imports | 定义引用其它的context.json 的路径的数组 |
dependencies | 标识在依赖的子模块中有需要被容器管理的beans |
namespace | 标识在context.json 定义的beans的命名空间 |
Bearcat内部bean id
会以namespace:id
作为唯一标识,若在依赖注入或getBean
时bean id
为namespace:id
。
$ vim context.json
{
"name":"project_name",
"scan":"app",
"beans":[{"id":"xxx", "func":"xxx"}]
}
bean/pojo
BeanDefinition
对象元数据配置选项 | 描述 |
---|---|
id | 用于唯一标识所描述的bean定义,当前容器的唯一性编号,以便容器据此查询。 |
func | 定义bean所对应的构造函数,具有两种配置风格。 |
order | 当bean是单例时的初始化顺序 |
init | 构造函数被调用后执行的初始化方法,可以是async异步的。 |
destroy | 析构函数,当容器优雅关闭时被调用,用于执行摧毁工作。 |
factoryBean | 用于实例化bean的工厂bean的名字 |
factoryMethod | 工厂bean的工厂方法名 |
scope | 作用域,取值singleton或prototype,默认singleton。 |
async | 用于设置init方法是否为异步方式,默认false。 |
Bearcat中元数据配置可以以JSON形式出现在context.json文件,也可以直接编写的POJOs代码文件中。两种方式的唯一区别在于func属性值的不同,基于JSON形式的func属性是一个字符串用于指明Bean构造函数所在文件的相对位置,而基于代码的func属性则是当前POJO的构造函数。
元数据配置beans采用JSON方式表示时格式为{"id":"", func:""}
$ vim context.json
"beans":[{"id":"pojo", "func":"pojo"}]
$ vim app/pojo.js
module.exports = {id:"pojo", func:Module};
const Module = function(){}
Module.prototype.fn = function(){};
$
命名的变量来进行描述配置Bearcat 0.2.10后可通过$
命名的变量来描述配置的支持,因此可抛弃在JSON配置中编写beans配置的烦恼,直接将配置描述编写在bean的构造函数中,bearcat会解析配置来进行依赖注入。
$ vim app/pojo.js
const path = require(path);
module.prototype = Module;
const Module = function(){
//使用当前文件名称作为bean的id用于容器查找
this.$id = path.filename(__filename, path.extname(__filename));
};
Module.prototype.fn() = function(){
};
使用$
命名的参数后可将context.json中的beans选项去除
$ vim context.json
{
"name": "project_name",
"scan": "app"
}
$ vim app/pojo.js
const path = require("paht");
const Module = function(){
this.$id = path.filename(__filename, path.extname(__filename));//使用当前文件名为POJO的编号
}
Module.prototype.fn = -=>{
console.log("hello wolrd");
};
module.exports = Module;
一个bean的定义是创建一个或多个对象的要素,IoC容器以此来查询创建具体的对象实例。Bearcat中提供了两种实例化Bean的方式,分别是通过构造方法实例化Bean、通过工厂对象的工厂方法实例化Bean。
Bearcat中通过构造方法实例化Bean只需在context.json配置文件中定义beans选项即可。
$ vim context.json
{
"name":"project_name",
"scan":"app",
"beans":[
{"id":"pojo", "func":"pojo"}
]
}
在bean的元数据定义中可使用factoryBean属性指明当前IoC容器中工厂对象所对应的bean的名字,通过factoryMethod属性指明工厂对象中的方法名作为工厂方法。
$ vim context.json
{
"name":"project_name",
"scan":"app",
"beans":[
{"id":"pojo", "func":"pojo", "beanFactory":"pojoFactory", "factoryMethod":"create"}
]
}
$ vim app/pojoFactory.js
const Pojo = require("./pojo");
const PojoFactory = function(){};
PojoFactory.prototype.create = function(){
return new Pojo();
};
module.exports = PojoFactory;
Bearcat使用bearcat.createApp(contextPath)
初始化一个IoC容器,仅需将元数据配置文件的路径作为参数传递给createApp工厂方法。
//获取元数据配置文件的绝对路径
const abspath = require.resolve("./context.json");
//初始化IoC容器
bearcat.createApp([abspath]);
使用bearcat.start
方法启动IoC容器成功后,可使用bearcat.getBean
方法获取指定ID的Bean。
bearcat.start(_=>{
//IoC容器启动成功后可使用getBean(id)获取Bean实例
const bean = bearcat.getBean(id);
});
$ vim main.js
const bearcat = require("bearcat");
// 初始化IoC容器
bearcat.createApp([require.resolve("./context.json")]);
// 启动IoC容器
bearcat.start(_=>{
// 获取指定id的bean
const bean = bearchat.getBean("pojo");
// 执行bean中的方法
bean.fn();
});
pomelo对handler和remote进行了抽象,开发者只需依照pomelo规范在指定文件夹下编写handler和remote用以处理客户端的请求和rpc调用,即可搭建弹性可伸缩的实时应用,handler和remote可以封装成简单的POJOs,通过bearcat进行统一管理,这样就可以使其代码不再孤立,变得简单、可重用、可维护。用以解决require不够友好、handler共享等问题。
pomelo游戏服安装bearcat
$ cd game-server
$ npm i -S bearcat
新建Bearcat配置文件,编写元数据定义。
Bearcat中元数据配置以JSON形式的配置文件或直接写在POJOs的代码文件中,两种风格唯一的区别就是func属性的不同,基于代码的元数据配置的func属性是当前POJO的构造函数,即是一个Function函数。配置文件形式的元数据配置的func则是一个String字符串,用于指明构造函数所在文件的相对位置。
$ vim game-server/context.json
{
"name": "project_name",
"scan": "app"
}
配置 | 描述 |
---|---|
name | 标识项目或库的名称 |
scan | 指定扫描路径进行自动扫描POJOs,自动扫描路径,扫描基于代码的元数据配置的beans。 |
beans | 标识需要在容器中管理的beans元数据定义 |
修改应用入口文件添加Bearcat启动代码
$ vim game-server/app.js
const pomelo = require('pomelo');
const bearcat = require("bearcat");
//初始化bearcat的IoC容器
bearcat.createApp([require.resolve("./context.json")]);
//启动IoC容器
bearcat.start(_=>{
//创建应用
const app = pomelo.createApp();
//应用设置变量
app.set('name', 'treasure');
//应用配置全局服务器
app.configure('production|development', function(){
app.set('connectorConfig',
{
connector : pomelo.connectors.hybridconnector,
heartbeat : 10,
useDict : false,
useProtobuf : false
});
});
//开启应用
app.start();
});
process.on('uncaughtException', function (err) {
console.error(' Caught exception: ' + err.stack);
});
path.join与path.resolve的区别
path.resolve
bearcat环境搭建完毕,之后可利用bearcat提供的IoC、AOP、一致性配置等特性来编写简单可维护的pomelo应用。
pomelo配置bearcat后,handler和remote都会交由bearcat进行管理,handler、remote都应该以POJO的形式进行编写。由于之前handler、remote在pomelo中都是通过pomelo-loader进行管理的,因此需要做适配转化。
配置网关服务器
$ vim game-server/config/adminServer.json
{
"type": "gate",
"token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
}
$ vim game-server/config/servers.json
"gate": [
{"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3010, "frontend": true}
]
编写网关处理器
$ vim game-server/app/servers/gate/handler/gateHandler.js
const pomelo = require("pomelo");
const bearcat = require("bearcat");
const path = require("path");
const crc = require("crc");
module.exports = function(){
return bearcat.getBean(Bean);
};
let Bean= function(){
//以当前文件名作为bearcat的id
this.$id = path.basename(__filename, path.extname(__filename));
};
Bean.prototype.queryEntry = function(msg, session, next){
const app = pomelo.app;
const uid = msg.aid;
if(!uid){
next(null, {code:500});
return;
}
const servers = app.getServersByType("connector");
if(!servers || servers.length===0){
next(null, {code:500});
return;
}
const index = Math.abs(crc.crc32(uid.toString())) % servers.length;
const server = servers[index];
next(null, {code:200, data:{host:server.host, port:server.clientPort}});
};