欢迎光临汤雪华的博客

一个人一辈子能坚持做好一件事情就够了!坚持是一种刻意的练习,不断寻找缺点突破缺点的过程,而不是重复做某件事情。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

关于对象之间通信的一点思考

Posted on 2012-02-12 13:42  netfocus  阅读(6183)  评论(4编辑  收藏  举报
经典的DDD的告诉我们如果一个领域概念是一个跨多个聚合的动作,比如转帐,那么就应该用领域服务来实现这样的业务概念。领域服务的输入和输出参数都是聚合根,领域服务内部按照业务逻辑规定的执行顺序,按照面向过程的方式,逐个调用相关聚合根的相关方法完成整个业务操作。这种方式的优点是:1)清晰的表达和封装了业务逻辑;2)代码清晰,容易理解,代码可读性强;缺点:1)基本的OO思想告诉我们,对象与对象之间应该是通过发送消息和接收消息的方式来通信的。但是通过前面这种方式,对象之间不再像我们想的那样会通过发送消息和接收消息来相互协作了,而都是被动的被一个第三方协调者服务对象来统一协调,这其实是一种面向过程的思维,而非基于消息通信的面向对象思维;2)这种通过领域服务的方式只强调了对象的个体行为(即对象只有改变自己状态的行为),而没有注重对象之间的交互行为,对象应该还有能力发送消息给其他对象或者响应其他对象发送的消息;
 
那么为了能让对象之间有交互行为,能相互发送消息和接受消息,该如何做呢?一般来说有两种方法:1)方法调用,A对象调用B对象的方法,这种方法最常用最直接,但是缺点是会导致A依赖于B。这个问题我们往往会通过面向接口编程在一定程度上消除这种依赖关系;2)事件消息模式,生产者-消费者模式,即采用触发事件响应事件的模式;通过这种方式,A对象就不是直接调用B对象的方法了,而是触发一个事件,然后B对象去响应这个事件,或者如果采用消息总线的方式的话,就是A对象通知消息总线发布某个事件,然后消息总线发布这个事件,然后B对象响应这个事件。通过这样的通信方式,巧妙的将A,B两个对象的关系进行了解耦,本来A直接依赖B(A->B)的情况变成了A与B相互独立,变成了这种方式:A->消息<-B。
 
CQRS & Event Sourcing架构采用的通信方式。首先,在这种方式下,聚合与聚合之间的关系通过ID表示,而不是通过对象引用的方式来表达对象关联。这就让我们不能直接通过简单方便的对象导航的方式定位到关联对象并调用其方法了。那么这种方式下,对象之间如何通信呢?就是采用上面提到的基于事件消息的通信模式。CQRS架构中Command端的大致执行流程是:UI发送Command到CommandService,CommandService根据传入的Command调用相关的Command Handler,Command Handler获取相关的聚合执行相关方法,聚合执行方法后触发事件,消息总线发布该事件到外部,事件响应者(Event Handler)响应事件。关于最后一步的实现还有一些目前不确定的方案,目前采用一般两种方式:1)Event Handler生成一个新的Command然后发送给Command Service;2)Event Handler直接取出目标聚合根,然后调用其方法;另外,一般两个聚合之间采用异步的方式通信,所以我们会集成NServiceBus等异步消息通信框架实现消息的传递;
 
关于对象之间通过直接方法调用来交互,和通过发送消息来交互的一点思考:
1)对象A直接调用对象B的某个方法,实现交互;直接方法调用本质上也是属于一种特殊的发送与接受消息,它把发送消息和接收消息合并为一个动作完成;方法调用方和被调用方被紧密耦合在一起;因为发送消息和接收消息是在一个动作内完成,所以无法做到消息的异步发送和接收;
2)对象A生成消息->将消息通知给一个消息处理器(如NServiceBus,EventBus之类)->消息处理器通过同步或异步的方式将消息传递给接收者;这种方式是通过将消息发送和消息接收拆分为两个过程,通过一个中间者来控制消息是同步还是异步发送;在消息通信的灵活性方面比较有优势,但是也带来了一定的复杂度。但是复杂度一般可以由框架封装,消息的发送方和接收方仍然可以做到比较简单;
 
最后,在说一下关于事件与消息的关系的论述:
事件和消息可以说是从不同方面描述的同一个东西,消息是事件发生后产物,消息发送必须有发送事件发生才能实现。每次事件只发送一次消息,事件和消息是一对一的。