背景
随着业务的发展,技术架构也在不断的演变升级,为了实现快速、稳定的响应上层业务的需求,各个业务点也在不停的组件化,京东零售的整体技术架构纵向使用分层解藕,横向进行多业务拆分,这样极大的调动整体技术架构的灵活性,也能加快业务的迭代。就拿京东商城来说,它的需要支持的端就包含PC、M站、App、微信手Q等业务线,每条业务线下面需要融合网关系统、搜索系统、支付系统、商品系统、广告系统、促销系统、订单系统、商家系统、供应链系统、物流系统等。大体架构我们可以用一张图来说明一下。
随着系统架构的不断升级增加、组织架构的调整人员替换交接,也出现了一些问题:
1) 中台很多系统都没有测试环境,导致很多团队负责的应用无法在本地(开发人员机器)运行,开发的功能无法及时得到测试,极大的影响了需求开发的效率;
2) 由于依赖系统不存在测试环境,测试团队在接到测试需求时候,无法在测试环境对测试需求进行测试,极大的影响测试人员的工作效率;因为依赖系统没有对应的测试环境,导致目前的开发、测试模式均是将代码部署至预发环境,功能变更到部署至预发耗时较 长(公司没有成熟的CI/CD流程),导致整个需求交付周期变长。同时,研发、测试共用一套预发环境,也导致多个需求并行的的时候存在冲突,进一步降低需求交付效率。
3) 需求的排期需要牵涉更多的系统,单个需求原来需要一个团队开发完成即可上线,现在需要多个多个团队各自开发,不同团队开发的系统之间相互依赖,开发效率各不相同,很多时候,我们都在等待依赖系统提供环境然后验证自己的系统功能是否正确,整个需求交付效率就更加低效。
综合目前面临的问题,我们需要找到一款产品能解决以下几个痛点:服务依赖缺失、应用无法启动、mock接入流程复杂、手动mock的操作繁琐。Jmock平台的产生由此而来,我们可以从下图中了解Jmock平台在架构的位置:
Jmock是如何解决以上痛点我们将在下面的章节中给大家详细讲解。
一步接入
01
client的SDK接入
为了实现应用接入流程复杂,JMock的接入仅仅需要引入Maven依赖即可,完全不需要任何其他的客户端配置。
<!-- JMock refer: http://test.jmock.jd.com --> <dependency> <groupId>com.jd.m.mocker</groupId> <artifactId>mocker-client</artifactId> <version>x.x.x-beta.x</version> </dependency>
对于需要mock的本地方法只需在方法上添加@JMock注解即可。
@JMock public String foo(String param) { return param; }
另外,SDK会自动校验应用环境是否符合mock条件,如果是生产环境机器,SDK会主动关闭开关,不会产生任何不必要的性能损耗及风险。
0 2
零配置
JMock遵循零配置原则,做到尽量减少研发的使用、学习成本,JMock的零配置主要体现在3个方面:
1) 基础数据零配置:Mock所用到的接入方应用中包括使用者、应用名称等基础数据会由SDK自动获取;
2) JSF调用零配置:JMock采用JSF自身提供的扩展点支持来实现JSF调用过程的零配置,mock过程在JSF的filter层完成;
3) 应用健康监控零配置:应用的启动过程的健康情况会随Spring的生命周期由SDK上报,并自动注册第一次接入的新应用
智能mock
01
网络隔离
为了方便大家快速使用,减少用户手动mock的繁琐操作,内置了智能化的mock开关逻辑。mock-client发现当前网络不属于开发环境、测试环境的时候,会自动关闭mock。此时对应用毫无影响。
0 2
mock-client维度的mock开关
SDK维度的mock开关用来控制全局,默认开启mock,不需要配置。当用户现式的将其关闭之后,应用的mock动作直接停止,配置方法为在应用的classpath目录下新建文件mocker.properties,并写入以下配置。
<span> <span># 关闭JMock</span></span>
<span> mocker.<span>on</span>=<span>false</span></span>
03
mock-server维度的开关
在mock-server中区分应用级别的mock开关和方法级别的开关。应用级别的mock开关一旦关闭,此应用下所有的方法均不再进程mock操作;方法维度的mock开关,顾名思义就是控制当个方法是否进行mock操作,一旦关闭,只会影响这个方法是否进行mock。如果要实现智能mock,就需要将应用mock开关和方法mock开发都是开启状态,默认开启状态,再配合mock数据源控制,这个在下一节中会说到。交互关系我们通过下面一张图来进行说明:
04
数据源智能控制
mock数据来源用来控制方法返回值生成策略,也是智能mock实现的关键之一。我先说明一下几个数据源:
1) 历史数据:也称为缓存数据,也就是当项目启动的时候,上一次拉取的线上数据,保存到缓存数据表中;
2) 自动生成:强制mock操作,通过返回值模版来生成mock数据,在应用级别和方法级别都有这个数据源的控制;
3) 智能选择:智能选择是在服务优先请求服务端接口,拉取数据,如果接口不存活,就会到缓存数据表中拉取数据,如果缓存数据表也不存在,那就直接使用mock生成返回值数据;
4) 后端依赖:强制请求服务端接口拉取返回值数据;
5) 同应用来源:这个是方法数据来源中的控制中出现,如果使用这个来控制数据来源的化,那就会同步到应用级别的数据来源控制;
总体而言,私有模版的数据源控制优先级最高,接着就是应用模板数据源控制,之后是公有模板数据源的控制,最后是应用级别的数据源控制,我们可以通过以下这张图来说明它们直接的逻辑。
05
mock开关和mock数据源的关系
Mock开关控制应用、方法是否进行Mock,一旦关闭,则执行方法原有逻辑。Mock开关开启的时候,才会按照Mock数据来源的规则进行处理。通过两者灵活设置来实现mock自动化生成,如下图展示:
数据自动生成
数据的自动生成不仅减少用户需要手动配置的流程,还可以为用户自动解析并mock出日益复杂的项目依赖的服务接口,实现对接口的自动生成需要解决两方面的问题,第一是对类的结构解析,第一个就是选择一个通用的占位符规范,生成标准的json模板,再解析json模板生成客户端需要的json字符串。如下下图所示,具体的实现方式我们将在下面的内容中介绍。
01
类结构解析
1)通过JavaTypeStructParser.parse来解析类的结构和每个字段的类型
解析结果示例如下:
{ "fields": [ { "name": "id", "struct": { "range": { "max": 9223372036854775807, "min": -9223372036854775808, "type": "RANGE" }, "structType": "NOR", "targetClassName": "java.lang.Long", "valueType": "NUM" }, "structType": "FIELD" }, { "name": "appName", "struct": { "structType": "NOR", "targetClassName": "java.lang.String", "valueType": "STR" }, "structType": "FIELD" } ], "structType": "OBJ", "targetClassName": "com.jd.jone.api.v2.beans.sysapp.AppWithSysInfo" }
2)再通过JtspTpl.parse(struct)根据每个字段类型,为字段生成对应的占位符
解析结果示例:
{ "id": "@natural", "appName": "@last", "@type": "com.jd.jone.api.v2.beans.sysapp.AppWithSysInfo" }
3)通过Mockingly.mock,生成mock数据替换占位符,生成JSON格式的返回值返回给SDK。返回值示例:
{ "id": 36322613, "appName": "White", "@type": "com.jd.jone.api.v2.beans.sysapp.AppWithSysInfo" }
4)SDK拿到JSON格式返回值反序列化生成Mock结果对象
02
占位符规范
占位符用于自动生成mock参数,目前有数值型、字符型、布尔型、日期型。占位符是借鉴MockJS部分占位符,不是所有的MockJS占位符都支持,占位符语法参考MockJS数据占位符定义规范。占位符 只是在属性值字符串中占个位置,并不出现在最终的属性值中。占位符 的格式为:
<span><span>@占位符</span></span>
<span><span>@占位符(参数[,参数])</span></span>
1)使用说明:
①用 @ 来标识其后的字符串是 占位符。比如:识别@first占位符,如果填成@firsttest,将识别为firsttest,需要使用@first()test才能识别。
②占位符不区分大小写。
③占位符 目前无法引用 数据模板 中的属性。
④占位符目前无法支持 相对路径 和 绝对路径。
⑤你可以同时使用多个占位符,比如例子中的‘name.full'属性值。
{ name: { first: '@FIRST', middle: '@FIRST', last: '@LAST', full: '@first @middle @last' } }// 填充后 => { "name": { "first": "Charles", "middle": "Brenda", "last": "Lopez", "full": "Charles Brenda Lopez" } }
2)部分占位符展示:
3) 占位符扩展
我们在现在已经支持的占位符基础上,可以进行增删改查,定期的通过接口使用的场景,会自动将一些占位符添加到到占位符库中。
服务依赖分析
服务依赖分析能协助研发清楚知道哪些接口服务是通过mock自动生成,哪些接口是存活拉取的线上数据。应用启动之后,从client端请求到mock-server端,传递参数和方法声明,在mock-server端我们会根据传递的数据调用jsf的服务。判断接口是否存活分为两部分,首先会根据方法声明在jsf注册中心验证是否注册,如果未注册,表明该方法声明不存活,如果已经注册,就会根据该jsf接口能否在1000ms内返回方法调用预期的数据即为可用,否则也会判定为不存活,并将接口声明的存活状态反馈给研发,协助研发快速定位依赖接口的问题。操作流程如下图:
未来展望
Jmock能帮助开发和测试快速启动本地应用,实现研发人员的降本提效,同时也提供了应用依赖分析,协助开发能快速的找到接口依赖的问题。目前Jmock平台已经完成了应用mock的数据智能选择、返回值模板的管理、应用依赖分析等,后续迭代我们将重点实现mock的参数化,全链路以及与测试用例的对接。
咨询邮箱: holdsupport@jd.com