如何构建通用存储中间层

5次阅读

共计 4459 个字符,预计需要花费 12 分钟才能阅读完成。

零、问题的由来
开门见山地说,这篇文章【又】是一篇安利软文~,安利的对象就是 tua-storage。
顾名思义,这就是一款存储数据的工具。
用 tua-storage 好处大大的有么?
那必须滴~,下面开始我的表演~

多端统一 api
支持数据同步
数据过期逻辑
自动清理过期数据
支持永久保存
支持批量操作

一、多端统一 api
日常开发中,在不同的平台下由于有不同的存储层接口,所以往往导致相同逻辑的同一份代码要写几份儿。
例如,小程序中保存数据要使用【异步】的 wx.setStorage、wx.getStorage 或对应的同步方法;
而在 web 端使用 localStorage 的话,则是【同步】的 setItem、getItem 等方法;
在 React-Native 的场景下,使用的又是 AsyncStorage 中【异步】的 setItem、getItem…
1.1. 异步方法
然而,经过 tua-storage 的二次封装,以上两个方法统一变成了:

save: 异步保存

load: 异步读取

此外还有一些其他方法:

clear: 异步清除(删除多个)

remove: 异步删除(删除单个)

getInfo: 异步获取信息(如 keys)

详情参阅这里的文档
1.2. 同步方法
在某些场景下正好需要调用同步方法的话,咋办咧?
与 Node.js 的 api 风格差不多,在上述异步方法后面加上 Sync 就是对应的同步方法:

saveSync
loadSync
clearSync
removeSync
getInfoSync

那么在 AsyncStorage 的场景下,压根就没有同步方法时调用以上方法会怎么样呢?
嗯,你猜得没错,会直接报错 …
1.3. 区分场景
如何区分不同的场景呢?
在初始化的时候传递 storageEngine 即可:
import TuaStorage from ‘tua-storage’

const tuaStorage = new TuaStorage({
// 小程序
storageEngine: wx,

// web
storageEngine: localStorage,

// React-Native
storageEngine: AsyncStorage,

// Node.js
storageEngine: {},
})
注意:传递的是【对象】,而非字符串!
二、支持数据同步
对于一个二次封装多端存储层的库来说,保证多端 api 的统一仅仅是常规操作而已。
tua-storage 的另一大亮点就是数据同步功能。
想想平时我们是怎么使用存储层的

读取一个数据

正好存储层里有这个数据
返回数据(皆大欢喜,happy ending~)

假如存储层里没这个数据

手动调用各种方法去同步这个数据
手动存到存储层中,以便下次读取

各位有没有看出其中麻烦的地方在哪儿?数据同步部分的复杂度全留给了业务侧。

让我们回归这件事的【初心】:我仅仅需要获取这个数据!我不管它是来自存储层、来自接口数据、还是来自其他什么地方 …
2.1. 数据同步函数
因此 tua-storage 在读取数据时很贴心地提供了一个 syncFn 参数,作为数据同步的函数,当请求的数据不存在或已过期时自动调用该函数。并且数据同步后默认会保存下来,这样下次再请求时存储层中就有数据了。
syncParams 的使用场景是接口需要传参时,这些参数会传给 syncFn。
tuaStorage.load({
key: ‘some data’,
syncFn: ({a}) => axios(‘some api url’ + a),
// 以下参数会传到 syncFn 中
syncParams: {a: ‘a’},
})
这么一来,存储层就和接口层对接起来了。业务侧再也不用手动调用 api 获取数据。
2.2. 合并分散配置
每次读取数据时如果都要手动传同步函数,实际编码时还是很麻烦 …
不急,吃口药~
tua-storage 在初始化时能够传递一个叫做 syncFnMap 参数。顾名思义,这是一个将 key 和 syncFn 映射起来的对象。
const tuaStorage = new TuaStorage({
// …
syncFnMap: {
‘data one’: () => axios(‘data one api’),
‘data two’: () => axios(‘data two api’),
// …
},
})

// 不用手动传 syncFn,默认匹配 syncFnMap 中的对应函数
tuaStorage.load({key: ‘data one’})
2.3. 自动生成配置
其实手动编写每个 api 请求函数也是很繁琐的,要是有个根据配置自动生成请求函数的库就好了~
诶~,巧了么不是~。各位开发者老爷们了解一下同样跨平台的 tua-api ~?
tua-storage 搭配 tua-api 之后会变成这样
import TuaStorage from ‘tua-storage’
import {getSyncFnMapByApis} from ‘tua-api’

// 本地写好的各种接口配置
import * as apis from ‘@/apis’

