特别是当出现模块之间出现相互依赖的时候,我这里说的应用场景不是为了保证查询数据的一致性, 而是由领域出发自然而然的过程。
分析了做过的一些项目(基于经典DDD),觉得应用CQRS的场景还是蛮多的,
特别是当出现模块之间出现相互依赖的时候,我这里说的应用场景不是为了保证查询数据的一致性,
而是由领域出发自然而然的过程。
举一个例子:ERP中的物流或供应链管理会有销售模块、采购模块、库存模块、财务模块等,每个模块会有不同的人来负责。
就以“销售订单”来举这个例子吧;
按照DDD设计,销售人员需要知道当前订单库存的装箱出货情况、财务的开票付款情况,
所以设计上销售订单(在库存上)单向导航的装箱单集合、出库交货单集合(多次装箱、多次出货)、
(在财务上)单向导航到收款单集合与发票集合(多次收款、多次开票)
如果销售、库存、财务分开模块设计,销售模块与库存模块和财务模块必然是存在依赖关系,
销售模块依赖于库存模块和财务模块,而库存模块和财务模块不能依赖销售模块,否则会出现交叉引用。
现在我想要查询,缺货的订单、已装箱未出库的订单、部分收款已出库、已开票已出库的订单:
把所有的订单明细与装箱单明细、出货单明细、收款单明细、发票明细进行数量、金额比较得过滤出订单,
说到这里有人可能会拍砖了,傻啊,不会在订单上加个库存状态属性、财务状态属性,(有上生产模块的话,再加个生产模块属性)
根据状态来过滤,看上去似乎很美,但问题是谁来更新这些状态?我们模仿一下DDD用一个对话来描述吧:
甲: 销售人员?
乙: 销售人员很闲的话可能会去干(老总肯定觉得他真闲得没事干了),否则他们更愿意看到库存、财务作业结果。
甲: 库存人员装箱出货作业完了通知销售人员一下行了,财务有笔款到帐了也通知一下,不就行了。(不通知,销售人员打电话去客户那里问问)
乙: 问题来了,库存、财务怎么知道这是哪个订单对应这个出货与款项了。
甲: 这些单据是根据订单自动生成,上面都有一个订单的订单号,可以根据订单号找到订单更新一下状态,不就好了。
乙: 你怎么知道出库单上关联的那个号码是订单号,它也可能是采购退货单的单号啊?
甲: 我们的编号都有规则订单号是以SO开头的,采购退货单号是以RP开头的,还有一个备注的属性,一目了然。
乙: 你是知道,但你的电脑它知道吗?(这问题问得很傻很天真)
甲: 我都参与了系统的开发,绝对是按照DDD规范来做,基础设施层、领域层、应用层、表现层,多清楚啊。
乙: 你这个判断关联号码的是否是订单号的规则是不是应该写在领域层的库存模块里?是属于领域的一部分?
甲:当然
乙: 那里怎么获得订单并改变它的属性状态的?
甲:是通过订单的服务接口,它是一个领域服务
乙: 这个订单服务接口应该在订单模块了对吧?
乙:销售模块赖于库存模块和财务模块,处于上层,你怎么获得对订单服务借口的引用?
甲:(打开代码……)哦,原来我们把这个判断号码的判断规则写在了应用层,但它应该属于业务的一部分的,怎么把它移到领域层上去了?
乙:你的库存装箱、出货有日志(History)吧,History一个事件集合记录作业过程,
可装箱单或出库交货确认完成时产生发布PickingEvent或DeliveryEvent等
销售订单或采购退货单订阅事件,让他们自己去判断这个单号是否属于自己,修不修该状态就是他们的事,跟库存没有关系。
当然在得在这个事件中带有它们感兴趣的信息(直接把这个聚合对象给他们也可以)
甲:领域事件出现了,那就可以使用CQRS :)
以上是我杜撰的一个过程,销售模块也不一定要这样设计,与库存模块、财务模块是可以解偶的,
解偶的话他们之间的联系,也是需要一个协调者或一个事件。
当然了,部分业务逻辑放在应用层,系统也能撮和着用,功能上没问题,只有完美主义者才会较真的。
其实,当初碰到这个问题,之前我的处理方案很土
(库存模块定义一个Service接口,在订单模块引用实现并注入容器,库存确认完成的时候获得Service接口执行,
跟领域事件差不多,实现接口实例算是订阅,通过Ioc容器来做发布)
这是一个地球人都能明白的例子,从纯业务角度来描述,涉及DDD分层、领域事件、CQRS,希望能对DDD理解有所帮助
个人观点是,领域事件的出现是自然而然的,CQRS应作为一个具体特定场景解决方案。,分析了做过的一些项目(基于经典DDD),觉得应用CQRS的场景还是蛮多的,
个人观点是,领域事件的出现是自然而然的,CQRS应作为一个具体特定场景解决方案。