客户端模块化解耦实践 - Router

客户端模块化解耦实践 - Router


背景

随着业务进入快速发展期,业务线拓展迅速,项目结构变得庞大复杂,导致迭代的成本越来越高。项目的越发庞大也使得整个工程编译时间越来越长。进行项目拆分后模块化运行(项目自运行、自管理)是一个较好的出路,但项目间的复杂相互引用导致我们无从下手,而如何去除这些项目间的引用则是今天的主题

产生

首先我们可以整理下现状,我们的碰到的最大问题其实就是项目间杂乱无章的耦合、引用

actu-structor

针对这些耦合、依赖,我们可以简单的分析:从理论上来说,每个项目负责单独的业务,应该本身就是相互独立的存在。但是实际上,处于同一个公司业务体系中,项目与项目之间不可能真正做到完全的切割。(公司与公司间都可能存在合作,项目与项目间当然就更可能了)

看来想从业务源头想去除业务依赖是不太可能了,我们只能从我们技术角度去进行项目间耦合、引用的去除了。而其中最关键的一点就是:耦合、相互依赖 , 而Router的概念正是为了解决这些问题而提出的。

那Router到底是什么呢?

Router是一个具有收口分发思想的、用于处理模块(项目)间直接依赖、调用的模型。最直接的体现其实就是一个约定、一个规则,按照约定规则进行解析,而后分发。

hope-router

发展

针对以上的一些问题,其实我们能get到一些关键特性,收口分发

OK,既然关键信息已经得到,伪代码很快就能敲出:

    public static void jump(String rule){
        if(rule){
            gotoBusinessActivity()
            return;
        }
    
        if(rule){
            invokeBusinessFunction();
            return;
        }
    }

Perfect!!! 调用方便,确实做到了 收口分发

但是随着业务发展,弊端显示的也越发明显。仔细分析过后,可以总结出一些问题

膨胀

  • 随着业务的增长,膨胀的太厉害,成千上万行代码坐落在这个类中,名副其实的大杂烩

维护成本

  • 所有项目均同时维护,一个项目改错可能导致其他项目也受到影响
  • if-else的逻辑维护太复杂
  • 分发逻辑是 hardcode ,一旦错误将无法进行修改
  • 各项目无法真正分离

扩展

  • 从代码结构不难看出,这里的分发实质上是'亲力亲为'并非真正的分发,而‘亲力亲为’是做不到真正的拆分的

从上面几个问题来看,其实收口并不难,如何做好分发这才是一件难事。

回顾需求与现有的问题,觉得自己的if-else模型其实丢了一件事:没有抽离出一套统一的流程

那流程怎么定义?

我在设计的时候喜欢类比现实生活,而Router也不外乎。而这个模型就很有趣了

我想要寄快递

这个是我需求,那么该怎么办呢?最重要的两点

  1. 地址
  2. 包裹

我只需要给到快递员地址以及包裹,那么就会给我送到(除非地址错了),但是这里要强调一点,我寄快递,并不是快递员来接受这个包裹。快递员只是,而真正接收的是所在地址的人。

想到这里其实就可以梳理出Router的核心生命周期与非核心生命周期了。当Router把调用者的意图转达给接受者时就已经完成了它的周期. 接下来的事就是接受者干的了,跟Router无关

invoke

到这里其实Router的基本框架已经确定(因为流程已经抽离), 那么剩下的就是具体模块的事了。不难分析出Router的两大核心模块:规则处理执行器

规则

通过模型,我们知道了两个必须的东西:地址包裹 ,那对应到程序中无非就是 目标指向数据 ,再契合到我们当前的业务场景,规则不难提炼出来:

protocol://path?data

是不是很眼熟?其实就是我们通用url的简化版本。其实重点就三个信息:

  1. 协议,用于版本区分、功能区分等
  2. 目标指向,路径,意图的接受者
  3. 数据,携带的数据

同样我们的规则定义为

tctclient://project/module?key=value

协议定义完毕了,但是难点来了: Path的映射如何处理

举个例子,Pathhotel/list ,但是这个只是一个字符串,我真正想要调用的是 酒店列表 HotelListAction ,如何将 Path目标指向 进行关联,这是一个问题。在讨论的过程中,其实有三种方案:

  1. if-else 直接逻辑关联
  2. 使用 Map 进行关联
  3. 使用 xml 进行关联

