最近公司准备开发一个新产品,需要重新设计一套新的框架,但是就这框架中各模块的通信方式,大家产生了争论,主要集中在各模块的交互方式是消息耦合还是接口耦合。

需求大概这样,我们需要封装一套客户端SDK, 暴露一系列API给外部用,而这套SDK内部会有很多模块组成,这些模块之间相互会有交互。

第一种设计是基于接口耦合,框架如下:


这种接口方式的设计要点是:
a. 各模块以类似COM组件的方式封装和暴露接口,也就是说模块会以接口的形式暴露接口,并且以Sink的方式通知外部事件。比如模块A的接口如下
class IA
{
public:
    virtual void fun1() = 0;
    virtual void fun2() = 0;
    .
    virtual void int AdviseSink(IASink* pSink) = 0;
    virtual void bool UnAdviseSink(int nCooki) = 0;
}

class IASink
{
public:
    virtual void Event1() = 0;
    virtual void Event2() = 0;
    .
}
b. 有一个统一的模块管理平台来管理所有模块,可以通过该平台查询和加载需要的模块,然后后得到相应的接口指针进行操作。
c. 各模块间的交互全都通过直接调用其他模块的接口或是订阅该模块的Sink来实现。

第二种设计是基于消息耦合,框架如下:

这种消息方式的设计要点是:
a. 各个模块只有一个消息处理接口。 
    class IMessageHandler
    {
    public:
        virtual void ProcessMessage(Message& msg) = 0;
    };
b. 中间的消息总线提供消息的订阅和分发,各个模块会向消息总线注册自己感兴趣的消息。
c. 需要调用某个模块接口 或是 触发某个事件时,都是通过向消息总线发送消息的方式, 然后由订阅消息的模块执行。

上面2种架构的设计方式各有优劣,下面我们来简单比较一下:
(1)耦合性: 尽管基于接口的耦合已经降低了耦合性,但是相比消息来说,显然是消息方式耦合性更弱。
(2)可扩展性: 某个模块新增加一个接口, 接口方式需要新加接口函数,而消息方式只需要新加一个消息类型。即使新增加一个模块,消息方式只是新增加几个消息处理类型,非常方便。所以可扩展性来说,显然也是消息方式占优。
(3)性能: 接口方式是直接调用,可是消息方式需要经过消息总线过滤分发, 显然性能上接口方式更高。 
(4)编码安全性,接口方式是强类型,接口一修改,编译时就能很快发现问题;消息方式却是弱类型,消息修改后,有可能要到运行时才能发现问题, 另外很多消息内容要做强制了类型转换才能使用。
(5)文档要求: 显然接口方式相对比较清晰,消息的话每个消息都要详细定义,并且严格按照该定义执行。
(6)可调试性: 显然接口方式要方便些,消息很可能不小心就会引起混乱。
(7)监控过滤方便性:消息方式走同一总线,可以很方便的增加过滤和监控功能, 接口方式则因为各个模块interface和Sink各不相同,增加这些功能没那么方便。
(8)跨线程或是跨进程,甚至跨机器调用:显然接口方式基本做不到,消息方式的话只要修改总线就可以做到。
(9)异步支持: 消息方式可以很方便的支持异步,接口方式则做不到。

经过上面的比较, 我们可以得出一些结论:
消息方式的强项是耦合性和扩展性,以及监控的方便性,个人感觉比较适合于Server端的大规模应用。
接口方式的强项是性能高效以及开发的方便性, 比较适用于同一进程内客户端的小规模应用。

但是大部分时候, 对于架构师或是公司领导,他们会更关注可耦合性和可扩展性,所以他们会倾向于选择消息方式,尽管有时可能不是那么适用。

另外,个人觉得编译时静态类型检测是C++的优势,能让我们高效而可靠的开发程序,我们不应该放弃这些优势而去把C++当作弱类型的动态语言来使用,我在 软命令接口的适用场合 一文也有相关描述。

最后,对于该框架的设计,其实我个人倾向于上面2种方式的结合, 即各个模块的入接口(调用接口)走接口方式,而各模块内部触发的事件走消息总线的方式,虽然没人采用这种方式,还是在这里记录一下。

一个多月了,消息还是接口,领导们老大们关于走何种架构还在争论中...

呵呵,不知道各位看客会作何选择?
posted on 2012-10-12 22:50 Richard Wei 阅读(4582) 评论(5)  编辑 收藏 引用 所属分类: 架构体系

FeedBack:
# re: 消息耦合还是接口耦合[未登录]
2012-10-13 21:58 | 123
反驳下:

多核技术发展到现在,接口比消息性能好显然是说不通的。毕竟消息的方式更有利于并行。
另外,个人觉得接口方式虚函数一大堆,各种继承,也不明白开发能有多方便。

最后关于接口方式的结论,出发点也不明确,既然是“同一进程内客户端的小规模应用”,接口方式也显得太大了,难道是为了模式而模式?  回复  更多评论
  
# re: 消息耦合还是接口耦合
2012-10-13 22:28 | Richard Wei
@123
我们是客户端应用,各模块内部却确实会有多线程,但是各模块间的调用及事件触发都是跑在主线程里,所以这种情况下,如果走消息,基本上要给每个消息维护一个数组,内部保存哪些人订阅了该消息,当该消息到达时进行依次分发,所以这里无论是内存大小还是性能都会比接口直接调用消耗更多。

接口每个方法都有各自不同的明确的参数定义, 消息的话因为要求所有消息参数一致,所以会导致参数含义不明确,内部保存的指针需要强制转换才能使用。

"客户端小规模应用";是相对“大规模Server集群”, 其实我们要做的东西本身也挺大挺复杂,好几十人同时开发。  回复  更多评论
  
# re: 消息耦合还是接口耦合
2012-10-14 15:56 | irons
我觉得接口更作为一个模块内的设计,而模块间还是用消息更好。
而多大的“模块”算是一个“模块”呢?还得自己把握。  回复  更多评论
  
# re: 消息耦合还是接口耦合
2012-10-15 09:44 | zaccheo
和楼主的情况差不多,同样有一个新项目的设计。采用的思路类似于楼主的第三种思路:各个模块对外提供接口(模块实现的业务接口);模块内部状态变化,向订阅者发消息。消息使用 googlebuffer 这样很容易从二进制的消息体中反序列化出来消息的结构体。

基于接口的设计中有一个要注意的问题:接口指针的生命周期管理。如是使用智能指针,是否能避免循环引用的问题?

看了楼主的分析,我现在倒觉得第二种更好。各个模块间完全被隔离开了。  回复  更多评论
  
# re: 消息耦合还是接口耦合
2012-10-15 10:25 | Richard Wei
@zaccheo

接口的管理,我感觉有2种方式: 一种是基于COM的引用计数,大家都可以保存接口指针,这里要避免循环引用;还有一种是让Module Manger统一管理,也就是只有Module Manager保存有接口指针,其他模块不保存接口指针,要使用时统一向Module Manger要。

第一种高效,第二种相对安全  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理