node.js的 Domain framework JSDM

大家好,我是利奥brighthas,下面介绍一下 node.js 的 CQRS框架[ JSDM ]

参看项目
https://github.com/brighthas/jsdm

以前我开发过cqrsnode是纯粹CQRS的框架,后来工作室本身需要开发相对复杂的系统,而且要浏览器也要有很好的抗“复杂性”,所以我改版成 JSDM。

JSDM不是完全传统的CQRS DDD框架,以下说明一下。

// 这是伪代码,不是完整的,只是说明一下基本用法


var domain = require("jsdm")();

domain
.bindAgg(...)
.bindCommandHandle(...)
.bindService(...)
.listen(...)
.bindAgg(...)
.bindDB(db)
....
.seal();
// 封印

// 封印后,只剩下如下三个方法有效。
// on / once 是 监听器,从domain外部监听内部,但数据都是只读的,改变 domain内部状态只能通过exec方式执行 command来操作 domain内部。

domain.on(...);
domain.once(...);
domain.exec(...);
// 执行 command ,对应着 command handle.

这里需要说明的是 domain.bindDB(db) 这里的db只有一个 get(id,callback) 方法,开发者可以自己实现一个get方法即可, jsdm不是传统的 CQRS ,去除了传统的 event store 的回溯的功能。目的是提供中小型项目也能快速开发的目的,并享受CQRS的精华机制。

那么, db.get 只有这个方法, update 、 remove 、 save呢?

在domain的内部,当 new XXXAggre() 一个根的实例时,会触发一个 create event, 相应的还有 update / remove 的事件产生,而domain new一个Aggre时,会先存放在 cache 中。只有从Repository 得到 Aggre实例时,会先从cache找,如果没有会调用 db.get 方法并放入 cache然后回调出来。


remove 、 update 、 save 实现很简单。


domain
.listen("create",function(event){
// 当然实现非常简单,可以使用内存、file 、mysql,一切都可以。
testdb.save(event.aggreType,event.data);
})
.listen(
"update",function(event){
testdb.save(event.aggreType,event.data);
})
.listen(
"remove",function(event){
testdb.remove(event.aggreType,event.data);
})

另外如果想实现事件的store,直接 domain.listen(" * " ... ) 监听 * 号,表示监听全部事件。


domain.listen("*",function(event){
// save event object.
})


如果项目很复杂,可以使用多个 domain


var domain1,domain2 ..... , domain n


[该贴被brighthas于2013-02-20 10:52修改过]

JSDM是一个node.js的CQRS Domain开发框架。下面介绍一下他的基本命令。

通过 npm install jsdm 安装,如果浏览器需要,可以采用component install brighthas/jsdm 方式安装。

jsdm -a Aggre01,Aggre02 这个命令可以创建两个Aggre

jsdm -c handle01, handle02 可以创建两个 command handle

jsdm -e Aggre01.*.changeName 该命令可以创建监听Aggre01的changeName的事件监听器

jsdm -s service01,service03 该命令可以创建 服务。

jsdm ls 命令可以查看domain内部有什么组件


[该贴被brighthas于2013-02-27 18:43修改过]

你好

我有两个问题,

第一 event handler一般一会处理保存或者持久化
第二 业务的验证是的哪一块,比如说名字的唯一性,只有get方法好像不行

第一个问题:我想深度的剖析一下 Domain , 我不是讨论 DDD 和 CQRS,我是讨论“域”的概念,那么“域” Domain 就是一个黑匣子,也可以说是一个稍大的模块Module,那么Domain的内部状态的改变只能通过 command 方式,拿JSDM框架举例。
domain.exec 方法用来执行domain内部命令command的,内部触发对应的 command handle.来处理。
而我们的思维往往局限在数据库方面,其实当下domain是一个独立体,他真实需要的数据都在他自身的 cache缓存中,也叫“内存式真实体” , 这些真实体才是domain运行的根本。
而repository在domain内部只有一个get方法,用来得到一个根的“内存式真实体”。

那么如上所述,我们也就是可以把这些模模块块分一下类:

以下是Domain所在的位置
UI layout + APP layout + Domain layout + write DB

所以, event handle 把监听domain内部的变化持久到 DB 中,因为这中持久化不是 domain 内部的职责。

不知道我说明白没有。


第二个问题可以采用2种方法,一个是service用于验证服务,一个是用command handle 进行验证,我写一下代码,大概如下:


// 先定义一个Aggre根的类
{
init:function(data){
},
proto:{
updateName:function(args,my){
var self = this;
// 检查是否有重复的名字
my.service(
"checkRepeatName",{name:args.name,function(repeat){
// 如果重复那么不会更改名字
if(repeat){}else{
my.data(
"name",args.name);
// 激活
self.active();
}

}})
},
// 激活方法
active:function(){略}
},
name:
"User"
}

