98 | 项目实战三:设计实现一个支持自定义规则的灰度发布组件(实现)
下载APP
关闭
渠道合作
推荐作者
98 | 项目实战三:设计实现一个支持自定义规则的灰度发布组件(实现)
2020-06-17 王争 来自北京
《设计模式之美》
课程介绍
讲述:冯永吉
时长08:43大小7.98M
上两节课,我们讲解了灰度组件的需求和设计思路。不管是之前讲过的限流、幂等框架,还是现在正在讲的灰度组件,这些框架、组件、类库的功能性需求都不复杂,相反,非功能性需求是开发的重点、难点。
今天,我们按照上节课给出的灰度组件的设计思路,讲解如何进行编码实现。不过今天对实现的讲解,跟前面两个实战项目有所不同。在前面两个项目中,我都是手把手地从最基础的 MVP 代码讲起,然后讲解如何 review 代码发现问题、重构代码解决问题,最终得到一份还算高质量的代码。考虑到已经有前面两个项目的学习和锻炼了,你应该对开发套路、思考路径很熟悉了,所以,今天我们换个讲法,就不从最基础的讲起了,而是重点讲解实现思路。
话不多说,让我们正式开始今天的学习吧!
灰度组件功能需求整理
针对上两节课给出的开发需求和设计思路,我们还是按照老套路,从中剥离出 V1 版本要实现的内容。为了方便我讲解和你查看,我把灰度组件的开发需求和设计思路,重新整理罗列了一下,放到了这里。
1. 灰度规则的格式和存储方式
我们希望支持不同格式(JSON、YAML、XML 等)、不同存储方式(本地配置文件、Redis、Zookeeper、或者自研配置中心等)的灰度规则配置方式。实际上,这一点跟之前的限流框架中限流规则的格式和存储方式完全一致,代码实现也是相同的,所以在接下来的讲解中,就不重复啰嗦了,你可以回过头去看下第 92 讲。
2. 灰度规则的语法格式
我们支持三种灰度规则语法格式:具体值(比如 893)、区间值(比如 1020-1120)、比例值(比如 %30)。除此之外,对于更加复杂的灰度规则,比如只对 30 天内购买过某某商品并且退货次数少于 10 次的用户进行灰度,我们通过编程的方式来实现。
3. 灰度规则的内存组织方式
类似于限流框架中的限流规则,我们需要把灰度规则组织成支持快速查找的数据结构,能够快速判定某个灰度对象(darkTarget,比如用户 ID),是否落在灰度规则设定的范围内。
4. 灰度规则热更新
修改了灰度规则之后,我们希望不重新部署和重启系统,新的灰度规则就能生效,所以,我们需要支持灰度规则热更新。
在 V1 版本中,对于第一点灰度规则的格式和存储方式,我们只支持 YAML 格式本地文件的配置存储方式。对于剩下的三点,我们都要进行实现。考虑到 V1 版本要实现的内容比较多,我们分两步来实现代码,第一步先将大的流程、框架搭建好,第二步再进一步添加、丰富、优化功能。
实现灰度组件基本功能
在第一步中,我们先实现基于 YAML 格式的本地文件的灰度规则配置方式,以及灰度规则热更新,并且只支持三种基本的灰度规则语法格式。基于编程实现灰度规则的方式,我们留在第二步实现。
我们先把这个基本功能的开发需求,用代码实现出来。它的目录结构及其 Demo 示例如下所示。代码非常简单,只包含 4 个类。接下来,我们针对每个类再详细讲解一下。
从 Demo 代码中,我们可以看出,对于业务系统来说,灰度组件的两个直接使用的类是 DarkLaunch 类和 DarkFeature 类。
我们先来看 DarkLaunch 类。这个类是灰度组件的最顶层入口类。它用来组装其他类对象,串联整个操作流程,提供外部调用的接口。
DarkLaunch 类先读取灰度规则配置文件,映射为内存中的 Java 对象(DarkRuleConfig),然后再将这个中间结构,构建成一个支持快速查询的数据结构(DarkRule)。除此之外,它还负责定期更新灰度规则,也就是前面提到的灰度规则热更新。
为了避免更新规则和查询规则的并发执行冲突,在更新灰度规则的时候,我们并非直接操作老的 DarkRule,而是先创建一个新的 DarkRule,然后等新的 DarkRule 都构建好之后,再“瞬间”赋值给老的 DarkRule。你可以结合着下面的代码一块看下。
我们再来看下 DarkRuleConfig 类。这个类功能非常简单,只是用来将灰度规则映射到内存中。具体的代码如下所示:
从代码中,我们可以看出来,DarkRuleConfig 类嵌套了一个内部类 DarkFeatureConfig。这两个类跟配置文件的两层嵌套结构完全对应。我把对应关系标注在了下面的示例中,你可以对照着代码看下。
我们再来看下 DarkRule。DarkRule 包含所有要灰度的业务功能的灰度规则。它用来支持根据业务功能标识(feature key),快速查询灰度规则(DarkFeature)。代码也比较简单,具体如下所示:
我们最后来看下 DarkFeature 类。DarkFeature 类表示每个要灰度的业务功能的灰度规则。DarkFeature 将配置文件中灰度规则,解析成一定的结构(比如 RangeSet),方便快速判定某个灰度对象是否落在灰度规则范围内。具体的代码如下所示:
添加、优化灰度组件功能
在第一步中,我们完成了灰度组件的基本功能。在第二步中,我们再实现基于编程的灰度规则配置方式,用来支持更加复杂、更加灵活的灰度规则。
我们需要对于第一步实现的代码,进行一些改造。改造之后的代码目录结构如下所示。其中,DarkFeature、DarkRuleConfig 的基本代码不变,新增了 IDarkFeature 接口,DarkLaunch、DarkRule 的代码有所改动,用来支持编程实现灰度规则。
我们先来看下 IDarkFeature 接口,它用来抽象从配置文件中得到的灰度规则,以及编程实现的灰度规则。具体代码如下所示:
基于这个抽象接口,业务系统可以自己编程实现复杂的灰度规则,然后添加到 DarkRule 中。为了避免配置文件中的灰度规则热更新时,覆盖掉编程实现的灰度规则,在 DarkRule 中,我们对从配置文件中加载的灰度规则和编程实现的灰度规则分开存储。按照这个设计思路,我们对 DarkRule 类进行重构。重构之后的代码如下所示:
因为 DarkRule 代码有所修改,对应地,DarkLaunch 的代码也需要做少许改动,主要有一处修改和一处新增代码,具体如下所示,我在代码中都做了注释,就不再重复解释了。
灰度组件的代码实现就讲完了。我们再通过一个 Demo 来看下,目前实现的灰度组件该如何使用。结合着 Demo,再去理解上面的代码,会更容易些。Demo 代码如下所示:
重点回顾
好了,今天的内容到此就讲完了。我们一块来总结回顾一下,你需要重点掌握的内容。
到今天为止,项目实战环节就彻底结束了。在这一部分中,我们通过限流、幂等、灰度这三个实战项目,带你从需求分析、系统设计、代码实现这三个环节,学习了如何进行功能性、非功能性需求分析,如何通过合理的设计,完成功能性需求的同时,满足非功能性需求,以及如何编写高质量的代码实现。
实际上,项目本身的分析、设计、实现并不重要,不必对细节过于纠结。我希望通过这三个例子,分享我的思考路径、开发套路,让你借鉴并举一反三地应用到你平时的项目开发中。我觉得这才是最有价值的,才是你学习的重点。
如果你学完这一部分之后,对于项目中的一些通用的功能,能够开始下意识地主动思考代码复用的问题,考虑如何抽象成框架、类库、组件,并且对于如何开发,也不再觉得无从下手,而是觉得有章可循,那我觉得你就学到了这一部分的精髓。
课堂讨论
在 DarkFeature 类中,灰度规则的解析代码设计的不够优雅,你觉得问题在哪里呢?又该如何重构呢?
欢迎留言和我分享你的想法。如果有收获,也欢迎你把这篇文章分享给你的朋友。
分享给需要的人,Ta购买本课程,你将得29元
生成海报并分享
赞 23
提建议
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
上一篇
97 | 项目实战三:设计实现一个支持自定义规则的灰度发布组件(设计)
下一篇
99 | 总结回顾:在实际软件开发中常用的设计思想、原则和模式
精选留言(27)
- 小晏子2020-06-17这个DarkFeature类中灰度规则的解析代码不优雅的地方在于不够灵活,如果有新的灰度规则要加入,就需要再添加if else作处理,破坏了开闭原则,为了解决这一问题,可以使用工厂模式➕策略模式来保证开闭原则和消除if/else,使用工厂模式来实现针对每个灰度规则的处理,使用“查表法”的策略模式来消除if/else!共 1 条评论41
- Lee2020-06-17可以使用解释器模式,将不同类型的规则解析拆分到不同的类中。共 1 条评论18
- tingye2020-06-17可以考虑用职责链模式,将不同规则字符串的解析抽象为单独的handle类,依次解析直到完成处理,也方便扩展对新规则编写语法的解析15
- Jagger2020-08-07DarkLaunch 构造器包含定时轮询,会不会影响单元测试?
作者回复: 嗯嗯,会,最好是再声明一个构造函数,传递executor进去
12 - robincoin2020-06-17mq和数据库灰度是不是要对mq和数据库再封装一层,方便aop处理?
作者回复: 支持! 封装来进一步简化开发!
共 2 条评论6 - Heaven2020-06-17在此类场景下,我们可以简单的使用工厂类去封装规则的解析, 但是我个人觉着,应该以配置文件中配置的规则为主,所以,第二版需要在配置文件中写上实现接口的全限定类名,反射获取实例,同样支持更新,这样配置文件的Map就可以移除了,而且可以将简单的原生三种解析规则也抽象为接口,利用策略类进行区分调用5
- 强哥2020-06-21这个灰度组件感觉适用简单场景,规则之间的表达式、优先级等组合方式不支持,规则的定义很重要。热更新可以通过zk下放到服务器上,通过sdk将配置信息加载到内存中。4
- djfhchdh2020-09-09DarkFeature类扩展性、灵活性都较弱,如果feature配置新增了字段,那么就要修改类内部的解析代码,不满足开闭原则。可以把parse函数单独拿出来,抽象成一个Parser接口,针对不同的feature配置格式,实现不同的parser。这样的话,对于配置格式的改动,只要修改或扩展相应的parser类就行了,不用改动DarkFeature类的代码,达到了对于修改的隔离。3
- Jxin2020-06-171.定时任务在方法内部创建和使用,这样就没办法手动调定时任务的退出方法了。 2.感觉业务接口的路由规则的选型和路由规则的具体实现应该分离。DarkFear里面应该只要表明,哪个业务接口用哪个灰度规则,这个意图就好。至于灰度规则的具体实现,包括dsl的解析和灰度规则的执行都应该剥离出来单独封装。3
- gogo2020-06-17可以考虑引入策略模式和工厂模式,消除if else3
- 罗 乾 林2020-06-17“DarkFeature 类中,灰度规则的解析代码“,是我能想到最直接简单的方式。我想可以抽象出规则解析类,对规则的解析交个解析类处理,将解析类对象注入到DarkFeature 类中。这样DarkFeature职责更单一2
- test2020-06-17parsedarkrule的代码可以单独出来2
- 果果果2021-11-20感谢王争老师,看完这本书后的最大收获是,自己动手完成了一个功能更为丰富的灰度组件,支持动态数据源、以及数据源的扩展、支持规则执行结果跟踪。 github地址 https://github.com/TangGuoGuoR/ab-gray1
- Yeyw2021-05-01规则扩展复杂的话可以用解释器,但是加载配置的话 我觉得定时会重复加载,定时加载加个判断吧,也可以暴露重新加载配置的方法,供开发提供自己自主刷新的接口1
- Leaf2021-01-16如果要集成到 spring 框架中,有些地方的设计和实现是否需要调整?比如: 1. ruleUpdateInterval 是要放到配置中,这样便于依赖注入? 2. loadRules 是否使用 spring 的 configuration 来替代? 3. addProgrammedDarkFeature 是否需要做成配置的方式?1
- 忆水寒2020-09-02震撼,其实思考方式就是最大的收获,这是作者多年的经验。1
- Tobias2020-08-04问:在 DarkFeature 类中,灰度规则的解析代码设计的不够优雅,你觉得问题在哪里呢?又该如何重构呢? 答:首先,parseDarkRule方法可读性和可测试性不好:方法代码偏多,if-else比较多。其次,可扩展性不好,如果将来又新增一种规则格式,需要在parseDarkRule插入代码解析规则,违背开闭原则。 解决方法:将parseDarkRule规则解析继续拆分成更小的类,每个类对不同的规则定义进行解析(e.g. 范围规则,百分比规则 etc.) 。相信解析器模式可以很好的解决这个问题。展开1
- 汝林外史2020-07-15好像dark方法中没有对区间的规则进行处理
作者回复: 有呀,你说没有是指的哪部分?能详细说说吗
共 2 条评论1 - 公号-技术夜未眠2020-07-04利用spi机制+热加载实现编程规则会更加优雅写1
- leezer2020-06-17解析规则可以参考之前,使用工厂模式回去解析器,通过对应的解析器进行解析对应的配置文件.1