业务建模:CQRS应用场景

12-08-29 clonalman
                   

分析了做过的一些项目(基于经典DDD),觉得应用CQRS的场景还是蛮多的,

特别是当出现模块之间出现相互依赖的时候,我这里说的应用场景不是为了保证查询数据的一致性,

而是由领域出发自然而然的过程。

举一个例子:ERP中的物流或供应链管理会有销售模块、采购模块、库存模块、财务模块等,每个模块会有不同的人来负责。

 

就以“销售订单”来举这个例子吧;

 

按照DDD设计,销售人员需要知道当前订单库存的装箱出货情况、财务的开票付款情况,

所以设计上销售订单(在库存上)单向导航的装箱单集合、出库交货单集合(多次装箱、多次出货)、

(在财务上)单向导航到收款单集合与发票集合(多次收款、多次开票)

 

如果销售、库存、财务分开模块设计,销售模块与库存模块和财务模块必然是存在依赖关系,

销售模块依赖于库存模块和财务模块,而库存模块和财务模块不能依赖销售模块,否则会出现交叉引用。

 

现在我想要查询,缺货的订单、已装箱未出库的订单、部分收款已出库、已开票已出库的订单:

把所有的订单明细与装箱单明细、出货单明细、收款单明细、发票明细进行数量、金额比较得过滤出订单,

说到这里有人可能会拍砖了,傻啊,不会在订单上加个库存状态属性、财务状态属性,(有上生产模块的话,再加个生产模块属性)

根据状态来过滤,看上去似乎很美,但问题是谁来更新这些状态?我们模仿一下DDD用一个对话来描述吧:

 

甲: 销售人员?

乙: 销售人员很闲的话可能会去干(老总肯定觉得他真闲得没事干了),否则他们更愿意看到库存、财务作业结果。

甲: 库存人员装箱出货作业完了通知销售人员一下行了,财务有笔款到帐了也通知一下,不就行了。(不通知,销售人员打电话去客户那里问问)

乙: 问题来了,库存、财务怎么知道这是哪个订单对应这个出货与款项了。

甲: 这些单据是根据订单自动生成,上面都有一个订单的订单号,可以根据订单号找到订单更新一下状态,不就好了。

乙: 你怎么知道出库单上关联的那个号码是订单号,它也可能是采购退货单的单号啊?

甲: 我们的编号都有规则订单号是以SO开头的,采购退货单号是以RP开头的,还有一个备注的属性,一目了然。

乙: 你是知道,但你的电脑它知道吗?(这问题问得很傻很天真)

甲: 我都参与了系统的开发,绝对是按照DDD规范来做,基础设施层、领域层、应用层、表现层,多清楚啊。

乙: 你这个判断关联号码的是否是订单号的规则是不是应该写在领域层的库存模块里?是属于领域的一部分?

甲:当然

乙: 那里怎么获得订单并改变它的属性状态的?

甲:是通过订单的服务接口,它是一个领域服务

乙: 这个订单服务接口应该在订单模块了对吧?

甲:当然

乙:销售模块赖于库存模块和财务模块,处于上层,你怎么获得对订单服务借口的引用?

甲:(打开代码……)哦,原来我们把这个判断号码的判断规则写在了应用层,但它应该属于业务的一部分的,怎么把它移到领域层上去了?

乙:你的库存装箱、出货有日志(History)吧,History一个事件集合记录作业过程,

可装箱单或出库交货确认完成时产生发布PickingEvent或DeliveryEvent等

销售订单或采购退货单订阅事件,让他们自己去判断这个单号是否属于自己,修不修该状态就是他们的事,跟库存没有关系。

当然在得在这个事件中带有它们感兴趣的信息(直接把这个聚合对象给他们也可以)

甲:领域事件出现了,那就可以使用CQRS :)

 

--------------------------------------------------------------------------------------

 

以上是我杜撰的一个过程,销售模块也不一定要这样设计,与库存模块、财务模块是可以解偶的,

解偶的话他们之间的联系,也是需要一个协调者或一个事件。

当然了,部分业务逻辑放在应用层,系统也能撮和着用,功能上没问题,只有完美主义者才会较真的。

 

其实,当初碰到这个问题,之前我的处理方案很土

(库存模块定义一个Service接口,在订单模块引用实现并注入容器,库存确认完成的时候获得Service接口执行,

跟领域事件差不多,实现接口实例算是订阅,通过Ioc容器来做发布)

 

这是一个地球人都能明白的例子,从纯业务角度来描述,涉及DDD分层、领域事件、CQRS,希望能对DDD理解有所帮助

个人观点是,领域事件的出现是自然而然的,CQRS应作为一个具体特定场景解决方案。

                   

11
banq
2012-08-29 18:29

看得出楼主是位实战经验丰富的大牛。读你的帖子是一种享受。

楼主已经看到了系统模块之间边界重要性,不同领域边界认为可以采取CQRS。通过事件或消息将模块之间进行切分松耦合又不失联系。

在经典解决方案中,还有引入SOA的JMS和ESB服务总线等方式来实现,应该和CQRS两者区别不大,两者都是异步方式,不同的是一个侧重事件,一个侧重消息。

当然,我个人认为CQRS和SOA还有一个本质区别,就是事件或消息的发源地不同,在CQRS中,由领域模型发出领域事件;而在SOA中,是由一个服务发出消息,这两种流派的竞争,就看你的应用是以服务为主,还是状态为主,当然,两者也是可以结合在一起的。

clonalman
2012-08-30 10:11

"大牛"不敢当,实践多了,总会有一些体会的 :)

任何“架构”对“领域模型”多少都有影响,

如果是这个影响正是领域“想要的”(正影响),就可以采用,否则(负影响)宁可不用;

采用哪个方案,都能到达目的地,就看你是像坐“火车”、“高铁”还是“飞机”

[该贴被clonalman于2012-08-30 14:30修改过]

banq
2012-08-30 16:42

2012-08-30 10:11 "@clonalman"的内容
如果是这个影响正是领域“想要的”(正影响),就可以采用,否则(负影响)宁可不用; ...

是啊,架构对领域的影响根据你的经验如何判断?特别是系统开始复杂时,“火车”“飞机”等方式可能影响的是不是效果不同?

clonalman
2012-08-30 19:47

同意,做飞机效果是好,但必须承受的负影响是:机票贵、 机场远、提前两个小时到达、飞机上不让打手机等。

如果说“我要到达某个地方,这个地方可能是很远”是领域模型,“飞机”是使用的架构,上面的负影响我根本不在乎,当然是使用“飞机”了;如果负影响是可能会发生“劫机”,那还坐不坐了?

同理,架构对领域的影响(从领域出发)可能是:

在领域层添加DCI里的Context、CQRS里的DomainEvent,如果这些元素是我们领域建模想要的、所期待的,就是正影响,反之,就是负影响。

所以,架构理解应该是领域某个特征自然延伸或实现方式,而不是用一个架构试图解决所有领域问题

[该贴被clonalman于2012-08-30 20:06修改过]

9Go 1 2 3 4 ... 9 下一页