// 这种方法就是先创建一个aggre object,创建的这个可以定义为非激活的对象,然后updateName,如果通过,就激活对象。


// 第二个方法,采用command handle 进行处理
// 定义一个command handle
{
name:“create a User”,
handle:function(args,my){
//先验证,也用service
// 检查是否有重复的名字
my.service(
"checkRepeatName",{name:args.name,function(repeat){
// 如果没有重复的,就创建一个User
if(!repeat){
var User= my.getAgg(“User”);
var user = new User({name:'利奥'});
}
})
}
}

还有其他方式,先介绍这两个
[该贴被brighthas于2013-03-04 20:40修改过]

还有一个方法,代码如下:

第一步,定义一个判断是否User重名的service服务


function checkUserNameRepeat_Service(args,my){
// 具体代码略 ...
args.callback(err)
// 如果重复err就不为空。
}


第二步,定义一个User Aggre的init 初始化方法


// 在此输入java代码
......
function init(data,cb,my){

// 执行判断是否重名的服务
my.serivce(
"checkUserNameRepeat_Service" ,{name:"利奥",callback:function(err){
// 如果重名
if(err){
cb(err);
}else{
// 如果不重名
cb(undefined,data)
}

}});

}
......


再在 command handle 里面用repository创建时,内部会自动通过 init 进行验证处理.


// 得到 User 仓储
var UserRepo = my.getRepo(
"User");
UserRepo.create(data,function(err,aggreObj){
if(err){
// 创建失败,可能是名字重复 }
// 如果成功,那么继续。。。
.....


})

这是自动运行在浏览器客户端吗?前后端如有一张图就很清晰了。

JSDM可运行在服务器和客户端,如果程序只是客户端RIA,那么直接和服务器端编码是一样的。
参看 JSDM前后端整合
如果客户端运行调用服务器端的domain那么客户端不需要require("jsdm"); 而只需要 require("jsdm.proxy"); 通过 jsdm.proxy 可直接发送 command , 监听domain 的事件等。

这是浏览器代码,需要 component编译一下。


var Domain = require("jsdm.proxy");
var domain = new Proxy(
"/domain");
domain.exec(... )
// 执行 domain 的 command handle
domain.on () ;
// 监听domain领域的事件
domain.once() ;
// 仅监听一次

服务器端代码正常编写domain。

jsdm.middle 是服务器框架EXPRESS的一个插件,用来连接客户端和Domain的,可以理解 express 是应用层。这个插件也是用来权限控制的,不能让全部的domain都让客户端调用,但是权限控制和一些应用层的东西不能侵入domain,所以这个插件就是用来做这个的。



var Middle = require("jsdm.middle"),
middle = new Middle(domain,query);

// 加入on的过滤器
middle.addOnFilter...
// 加入执行command的过滤器
middle.addExecFilter(...);
// query 的过滤器
middle.addQueryFilter(...);

// 集成到 express框架
express.use(
"/domain",plugin.middle);

JSDM 是创建domain的框架, jsdm.middle 是domain层和client层的桥梁,jsdm.proxy 是客户端访问domain的代理。

当然这个框架太年轻,文档也不全,我有几个项目正在使用这个框架开发,之后我会写完整的文档。

[该贴被brighthas于2013-03-16 13:47修改过]

如果只是浏览器的RIA,不需要服务器,那么视图如下



如果需要服务器方式运行,那么。


2013-03-16 13:45 "@brighthas
"的内容
视图如下 ...

这下清晰多了,在B/S架构下,还是JS简单跨平台,原来我想做一个Jdon框架的安卓平台客户端,只要是用Jdon框架做Domain层开发的应用,用这个安卓客户端只要加上界面设计就能直接运行,比如最简单CRUD等。

jdon framework 不错,我更喜欢JS语法。

UI层 表示人的表达层,比如语言、眼神、面部表情。
应用层 表示人的显意识,显意识具有判断和过滤功能。
Domain层 是人的潜意识,比显意识强大三万倍,他是人赖以生存的层面。

人的表达层直接调用潜意识层很危险,所以需要个过滤层(人的世界观、信念)

UI 调用 domain必然要加入 应用过滤层,来根据不同的权限访问操作不同的domain资源。

2013-03-16 16:18 "@brighthas
"的内容
UI层 表示人的表达层,比如语言、眼神、面部表情 ...

你这段描述非常精彩,为什么领域驱动或建模没有被广泛推广,关键是客户没有意识到,或者说他们只看人的表面,看这个人长相,也就是看软件的UI界面,这个原因还是人的原因。

很多人自己都没有意识到领域概念存在。