前言
我们这季度针对开发态出现的问题进行优化,开发的时候我们都遇到哪些比较棘手的问题呢?
- 联调异常的时候,代码排查比较麻烦,部署时间比较长
- 前端需要等待后端开发完成,然后部署后才能进行调试数据,依赖性比较强
那么今天我们讲讲联调中心怎么建设的
组成
有两块组成,一块是流量负载到本地,可以参考 啃老本-开发环境多泳道建设,这个解决第一个问题就是调试上效率比较低的问题,另一块是mock service,我们重点来讲讲第二块。
mock service
它分为前端、后端mock,前端mock为了减少依赖后端服务,提高开发阶段的效率,后端mock也是为了减少其他中间件或者服务依赖。
怎么联动
这个是很重要的问题,如果是单纯看某一端的mock是很方便的,但是整体来看是需要去琢磨的。
比如说后端的接口文档统一在哪里管理,可能某个新人想调一下商品接口查某个sku,他又不认识人,这个接口文档的统一就至关重要。
还有就是后端导出接口文档之后,怎么让前端去mock数据?这就是所谓的联动效果。
实战场景
我们以yapi产品来实践,后端在写完外部请求接口的时候,导出到yapi文档,这时后端同学继续开发里面的功能,然后前端同学根据yapi的高级mock调试里面数据,跟前端内容调试。
这里我们就实现了接口文档统一管理,然后也实现了前后端mock功能,达到前端联调对后端服务依赖减少的效果。
mock代码误提交
我有个单测群,里面腾讯开源的APIJSON框架负责人提到了这一点,就是当后端在做mock的时候,很容易将调试代码误提交。
这也是我们做架构这些基础功能的时候需要考虑的风险点。
说下我的思路:就是实现一个公共后端mock功能,如果本地打开调试开关,或者说环境是调试环境,自动我们拦截rpc、mq自动将数据mock回去,至于返回的数据格式,可以有个地方让你配置,比如说本地搞个文件,你不要提交到远程即可,即使提交了,默认调试开关关闭,也不会有影响,这样我们就解决了误提交的问题。
当然了对于大厂也有其他的问题,比如
这就是为什么很多中间件需要做兼容。
值得注意不是mock怎么搞,而是里面细节需要我们思考。
mock实现原理
Mock是一种在开发过程中模拟数据和行为的技术。其实现原理是通过创建虚拟接口来模拟真实的接口请求和响应,通过预先定义返回结果的数据结构和内容,从而实现在不依赖实际数据源的情况下进行开发和测试。
在具体实现中,可以使用一些工具或框架,比如json-server、YApi等,通过自定义路由、数据模板等方式实现Mock数据的生成和返回。Mock的实现可以帮助开发人员在开发过程中快速迭代和测试,提高开发效率。
后端mock实现原理是通过拦截后端接口的请求,并返回预设的模拟数据,以达到模拟接口数据的目的,从而在开发过程中减少对真实接口的依赖性,提高开发效率。常用的后端mock工具如MockServer、WireMock等,它们可以通过配置预设的规则和响应数据来实现后端接口的模拟。
后端mock框架 TestableMock
原理
通过asm技术改写字节码,将需要改的类、方法重写,我们在调用对应mock类的mock方法的时候,会调到我们改写的方法,从而实现mock功能。
具体源码
SourceClassHandler
com.alibaba.testable.agent.handler.SourceClassHandler#transformMethod
private void transformMethod(MethodNode mn, Set<MethodInfo> memberInjectMethods,
Set<MethodInfo> newOperatorInjectMethods) {
LogUtil.verbose(" Found method %s", mn.name);
if (mn.name.startsWith(DOLLAR)) {
// skip methods e.g. "$jacocoInit"
return;
}
AbstractInsnNode[] instructions = mn.instructions.toArray();
if (instructions.length == 0) {
// native method (issue-52)
return;
}
int i = 0;
do {
if (invokeOps.contains(instructions[i].getOpcode())) {
MethodInsnNode node = (MethodInsnNode) instructions[i];
if (CONSTRUCTOR.equals(node.name)) {
if (LogUtil.isVerboseEnabled()) {
LogUtil.verbose(" Line %d, constructing "%s"", getLineNum(instructions, i),
MethodUtil.toJavaMethodDesc(node.owner, node.desc));
}
MethodInfo newOperatorInjectMethod = getNewOperatorInjectMethod(newOperatorInjectMethods, node);
if (newOperatorInjectMethod != null) {
// it's a new operation and an inject method for it exist
int rangeStart = getConstructorStart(instructions, node.owner, i);
if (rangeStart >= 0) {
if (rangeStart < i) {
handleFrameStackChange(mn, newOperatorInjectMethod, rangeStart, i);
}
instructions = replaceNewOps(mn, newOperatorInjectMethod, instructions, rangeStart, i);
i = rangeStart;
}
}
} else {
if (LogUtil.isVerboseEnabled()) {
LogUtil.verbose(" Line %d, invoking "%s"", getLineNum(instructions, i),
MethodUtil.toJavaMethodDesc(node.owner, node.name, node.desc));
}
MethodInfo mockMethod = getMemberInjectMethodName(memberInjectMethods, node);
if (mockMethod != null) {
// it's a member or static method and an inject method for it exist
int rangeStart = getMemberMethodStart(instructions, i);
if (rangeStart >= 0) {
if (rangeStart < i) {
handleFrameStackChange(mn, mockMethod, rangeStart, i);
}
instructions = replaceMemberCallOps(mn, mockMethod,
instructions, node.owner, node.getOpcode(), rangeStart, i);
i = rangeStart;
} else {
LogUtil.warn("Potential missed mocking at %s:%s", mn.name, getLineNum(instructions, i));
}
}
}
}
i++;
} while (i < instructions.length);
}
将新方法替换到旧的方法,当然这里我们就不解读了,读完asm技术可能博主的头发又要掉几根了,哈哈~
怎么避免mock代码提交到远程
目前我公司基本没有写单测,所以存在直接在代码里面mock数据,我记得阿里之前也有要求说写单测,但是实际上业务团队在追求效率情况下,也会采用类似的粗暴的直接mock,我的想法希望能够兼容这种场景。
其实mock不止会写在单元测试,还可能会出现在正式代码,由于业务团队追求效率导致,testable-mock采用agent方式去改写,所以生产环境没有挂载的话不会影响生产环境。
总结
在联调阶段,我们遇到了一些比较棘手的问题:代码排查比较麻烦,部署时间比较长,前端需要等待后端开发完成才能进行调试数据,依赖性比较强等等。
为了解决这些问题,我们建设了联调中心,其中重点介绍了mock service。我们使用yapi实践了这一技术,并且实现了接口文档统一管理和前后端mock功能,达到了前端联调对后端服务依赖减少的效果。同时,我们也讨论了mock代码误提交的问题,提出了拦截rpc、mq自动将数据mock回去的解决方案。这些优化对于提升开发效率、减少依赖性以及保证代码质量都有很大的帮助。