采用cqrsnode框架开发的DDD CQRS例子

首先我们开发 Aggre 类 User

cqrsnode框架主页https://github.com/brighthas/cqrsnode


// cqrsnode容器会让User成为Aggre,但同时需要遵守一些编码约定。
function User(){

// Aggre 的自身状态都储存在 this._data 里
this._data.name = 'brighthas';
this._data.email = 'brighthas@[author]gmail[/author].com';
this.on('changeName',function(name){
// 这个是监听 changeName 事件,但这不是必须的
// 这里可以做一些事情,
// 请不要在这里更改自身状态,内部产生事件会自动调用自身的 updateDate 方法
})
}

// 这个静态方法是必须的,用于构建,但不是应用层使用,而是仓储使用。
User.create = function(){
var user = new this('id003');
return user;
}

User.prototype = {
// 更改名字,是一个DDD方法
changeName:function(name){
var event = ['changeName',name];
// 创建个event
this.publish(event);
// 发布event
// 当事件发布时,会发布到事件总线上,同时会 store event to eventStore.
},
// 这个是更改Aggre状态的, event 是事件,data是内部状态。
// 这个方法不能显式调用,而是当有事件产生时会自动调用,用于更改其自身状态。
updateDate : function(event,data){
switch(event.name){
case 'changeName':
data.name = event.data;
console.log(123)
break;
}
}
}
module.exports = User;

[该贴被brighthas于2012-09-19 08:41修改过]
[该贴被brighthas于2012-09-19 08:46修改过]

很好,是DDD+Event Sourcing,而不是简单的ES, 是围绕领域模型为核心,而不是领域模型被"强奸"被驱使,是领域模型发出事件通过事件中线趋势架构。

2012-09-19 08:55 "@banq"的内容
很好,是DDD+Event Sourcing,而不是简单的ES, 是围绕领域模型为核心,而不是领域模型被"强奸"被驱使,是领域模型发出事件通过事件中线趋势架构。 ...

谢谢 banq大哥鼓励! 我继续我的罗嗦。。。

然后我们开发一个监听事件的方法, eventHandles/UserchangeName.js