const tuaStorage = new TuaStorage({
syncFnMap: getSyncFnMapByApis(apis),
})
三、数据过期逻辑
一般各个平台的存储层都没有数据过期这一逻辑。但在使用 tua-storage 时默认每个数据都有过期时间这一属性。
3.1. 默认过期时间
默认为 30 秒,可以在初始化时配置默认超时时间。
import TuaStorage from ‘tua-storage’

const tuaStorage = new TuaStorage({
// 改为 60 秒
defaultExpires: 60,
})

// 返回一个 Promise
tuaStorage
.save({
key: ‘data key’,
data: {foo: ‘bar’},

// 这里传递的过期时间优先级更高
expires: 90,
})
.then(console.log)
.catch(console.error)

// 保存到 storage 中的数据大概长这样
// key 之前会加上初始化传入的默认前缀
{
‘TUA_STORAGE_PREFIX: data key’: {
expires: 90,
rawData: {foo: ‘bar’},
},
}
3.2. 数据保存前缀
为了保证存在 storage 中的数据名称不冲突,以及实现版本控制,tua-storage 默认有一个存储前缀:storageKeyPrefix。
默认值为 TUA_STORAGE_PREFIX:,所以在上一小节中保存的数据会有一个奇怪的前缀。
保证名称不冲突很好理解,如何实现版本控制呢?
3.3. 白名单机制
clear 函数能够接受一个白名单数组(因为内部是通过 indexOf 来判断的,所以不必填写完整的 key 值)。
import TuaStorage from ‘tua-storage’

const tuaStorage = new TuaStorage({…})

tuaStorage.clear([‘key’])
.then(console.log)
.catch(console.error)

// 假设现在 storage 中有以下数据
{
‘foo’: {},
‘bar’: {},
‘foo-key’: {},
‘bar-key’: {},
}

// 清除后剩下的数据是
{
‘foo-key’: {},
‘bar-key’: {},
}
所以在调用 clear 时,在白名单中传入新的存储前缀,即可实现删除上一版本数据的功能。
import TuaStorage from ‘tua-storage’

// 上一版本的前缀
const prefix1 = ‘STORAGE_PREFIX_V1.0: ‘

// 这一版本的前缀
const prefix2 = ‘STORAGE_PREFIX_V1.1: ‘

const tuaStorage = new TuaStorage({
// 将默认前缀切换成新版本的
storageKeyPrefix: prefix2,
})

// 开始清除上个版本的数据
tuaStorage.clear([prefix2])
.then(console.log)
.catch(console.error)
更多默认配置参阅这里的文档
四、自动清理过期数据
默认在启动时会进行一次过期数据清理(可以关闭),之后每过一段时间会再次清理。
什么样的数据会被清理呢?
4.1. 清理逻辑
首先当然是清理已到过期时间的数据,即有一个属性为 expires 的数据,且当前时间已超过了该时间。
一旦遇到不满足格式的数据(非对象、没有 expires 属性)则跳过,这样就不会误清除其他程序保存的数据。
4.2. 清理时间间隔
在初始化时可传入 autoClearTime 修改默认自动清理时间间隔。
默认为一分钟,注意是以秒为单位。
五、支持永久保存
在某些场景下,可能不方便写过期时间,这时默认可以传递 expires: null,标记该数据永不过期。
不喜欢用 null 标记?
大丈夫~,初始化时传递 neverExpireMark 即可修改为你喜欢的别的标记。
import TuaStorage from ‘tua-storage’

const tuaStorage = new TuaStorage({
neverExpireMark: ‘never’,
})

// 永不过期
tuaStorage.save({
key: ‘some key’,
data: ‘some data’,
expires: ‘never’,
})
六、支持批量操作
假设现在有一组数据需要保存或读取,常规操作就是使用 Promise.all 发起多个操作。
import TuaStorage from ‘tua-storage’

const tuaStorage = new TuaStorage({…})

const dataToBeSaved = [
{key: ‘key one’, data: ‘some data’},
{key: ‘key two’, data: ‘some data’},
]

// 异步
const result = dataToBeSaved
.map(tuaStorage.save.bind(tuaStorage))
.then(Promise.all.bind(Promise))

// 同步
const result = dataToBeSaved
.map(tuaStorage.saveSync.bind(tuaStorage))
讲道理这样写还是挺烦的 … 所以 tua-storage 的各个 api 还支持直接传入数组:
// 异步
tuaStorage.save(dataToBeSaved)
.then(console.log)
.catch(console.log)

// 同步
tuaStorage.saveSync(dataToBeSaved)
七、小结
还在为 web 端、小程序端、React-Native 端、node 端业务侧代码使用不一样的方式调用存储层烦恼么?还在为手动数据同步,保存数据,处理过期逻辑而烦躁么?各位开发者老爷们不妨试一试 tua-storage,(挤需体验三番钟,里造会干我一样,爱象介款工具)。

灵感来源
inspired by react-native-storage

正文完
 0