BFF端Mock方案
一、 总览
Godzilla BFF 也提供了 mock 数据服务,那为什么在 UI 端已经提供了 mock 数据的情况下,BFF 还需要提供,它们的共同点与不差异点如下:
相同
- 都能在开发阶段,模拟后端接口数据
- 都能在开发阶段,随时禁用 mock 服务
- 使用都比较灵活
差异
- UI-Mock 仅支持本地开发模式(Development),正式环境(Production)不支持,而 BFF-Mock 在正式环境下还有效(除非手动禁用调)
- UI-Mock 写法比较灵活,只要按照文件名约定,可以随意放在任意目录下,在不需要 BFF-Mock 的情况下,首选 UI-Mock 方案
- BFF-Mock 在正式环境也是有效的,如果项目初期,没有对接后端服务,又想在正式环境可以模拟一些数据,则可以考虑使用该方案
本手册提供了大量实例供用户参考,并对内部实现原理进行一定的阐述,方便排错。
二、 使用 bff 的 mock 功能
bff 里约定 src/app/mock 文件夹下的.js 即 mock 文件,文件名随便命名,框架都能识别
当客户端(浏览器)发送请求,如:GET auth/api/v2/users/1
,那么本地启动的 npm run dev
会跟此配置文件匹配请求路径以及方法,如果匹配到了,就会将请求通过配置处理,就可以像样例一样,你可以直接返回数据,也可以通过函数处理以及重定向到另一个服务器。
比如定义如下映射规则:
/**
* 工具对象提供了一系列添加mock配置的方法,现在我们来添加一个mock配置
**/
module.exports = mockUtil => {
const { get } = mockUtil.prefixSite('auth');
get('/api/v2/users/:id', {
id: 2,
name: 'admin'
});
};
在浏览器中打开http://127.0.0.1:8889/mock,端口号视你的 proxy 设定而定,你将看到:
可以看到我们刚刚的配置以及加进 mock 配置中,其中 data template 是我们写的模板配置,Result 是我们实际请求获取到的结果。默认带上了我们前后端的约定的返回格式,这样我们再写 mock 配置的时候就只需要写 data 中的内容了。新增的配置,默认是开启状态的(即使用 mock 数据),关闭后向真实的后端服务发起请求。
配置都完成了,我们来尝试一下:
在登录界面点击登录或者使用 POSTMAN 工具请求 http://127.0.0.1:8889/auth/api/v2/users/login 进行测试,这里我们再登录界面进行登录尝试:
可以看到,我们的请求返回的已经是我们自己的 mock 的数据结构。
那么这个过程到底发生了什么?
BFF 中间件 会监测 app/middleware/mock 目录下的所有文件的变动,在初始化和文件发生变动的时候,BFF 中间件 会自动 reload,将所有 mock 配置重新加载,处于开启状态的 mock 配置,请求相应接口将返回用户配置的 mock 数据。
三、 mockUtil API
get/delete/post/put
mockUtil 提供了四个基础 mock 配置接口,它们接收三个参数site、url、mockData;分别是服务名、服务地址、配置的 mockData 模板,且所有方法都是已经柯里化。
e.g. 1.模拟普通的 get 请求
/** 模拟请求qtw服务下的/api/v2/ibbond接口 **/
mockUtil.get('qtw', '/api/v2/ibbond', {
ibbond: 'your mock test'
})
/** 在界面请求你将获得如下数据 **/
get /api/v2/ibbond
response {
"code": 200,
"codeDetail": null,
"data": {
"ibbond": "your mock test"
},
"moreInfo": null,
"msg": "mock server",
"msgDetail": "data created by mock server",
"requestId": "0.9346972432594425"
}
e.g. 2.带参数的 URL
/** 模拟请求qtw服务下的/api/v2/ibbond/{id}接口 **/
mockUtil.get('qtw', '/api/v2/ibbond/:id', {
ibbond: 'your mock test'
})
/** 在界面请求你将获得如下数据 **/
get /api/v2/ibbond/5
response {
"code": 200,
"codeDetail": null,
"data": {
"ibbond": "your mock test"
},
"moreInfo": null,
"msg": "mock server",
"msgDetail": "data created by mock server",
"requestId": "0.9346972432594425"
}
e.g. 3.预设请求参数
/** 由于所有基础配置方法都已经curry化,所以我们可以预设参数 **/
const qtwGet = mockUtil.get('qtw');
/** 实际mock请求将会是/qtw/api/v2/ibbond **/
qtwGet('/api/v2/ibbond', {
ibbond: 'curry'
});
疑问点:我想拥有像是 new API('qtw')的效果如何实现?请参考 prefixSite 章节。
e.g. 4.使用 mock 语法
/** 模拟动态数据 **/
mockUtil.get('qtw', '/api/v2/ibbond', {
'list|5': [ 'data' ]
})
/** 在界面请求你将获得如下数据 **/
get /api/v2/ibbond
response {
"code": 200,
"codeDetail": null,
"data": {
"list": ["data", "data", "data", "data", "data"]
},
"moreInfo": null,
"msg": "mock server",
"msgDetail": "data created by mock server",
"requestId": "0.9346972432594425"
}
更多 mock 语法参考:mockJS
request
疑问:我不想返回带有固定模板的 response,想要完全自定义模板,如何实现?
答案是使用 request,request 接受四个参数site,method,url,data;分别代表服务名,请求方法,请求地址,自定义模板,同样 request 也是支持 curry 的。
e.g. 1. 完全自定义返回格式
mockUtil.request('qtw', 'get', '/api/v2/ibbond', {
code: 200,
msg: 'msg write by xxx',
data: { list: [] }
})
/** 在界面请求你将获得如下数据 **/
get /api/v2/ibbond
response {
code: 200,
msg: 'msg write by xxx',
data: { list: [] }
}
可以看到 request 设置的 mock 模板,是不会带上我们的默认模板的。
prefixSite
prefixSite 如同字面意思就是为了预设服务名称的,该接口接受一个服务名作为参数,返回预设了服务名的 get/post/put/delete/request 方法。
e.g. 1.使用 prefixSite 预设服务名
const qtw = mockUtil.prefixSite('qtw');
const auth = mockUtil.prefixSite('auth');
qtw.get('/api/v2/ibbond', {
ibbond: 'qtw service'
})
auth.get('/api/v2/ibbond', {
ibbond: 'auth service'
})
/** 在界面请求你将获得如下数据 **/
get /qtw/api/v2/ibbond
response {
/** 这里省略部分默认返回字段 **/
data: { ibbond: 'qtw service' }
}
get /auth/api/v2/ibbond
response {
/** 这里省略部分默认返回字段 **/
data: { ibbond: 'auth service' }
}
四、 进阶用法
内在原理
mock server 是基于 koa 中间件 进行搭建的,所有用户的配置,在初始化的时候会注册到 koa-router 的路由上,检测到用户配置变更的时候会重启应用(这个时间通常很快)以加载新的配置。
当 ui 界面发起请求的时候,mock server 会先检测是否存在相应在启用状态下的路由配置,如果存在则启用相应的路由进行处理,在对应的路由中会调用 mockJS 这个库的接口对用户配置的 mock template 进行 mock 操作,得到响应结果,并返回给 UI 界面,如果没有检测到可以处理该接口的路由,则将接口转发至真实的后端服务。
由于上述操作是以 koa 中间件的形式组装起来的,所以给我们带来更加灵活的 mock 操作。
对于 get/post/delete/put/request 这几种基础添加配置的方法,mockData 参数还支持传函数的形式。
使用函数作为 mockData
e.g.1. 从第三方异步源获取数据
const qtw = mockUtil.prefixSite('qtw');
/**
* 传入的函数接受两个参数ctx和next,ctx为单次请求的上下文,next为一个函数,
* 调用后可以执行下一个中间件,具体文档请参考koa文档。
**/
qtw.get('/api/v2/ibbond', async (ctx, next) => {
const data = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
code: 200,
asyncData: true
})
}, 500)
});
ctx.state.disabledProxy = true; // 禁用代理服务,否则请求将转发到真实的后端服务
ctx.response.body = data;
next();
})
/** 在界面请求你将获得如下数据 **/
get /qtw/api/v2/ibbond
response {
code: 200,
asyncData: true
}
e.g.2. 对真实后端返回的数据进行筛选
const qtw = mockUtil.prefixSite('qtw');
qtw.get('/api/v2/ibbond', async (ctx, next) => {
await next();
const data = ctx.response.body; // 真实的后端返回的数据
// 对后端返回的列表进行筛选,只要list item中status为3的项
data.list = data.list.filter(item => item.status === 3);
ctx.response.body = data;
});