// 这个有个约定,监听事件的方法名要是 AggreName + eventName
// domain 是个CQRS顶级控制类,会自动注入这里,也是整个领域的入口。有时如果不需要访问domain层,而是要处理持久化db,或是发送email,就不用回到domain老家里操作领域层了,不过默认是会注入的,因为也许会用上。
function UserchangeName(event,domain){

// 当监听到这个changeName事件时,我们可以对他进行持久化,
// 这里的持久化可不是 event store,因为那个已经默认持久化完成了,
// 这里的持久化,可以持久化到其他数据库,用 query command 访问。
// 比如:异步保存
db.update(event.aggreId,{name:event.data}...)
// 大概就是这个意思。

// 我们也可以 email
emailer.push(...) ;
// 只是个例子

// 我们可以对领域其他对象进行操作,利用domain可以访问到领域层对象
var r = domain.repo('X');
// 得到X的仓储。
r.findById(.. . , functino(x){
// 得到其他对象 })

}

那么,我们如何操作DDD领域对象呢? 答案是需要创建 Command , 就是 CQRS说的Command
如下: commands/ChangeUserName.js


function ChangeUserName(id,name){

// 验证 id / name 是否正确,这是 command对象做的最有用的,
// 有些人认为 Commmand 只不过就是个数据对象,然后发给 handle,
// 其实不对的,因为 Command可以验证等功能。
......

this.id = id;
this.name = name;
}

我们有了 Command 我们还需要 Command Handle ,所以我们需要创建 commandHandles/ChangeUserName.js
这里也有个约定,就是handle和command名字要一致。


// 这是处理 command 的handle,
// 还应该注意的是,这里编码时,可以用 this.domain 访问到领域层入口,而这是注入的,不需要我们做什么。
// callback是回调,虽然cqrs方式的command不应该返回,但是最新调查,是应该返回是否操作成功和一些简单的提示信息的,和 query 返回的不同。
function ChangeUserName(cmd,callback){
var userRepository = this.domain.repo('User');
// 这里有个事,就是findById 是个 lock method ,也就是相同 id 时,会lock, 其他持有lock调用 next() 才能被下一个访问者使用。
userRepository.findById(cmd.id,function(user,next){
user.changeName(event.name);
next();
})
}


[该贴被brighthas于2012-09-19 09:15修改过]

2012-09-19 08:55 "@banq"的内容
很好,是DDD+Event Sourcing,而不是简单的ES, 是围绕领域模型为核心,而不是领域模型被"强奸"被驱使,是领域模型发出事件通过事件中线趋势架构。 ...

那么,肯定要有个 run 的启动方法了。 取名 run.js 吧。


// 实际应用需要找个web框架,比如 express 配合使用。
// web framework + cqrs framework 比较合适,我也想过,整合两个框架,我发现没必要,
// 因为cqrs框架独立比较好,这样外界可以随意的更换环境。

function run(){

// 这里有个事情,我们可以 new Domain 很多个领域,呵呵,这是可以的,领域之间可以通过 command 交流。
// 有人说 领域之间通过事件交流,我觉得不妥,那样就会出现事件耦合和不必要的复杂性,
// 每个领域都应该有个边界,我觉得通过 new Domain 创建个新领域,可以解决模块问题。
// 如果强制需要扩展,也就是需要领域和领域密切接触,这时可以在 Event Handle 里做文章,可以说可以无限扩展。
var Domain = require('cqrsnode');
// 得到Domain类
var Store = require('cqrsnode.store').Tiny;
// 我们需要一个EventStore 的实际持久化DB.这里选择了Tiny,还可以选择 Mem 和 Mongdb / mysql 等等。随自己喜欢了。

// 创建一个领域,当我们做这一步之后,内部会做一些好玩的事,
// 比如: 加载我们之前创造的那些东西。
// 还有就是根据 Aggre 自动创建对应的 Repository 等功能
// 包括 自动 extend Aggre等等
var domain = new Domain({mainPath:__dirname,Store:Store});
// 得到 change user name 的Command类
var CMD = domain.commands.ChangeUserName;
// 创建一个Command实例
var cmd = new CMD('id001','brighthas');
// 执行方法,内部会让指定的handle处理这个command
domain.commandBus.execute(cmd,function(r){});
}

就介绍这些吧,适合对CQRS概念了解的童鞋看。

[该贴被brighthas于2012-09-19 09:45修改过]

2012-09-19 08:55 "@banq"的内容
很好,是DDD+Event Sourcing,而不是简单的ES, 是围绕领域模型为核心,而不是领域模型被"强奸"被驱使,是领域模型发出事件通过事件中线趋势架构。 ...

我觉得banq判断代码的标准有些不准确吧!

楼主的代码,我看了下,只有一个user模型,user中有一个changName的方法,changeName方法触发事件,首先改变user模型自己的状态,然后通过事件总线发布出去,这难道不是标准的event sourcing吗?为什么说不是简单的ES。那请问简单的ES是怎样的?判断的依据是什么?

另外,我之前的帖子中,你对我的代码点评时都用了很多其他概念去审视,比如DCI架构,并且认为我的代码中model直接和event source绑定,所以认为不是DDD。那为什么他的代码中根本没用到DCI,也没有角色的概念,为什么就是DDD了呢?

如果你是依据模型是否处于主动地位来判断是否是DDD,那我的代码与楼主的代码的唯一区别就是模型的方法被谁调用的问题,楼主是在command handler中调用模型的方法,然后由模型发出事件驱动其他东西;而我的代码是通过应用层服务去调用模型的方法,然后也是发出事件驱动其他东西。

这有本质区别吗?请指教,呵呵。

2012-09-19 13:26 "@tangxuehua"的内容
楼主是在command handler中调用模型的方法,然后由模型发出事件驱动其他东西;而我的代码是通过应用层服务去调用模型的方法,然后也是发出事件驱动其他东西 ...

我赞赏的做法是:用户 操作 UI====>发出 Command ==>模型 ===>事件。这是典型一个CQRS做法。

如果你的代码也是这样,那可能是我眼花,看错了,对不起了。

2012-09-19 13:26 "@tangxuehua"的内容
只有一个user模型,user中有一个changName的方法,changeName方法触发事件,首先改变user模型自己的状态,然后通过事件总线发布出去,这难道不是标准的event sourcing吗?为什么说不是简单的ES。那请问简单的 ...

这个不只是一个EventStore,而是以领域核心为中心的。你需要阅读全部的CQRS DDD,阅读全部的BANQ的文章,就会慢慢领会 CQRS 了,BANQ他说话很婉转,但我可以负责的和你说: 你还需要阅读大量 DDD CQRS 的东东,希望你能接受这个意见。

2012-09-19 16:25 "@brighthas"的内容
这个不只是一个EventStore,而是以领域核心为中心的。你需要阅读全部的CQRS DDD,阅读全部的BANQ的文章,就会慢慢领会 CQRS 了,BANQ他说话很婉转,但我可以负责的和你说: 你还需要阅读大量 DDD CQRS 的东东,希 ...

呵呵,我相信我读的这方面的东西比你想象的要多的多。

我希望的是说要说有意义的话,比如你问我就答,我问你就答,而不是我问,然后你说就是这样的,无需解释,要知道原因,自己去看banq以前写的这么多东西。那如果每个人都像你这样说话,那就不存在讨论了。

我希望每个人说话的方式都是摆事实讲道理,得出观点的同时会给出思路或理论依据或例子。否则,我只会觉得你在空谈。

干我们这一行的难道不应该是思维严密,逻辑严谨的吗?任何大理论或大的架构,都是很注重细节的。只有细节的差别搞清楚,才能真正懂得架构或框架的意义。
[该贴被tangxuehua于2012-09-20 13:39修改过]

2012-09-20 13:36 "@tangxuehua"的内容
呵呵,我相信我读的这方面的东西比你想象的要多的多。 ...

路还有很远,慢慢学习吧。呵呵,说多无益。

问一下,如果名称有这样的规则:名称里只允许英文和字母。
那这个判断加在哪里呢?
user的changeName方法里,还是updateDate里