DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师。
官方网站:devui.design
Ng组件库:ng-devui(欢迎Star)
引言
作为前端开发者,有些时候我们在后端服务还未ready的时候就接到了紧急开发需求,⾯对数据接⼝的缺失和数据持久化的⽀持,开发举步维艰。当然,加班也许也是⼀种解决问题的⽅法,但如果我们能够⾃⼰动动手指头去解决这两个问题,那么前端开发者们不仅增进了对业务的了解还掌握了对数据接⼝定义的主动权,后期的联调时间成本也可以⼤⼤缩⼩。
本文适合前端开发者阅读,阅读时长10分钟。
开始
⾸先,我选⽤Vue全家桶来做这个Mock App的讲解,因为代码少、效率高。前端做数据持久化需要一个存数据的地方,有的读者可能对localstorage和sessionstorage比较熟悉,但它们的缺点如下:
- 缺少对结构化数据存储的优化,存取都需要调用JSON.stringify和JSON.parse。
- 缺少对数据系统化的管理
- 缺少对数据查询的支持
由此可得localstorage和sessionstorage都不适合拿来解决我们的问题,⽽WebSQL已经寿终正寝,所以只有IndexedDB可选了。
INDEXEDDB是⼀个嵌⼊在浏览器中的事务数据库。该数据库的管理围绕JSON对象集合的概念,这类似NOSQL数据库MONGODB与COUCHDB。其中每个对象使⽤插⼊时⽣成的键标识。⽽索引系统优化对存储对象的访问。[Wikipedia](https://zh.wikipedia.org/wiki/Indexed_Database_API)
决定了使⽤IndexedD做数据持久化⽅案,我推荐使⽤Dexie.js对IndexedDB进⾏操作。实际上,如果没有Dexie.js这层封装,我也不会想在前端做数据mock。
所有的⼯具已经ready,我们将使⽤经典的message board来作为业务模型,做⼀个只关注数据层⾯的mock。
业务剖析
先上⼀张图, Message board的业务逻辑很简单,⽤户先创建Board(帖⼦),然后此⽤户或其它⽤户在Board(帖⼦)⾥创建Message(回复)。按照正常BBS的逻辑,⽤户未登录的时候也可以看到帖⼦,只是不能回复,这个特性也会在Mock App中体现。
由于IndexedDB和MongoDB很相似,所以我们可以直接将User直接保存在Message和Board的author中,⽽Message则使⽤parentId+parentType来建⽴和Board的关系。
Coding
定义数据库
使⽤Vue cli新建⼀个⼲净的项⽬,安装dexie,在src⽂件夹下新建⼀个db.ts⽂件,放⼊数据库的Schema
import Dexie from "dexie";
interface DBObject {
[key: string]: any;
}
const db: DBObject = new Dexie("myDb");
db.version(1).stores({
users: `++id, name`,
boards: `++id, topic, description, author`,
messages: `++id, content, author, createdAt, parentId, parentType`
})
这⾥传⼊stores⽅法的object就是数据库的schema了。 keys代表了数据库的表, value中是以逗号分隔的columns。⼀个⽐较特殊的是++id,它的意思是⾃增整形,并且它是作为表的主键存在的。其他的columns和业务剖析中的图⼀致,就不展开了。
定义Mock API
我们只实现最⼩可⽤的Mock App,只需实现以下⼏个API:
- createUser: 创建⽤户,⽤以登录应⽤
- getUsers: 获取⽤户列表,⽅便我们切换⽤户
- createDiscussion: 创建Board,类似于创建⼀个帖⼦的概念
- getDiscussions: 获取Board列表,类似于获取帖⼦列表的概念
- getDisucssionMessage: 获取Board下的Message列表,类似于获取⼀个帖⼦下⾯所有回复的概念
- createDiscussionMessage: 创建Board下的Message,类似于在帖⼦下创建回复的回复的概念
在本⽂最后给出的项⽬代码中有它们的具体实现。这⾥只对getUsers、 createUser和getDiscussionMessage⽅法做讲解:
api.prototype.getUsers = function() {
return db.transaction("r", db.users, function() {
return db.users.toArray();
});
};
这个⽅法返回了⼀个Promise, db.transaction中第⼀个参数是”r”,熟悉linux权限系统的同学肯定知道了,这是read权限的意思,因为这个getUsers⽅法涉及到读数据库操作,所以这个transaction需要read access。 第⼆个传⼊的参数是db.users,它声明了transaction将建⽴和Users table的连接,⽽function中return的db.users.toArray()则返回了Users table中所有的数据。
api.prototype.createUser = function(name: string) {
return db.transaction("rw", db.users, function() {
return db.users.add({ name: name });
});
};
这个⽅法同样返回了⼀个Promise, db.transaction中第⼀个参数是”rw”,也就是read & write的意思,因为这个createUser⽅法涉及到读数据库操作,所以它需要write access。 db.users.add({name:name})则新建了⼀⾏⽤户数据,⽤户的id被⾃动补全。
api.prototype.getDisucssionMessage = function(discussionId:number) {
return db.transaction("r", db.messages, function() {
return db.messages.where({
parentId: discussionId,
parentType: "discussion"
}).toArray();
});
};
这个⽅法也返回了⼀个Promise, function中的db.message.where⽅法有点SQL的味道,它的作⽤你也猜到了,就是通过提供的过滤器(object)在Messages table中查询数据,然后返回所有符合过滤器筛选的结果。
定义Vuex Store
我们会⽤Vuex actions把API管理起来,在Angular中也可以⽤Service达到同样的效果,管理起来的⽬的是当后端API开发完成的时候,我们可以很⽅便地迁移到新的API上,⽽不需要⼤量地变更已经写好的业务代码。
我们在Vuex的actions中建⽴和上⽂Mock API中相对应的⽅法,同时为了⽅便获取到⽤户登录的状态,我们可以在getters中加⼊loginStatus,因为我们的多个⻚⾯需要判断⽤户是否登录,只有登录的⽤户才可以发⾔,未登录的⽤户只能查看讨论。
完成业务
Mock API和Vuex Store都写好之后我们可以开始着手实现我们的业务逻辑。由于本身的业务实在太简单,我直接放源码了,这⾥不做展开。
假设我们完成了Mock App的编写,如何利用已完成的代码,尽量少地改动业务逻辑来适配后端的API呢?在Vuex(或服务)中抽象出来的API就功不可没了,理想情况下我们只需要改变引入的API源(Angular服务注入的时候可以用UseClass)就能做到切换API的工作,因为我们的业务逻辑和API用什么技术方案实现的完全没关系!
事实上,基于这样的流程编写的App也能降低Code Smell,促进应用与API的解耦,为更健壮、更具拓展性、更具可测性的Code Base打好基础。
总结
IndexedDB赋予了开发者们即使没有后端的时候仍可以继续前端开发的能⼒,使⽤Dexie的API去管理IndexDB⽆疑减⼩了IndexedDB的使⽤⻔槛。同时,使⽤Vuex、 Angular这类具有服务注⼊能⼒的库/框架可以有效降低API源切换的时间成本和⻛险。
在前端给出需要的数据接⼝格式后,后端只需按要求提供相应数据即可。如果有扩展需求,只需在前端给出的接⼝格式上持续演进,⽽不⽤为了对⻬接⼝格式⽽扯⽪。
在业务逻辑与API实现解耦之后,前端开发者可以有更多的时间去思考⽤户体验、编写单元测试,从⽽提⾼整个应⽤的鲁棒性、可⽤性和易⽤性。
最后需要强调的是,此⽂并不是说后端没⽤,因为后端提供的⼀些能⼒仍是前端远远不及的。本⽂的观点是,在前端做Mock App的这段时间内,后端可以有更充⾜的时间、空间去思考后端架构和实现、以更稳定的状态承载更⼤的总业务容量,从⽽为产品、公司和客户带来价值。
以下是项目源码:
https://github.com/AnonymousArthur/expedited-mock
加入我们
我们是DevUI团队,欢迎来这里和我们一起打造优雅高效的人机设计/研发体系。招聘邮箱:muyang2@huawei.com。
文/DevUI Arthur
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。