当前位置: 首页 > 工具软件 > Bearcat > 使用案例 >

Pomelo Bearcat

谈萧迟
2023-12-01

企业级Node.js应用开发中存在比较大的问题是缺乏基础的框架来处理应用层的逻辑对象,开发者不得不重复手动编写诸如单例、工厂方法、不一致的配置文件等。使得很难分辨应用层的逻辑,缺乏一致性,提高了维护成本。Bearcat提供了一个基础的管理业务层的逻辑对象用于解决Node.js开发中存在的问题。

Bearcat是网易基于POJOs(Plain Ordinary Java Object/Pure Old Java Object,简单的Java对象或普通的JavaBeans)开发的应用层框架。Bearcat提供轻量级容器用来编写简单可维护的Node.js应用,提供基础的底层来管理应用逻辑对象,使开发者将精力放在应用层的逻辑上。

POJO

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

Bearcat由两部分组成分别是核心容器、AOP面向切面编程

Bearcat的核心容器包括三部分,分别是核心、Beans模块、Context模块。核心和Beans模块提供了容器的基础部分,包括IoC容器和DI依赖注入。

  • Bean是指Bearcat所管理的对象,Beans模块的beanFactory是一个复杂的工厂模式实现,去除了编写单例并允许实际程序逻辑从配置和依赖的管理中解耦。
  • Context上下文模块基于核心与Beans模块之上,提供更高级的内容,applicationContext则是它的主要实现。

Bearcat基于核心容器提供了AOP面向切面编程的支持,允许开发者定义方法拦截器,并使用切面来解耦需要被分离的业务逻辑。

核心容器描述实现
核心核心和Beans模块提供了IoC容器和DI依赖注入
BeansBearcat管理的对象beanFactory
Context上下文模块applicationContext

IoC容器

IoC控制反转是用于解决组件之间的依赖关系配置和生命周期的设计模式,Bearcat使用DI依赖注入的方式实现IoC,依赖注入是指容器内的组件无需自己去查找,组件只需要提供简单的元数据配置,容器可以根据配置将组件所依赖的注入到组件中。

依赖注入可以通过构造函数或对象属性的方式实现,Bearcat底层的beanFactory和高层的applicationContext中实现了IoC容器。

安装Bearcat

$ npm init ./project_name

$ cd project_name

$ npm i -S bearcat

上下文配置context

  • Context应用上下文对象属性在context.json文件中进行定义
上下文配置属性描述
name标识项目或库的名称
beans标识需要在IoC容器中管理的beans元数据定义
scan自动扫描路径,扫描基于代码的元数据配置的beans。
imports定义引用其它的context.json的路径的数组
dependencies标识在依赖的子模块中有需要被容器管理的beans
namespace标识在context.json定义的beans的命名空间

Bearcat内部bean id会以namespace:id作为唯一标识,若在依赖注入或getBeanbean idnamespace:id

$ vim context.json
{
  "name":"project_name",
  "scan":"app",
  "beans":[{"id":"xxx", "func":"xxx"}]
}

元数据配置bean/pojo

  • 元数据配置beans开发者告知Bearcat容器来创建、配置、生成应用程序中对象POJO的关键所在
  • Bean属性的配置会封装成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的构造函数。

基于JSON配置文件的元数据配置

元数据配置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"
}

定义Bean

  • Bearcat的IoC容器中管理的POJOs又称为Beans
  • IoC容器会根据开发者提供的元数据配置信息创建Beans
  • 可使用文件的名称作为Bean的ID,以方便使用IoC容器实例使用getBean方法进行查找。
$ 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

一个bean的定义是创建一个或多个对象的要素,IoC容器以此来查询创建具体的对象实例。Bearcat中提供了两种实例化Bean的方式,分别是通过构造方法实例化Bean、通过工厂对象的工厂方法实例化Bean。

  • 通过构造方法实例化Bean

Bearcat中通过构造方法实例化Bean只需在context.json配置文件中定义beans选项即可。

$ vim context.json
{
  "name":"project_name",
  "scan":"app",
  "beans":[
    {"id":"pojo", "func":"pojo"}
  ]
}
  • 通过工厂对象的工厂方法实例化Bean

在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;

初始化IoC容器

Bearcat使用bearcat.createApp(contextPath)初始化一个IoC容器,仅需将元数据配置文件的路径作为参数传递给createApp工厂方法。

//获取元数据配置文件的绝对路径
const abspath = require.resolve("./context.json");
//初始化IoC容器
bearcat.createApp([abspath]);

启动IoC容器

使用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配置Bearcat

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.join是使用平台特定分隔符将给定路径片段连接到一起,并规范化生成的路径。
  • path.resolve是将一个路径或路径片段的序列解析为一个绝对路径

path.resolve

  • 给定的路径的序列是从右向左被处理,后续path依次解析直到构造成一个绝对路径。
  • 若处理完毕后给定的path仍未生成绝对路径则使用当前工作目录的绝对经
  • 生成的绝对路径是规范的,末尾分隔符会被删除,除非路径被解析为根路径。
  • 路径片段中为空的部分会被忽略
  • 若未传入路径片段则返回当前工作目录的绝对路径

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}});
};
 类似资料:

相关阅读

相关文章

相关问答