Author:fox Date:2010-3-30
UI的全称是User Interface,但公司里的通用定义是:用于web类接入服务的后端接入模块,常用于用户身份校验、页面渲染和协议转换等功能,位置介于web服务器和逻辑服务器之间。在Webim系统结构中,由于前后端基于数据分离,即后端只是提供数据,由前端js完成页面渲染,因此Webim系统内的UI更多地承担了身份校验和协议转换的功能。
重构前WEBIM-UI主要存在的问题如下:
1. 代码庞大,组织较混乱,最大的一个源文件代码超过1w行。
2. 流程不够清晰,指令流程与具体实现耦合较紧,导致修改指令处理流程和新增指令较为困难,特别是后期同时支持bridge和普通用户时这一点尤为突出。
3. 公共逻辑代码抽象的不够,很多相同的逻辑重复实现,冗余代码较多,增加了维护的难度。
4. 随着业务需求的不断变化,已有的接口设计不能满足需要,需要进行接口层面的扩展。
如之前单一的根据后端回包中的code决定给前端回包的result,随着需求实现协议的增多,会出现不同协议返回相同code代表不同语义,需要输出不同的result。
5. 为了解决前端指令与后端接口存在先天的矛盾,需要UI从结构上灵活的支持组合指令。
产品为了提高用户体验,前端设计的趋势是要求大而全的接口,尽量减少与后端通信交互的次数。
而UI与后端服务的接口更注重的是接口语义的原子性,因此对UI的结构提出了更高的要求,需要能轻易地支持前端指令与多条后端接口的映射。
在重构前花了一段时间整理所有指令的处理流程,事实证明这一步是非常有必要的。整理后把系统的指令分为几类:welcome(登陆)、transfer(通用上行指令)、logout(登出)、pick(异步获取消息)、read(其它),其中划为transfer的指令占据了绝大部分。除去相对特殊的welcome指令,transfer/logout/read的指令处理流程相对类似,大致可以分为两步:解析上行请求组装发送后端请求包(pack) 和 解析后端应答包格式化输出json(format)。区别在于组装的后端请求包格式不同,并且transfer的解析/格式化过程相对复杂,可能涉及到username <–> uid的转换。对于pick,可以把该指令进行划分成上下行:上行pick用于注册通知通道及保持用户在线,下行notify用于处理通知消息。经过这样划分,pick的处理流程与transfer等指令几乎一致。
对于transfer类指令,单独整理了其实现流程,流程图如下:
从流程图可以看出,transfer类型各条指令的区别在于:解析请求上行组装协议xml(pack) 和 把后端响应包转化为json(format)。通过提取出通用的pack和format接口,分别建立pack和format的映射表,建立指令名称(command) -> 指令对应接口实现(function)的映射关系,即可屏蔽掉这层差异,保证处理流程的统一。在format过程中,发现json回包中result和errorMessage字段由后端响应包中的code和指令名称(command)决定。对这部分实现配置化,避免了代码中出现过多的if/else条件分支、新增指令时对程序的修改,便于模块的后续维护和升级。
在transfer指令处理流程统一的前提下,组合指令的实现就变得水到渠成了。让每条组合指令从配置中读取需要执行的单条指令序列列表,遍历列表顺序执行每条指令,汇总执行结果输出即可。
组合指令的处理流程如下:
目前实现的组合指令相对比较简单,只是把多条指令的执行流程串行化,参数都在初始时全部传入,各条指令分别从初始的请求中解析参数,把各条指令的json包追加到结果中。即组合指令流程序列中各条指令没有明显的逻辑依赖关系,可以任意排列先后顺序。对于部分关键字段如uid做了特殊处理,会自动解析前一条指令应答包中的uid,作为下一条指令的请求参数。由于所有指令从公共数据存储(mc_pack_t)解析请求,并没有按请求区分参数,如果不同的请求参数重名时,会出现该参数无法区分的情形。
考虑到当前的业务需求,组合指令目前的实现方式比较简单,还有较大的改进空间。初步整理了下,大致的改进点如下:1、为每条指令建立单独的请求参数存储;2、支持变化的指令序列,可以根据初始的请求参数决定指令序列;3、支持组合分页类的指令,即允许单条指令在组合列表中执行多次。仔细深究组合指令的执行流程,发现与设计模式中的管道-过滤器模式比较类似。基于管道-过滤器模式的系统由管道和过滤器组成,每个处理步骤被封装成一个过滤器组件,数据在相邻过滤器之间通过管道传输。每个过滤器可以单独修改,功能相对单一,顺序可配置。
针对管道和过滤器实现时一些关键的点,结合组合指令的实现方式进行分析如下:
管道和过滤器实现的前提,在于把系统任务分割为相对独立的任务,任务之间没有明显的逻辑
依赖关系,仅关注前一个任务的输出数据流。这样通过数据流把各个任务串联起来,可以灵活替换某个步骤,实现不同的结果输出。WEBIM-UI中对组合指令按照后端独立接口分解成不同的指令,每条指令的逻辑处理可以看作一个过滤器,过滤器之间管道传输的数据流由三部分组成:去除前一指令解析参数后的请求、前一指令输出的部分参数(可选)和前一指令输出的中间结果。
2. 定义沿管道传输的数据格式
对于每个过滤器,考虑到后续的扩展和灵活性,倾向于制定一个统一的数据格式。WEBIM-UI中指令之间传输的请求数据通过mc_pack_t表示,中间结果采用json表述。
3. 如何实现过滤器链
所有的过滤器都服从统一的过滤器调用接口,则形成了过滤器链(Filter Chain),常见过滤器链
连接是采用推的方式实现的,即在前一个过滤器流程运行结束时主动调用FilterChain.next()转到下一个过滤器。WEBIM-UI里采用维护指令列表下标的方式来标记当前运行的指令,在指令处理结束时对下标操作,指向下一步需要执行的指令。
4. 设计和实现过滤器
对过滤器抽象出公共的interface,仔细划分每个过滤器的功能,实现对应接口即可。WEBIM-UI
中抽象出pack和format接口,建立每条指令到这两个接口实现的映射。
5. 建立流水处理线
过滤器的部署可以通过配置文件指定其调用顺序,WEBIM-UI中在配置文件中指定组合指令对应的指令列表和各条指令的顺序。
除了这些共同点,WEBIM-UI组合指令的实现与管道-过滤器的原则有一些区别,主要的区别在于目前组合指令的实现会共享状态信息,各条指令运行过程中会共同使用同一个上下文。
有了清晰的重构目标,结合当前的业务需求,在深入了解业务流程的基础上抽象出公共的流程,模块的重构之路变得简单很多。