其实从本质上来说,都一样,都是为了数据关联。但是在维护、扩展上是有区别的。

if-else 这个不用说,直接的引用会产生的非常复杂的依赖网,而且无法抽离出分发这块,所有的分发都需要在当前环境下进行关联。

Map & XML的逻辑很像,都是 path处理类 的对应。但是在维护和扩展性上我们最终选择了xml, 优点很明显:无代码耦合可动态更新 。而MAP的优点在于,不需要进行数据加载,节省了这一部分的性能。

执行器

执行器的概念就好理解多了,当一段规则被解析成可读的意图后,我需要将意图、数据传递给接受者,而这个接受者可能是各个业务所处理的。因此我需要设计一个接口(不然没指向性了)来进行接收,而业务部门可通过实现这个接口来进行处理相关逻辑。

flow1

虽说到这里流程基本已经结束,但是在实际的业务逻辑中会衍生出一些特殊的逻辑,比如:

我要打开酒店列表,但是有个要求,必须是登陆状态才可直接进入,否则需要先去登陆。

可能仅仅从这一个需求上来看,用现在的流程去完成也不难,可以使用两个接收者去完成。但是当中间衍生的特殊逻辑很多而且可能多个业务都会需要,那用两个(多个)接收者可能就有会膨胀的很厉害了。那这个时候其实另外一个概念就产生了 -- 拦截器

拦截器

拦截器是当一个规则产生最终作用前进行的一些特殊而通用的逻辑处理。

处理逻辑其实很像View Touch事件,当一个Touch事件产生后,首先是分配(分发),然后是拦截,最后才是消费

而我们的拦截器稍有不同的地方是

  1. 拦截器可以多个
  2. 只有当所有拦截器全部满足(pass)的情况下才会走到执行器(消费)
  3. 这种拦截器模型可以通过递归来进行实现
flow-interceptor

拓展

对于Router框架设计到使用到现在已经2年多了,业务需求等也都基本可以完成、满足。也确实做到了Router框架抽离、流程确定且业务逻辑(包括映射关系)各自维护。当一个业务需求产生时,只需要产生一个新的规则来维护即可。

但是重新审视下目前的框架其实仍然有很多需要完善的:

  1. 数据映射是通过xml来映射,那么必然会带来I/O加载的性能开销
  2. 是否项目调用需要写一段很冗长的规则,比如 jump("tctclient://hotel/list"),而这种字符串参数通常是无法通过编译器去检查正确与否的

针对这两个问题,其实我们采用的是(插件)工具去完成。

Gradle Plugin + Freemarker

在编译时进行辅助类的建立

  • xml的加载可以在编译时直接生成Map关系维护。
  • 冗长规则则可以通过枚举来进行维护(枚举通过xml生成), 调用方式则改为jump(Bridge.HOTEL_LIST)

总结

该篇文章更多的写的是我在接受到这个需求时的心路历程,每个人在写代码的时候都有自己的感受。而从设计Router中其实能发现很多道理

没有一个模型放之四海而皆准的

比如一开始的 if-else ,当业务量很小的时候 , 这可能是一个非常好的处理方式。而当业务膨胀,当前结构不适用时需及时重构。

框架脱离了业务那就什么都不是,只有契合业务的框架,才是一个好框架。

思考比编码更重要

当接受到一个需求后,更多的是需要去思考,去设计。提炼出核心关键点、流程,针对流程梳理出核心生命周期非核心生命周期很重要

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • 你治愈了我 你是我的天使 你是我的生命之光 起初我 犹豫不决 我害怕又彷徨 最终我 尘埃落定 我合上我干涸已久的贝...
    半參不饱阅读 198评论 0 1
  • 一直想找个地方些东西,日记本太麻烦,博客不想用,空间太多人关注。所以找这么个清静地方,又不知道说些什么。那就从此开...
    查生阅读 226评论 1 1
  • 在我抱怨、吐槽、葛优瘫、懒的时候,总有人把生活过的活色生香。 虽然知道网络上的东西多少是经过美化的,但看到有些人的...
    橙子园丁阅读 141评论 0 0