为什么分层架构中会用命令总线和查询总线模式?
有个以前小伙伴看我最近开源项目,问我为什么要用命令总线(CommandBus)以及查询总线(QueryBus),感觉非常诡异,它既不是PipeLine,也不提供复杂构造模式,仅仅是为了找到某个处理器,就需要这么复杂的开发范式吗?
我估计他以前在团队里只是被动强迫使用,疑虑颇多。
其实很简单,使用CommandBus或QueryBus,你首先需要接触到CQRS架构模式,即命令查询职责分离。
不管你是否选择面向对象语言,CQRS模式都能帮助你理清复杂业务,减少不同的复杂业务用例之间的相互影响。
但不是所有领域都需要使用CQRS的,尤其像所谓的支撑域(Supporting/Generic)、平台域(Platform)等, 都是复杂度有限的领域。
但是一旦碰到复杂的核心域,采用CQRS就是个很好的选择,这样你就不会被某种单一模型(约定)绑架,可以更灵活的实现复杂用例。
但即使我们采用CQRS,在某个单一的命令处理器中依然不能完成所有工作,复杂度超表。
例如执行购物车下单这个命令, 我们就可能需要进行以下处理:
- 检测库存并锁库存
- 生成商品快照留痕
- 创建订单(创建订单和记录商品在同一个约束里)
- 生成支付订单返回
你看看这是一个非常复杂的业务用例,我们不可能在表现层的Controller里完成,或者在RPC框架的Adapter里完成。
那样就太蠢了,中台思想在哪里,解耦在哪里?
所以我们必然会在Application中Services文件夹下创建一个CreateOrderAppService。
但这只是一个门面(Facade),如果你将所有业务逻辑都放在这里,你将违背一个很重要的原则:单一职责。因为你当前的行为,未来你还可能违背开闭原则及接口隔离原则。
所以这个门面我们只能协调大量命令处理器(Handler),协调基础设施共同工作。
这时候你就会发下一些问题:
依赖性问题
你的门面(Facade)依赖了大量Handler, 你能想象你需要注入或者在构造函数中定义超过3个Handler哪有那种场景吗?你会瞬间得到一个“巨大类”, 你的耦合度爆表,你无法自由替换。生命周期问题
如果我们规避第一个问题,从注入改为直接new, 那生命周期就会完全固定,灵活性失去。无法替换Handler
我知道你可能会说我修改接口注入就可以啦,但请问你能实现插件化还有A/B测试这种替换吗?无法实现精准统一的应用级控制
我知道有人在上层用IF/ELSE,有人用一个Abstract,但那样是真的蠢爆了好么?
最后一个问题总线模式是否损耗性能?
在C#或JAVA中,通常通过扫描指定文件夹将命令与处理器的关系,将查询与处理的关系注册到容器,或自定义类型存于内容,又是扫描又是反射,是否会拖慢性能?
这得看情况,因为它有两个阶段:
一个是启动阶段,遍历扫描可能需要5-200ms, 因为I/O(读取元数据)有开销、反射有开销、过滤匹配有开销。
一个是运行时阶段,分发调用的时间几乎可以忽略不计。
所以影响性能只有启动阶段,但这对单体应用基本没什么意义。
对于微服务启动阶段增加二三十毫秒,大部分公司还是能接受的。
要是超过, 那一定是服务的粒度出问题了。
发表回复